x.json2,checker,toml: allow field.typ compile-time checking with MatchExpr and add array of option checking (#21171)

This commit is contained in:
Felipe Pena 2024-04-04 06:43:59 -03:00 committed by GitHub
parent ba577e4e5f
commit 625048c5b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 143 additions and 83 deletions

View File

@ -578,18 +578,6 @@ pub fn (s string) bool() bool {
return s == 'true' || s == 't' // TODO: t for pg, remove
}
// int returns the value of the string as an integer `'1'.int() == 1`.
@[inline]
pub fn (s string) int() int {
return int(strconv.common_parse_int(s, 0, 32, false, false) or { 0 })
}
// i64 returns the value of the string as i64 `'1'.i64() == i64(1)`.
@[inline]
pub fn (s string) i64() i64 {
return strconv.common_parse_int(s, 0, 64, false, false) or { 0 }
}
// i8 returns the value of the string as i8 `'1'.i8() == i8(1)`.
@[inline]
pub fn (s string) i8() i8 {
@ -602,6 +590,24 @@ pub fn (s string) i16() i16 {
return i16(strconv.common_parse_int(s, 0, 16, false, false) or { 0 })
}
// i32 returns the value of the string as i32 `'1'.i32() == i32(1)`.
@[inline]
pub fn (s string) i32() i32 {
return i32(strconv.common_parse_int(s, 0, 32, false, false) or { 0 })
}
// int returns the value of the string as an integer `'1'.int() == 1`.
@[inline]
pub fn (s string) int() int {
return int(strconv.common_parse_int(s, 0, 32, false, false) or { 0 })
}
// i64 returns the value of the string as i64 `'1'.i64() == i64(1)`.
@[inline]
pub fn (s string) i64() i64 {
return strconv.common_parse_int(s, 0, 64, false, false) or { 0 }
}
// f32 returns the value of the string as f32 `'1.0'.f32() == f32(1)`.
@[inline]
pub fn (s string) f32() f32 {
@ -614,12 +620,6 @@ pub fn (s string) f64() f64 {
return strconv.atof64(s) or { 0 }
}
// u8 returns the value of the string as u8 `'1'.u8() == u8(1)`.
@[inline]
pub fn (s string) u8() u8 {
return u8(strconv.common_parse_uint(s, 0, 8, false, false) or { 0 })
}
// u8_array returns the value of the hex/bin string as u8 array.
// hex string example: `'0x11223344ee'.u8_array() == [u8(0x11),0x22,0x33,0x44,0xee]`.
// bin string example: `'0b1101_1101'.u8_array() == [u8(0xdd)]`.
@ -673,6 +673,12 @@ pub fn (s string) u8_array() []u8 {
return []u8{}
}
// u8 returns the value of the string as u8 `'1'.u8() == u8(1)`.
@[inline]
pub fn (s string) u8() u8 {
return u8(strconv.common_parse_uint(s, 0, 8, false, false) or { 0 })
}
// u16 returns the value of the string as u16 `'1'.u16() == u16(1)`.
@[inline]
pub fn (s string) u16() u16 {

View File

@ -58,70 +58,70 @@ fn decode_struct[T](doc Any, mut typ T) {
typ.$(field.name) = value.time()
} $else $if field.is_array {
arr := value.array()
match typeof(typ.$(field.name)).name {
'[]string' { typ.$(field.name) = arr.as_strings() }
'[]int' { typ.$(field.name) = arr.map(it.int()) }
'[]i64' { typ.$(field.name) = arr.map(it.i64()) }
'[]u64' { typ.$(field.name) = arr.map(it.u64()) }
'[]f32' { typ.$(field.name) = arr.map(it.f32()) }
'[]f64' { typ.$(field.name) = arr.map(it.f64()) }
'[]bool' { typ.$(field.name) = arr.map(it.bool()) }
'[]toml.DateTime' { typ.$(field.name) = arr.map(it.datetime()) }
'[]toml.Date' { typ.$(field.name) = arr.map(it.date()) }
'[]toml.Time' { typ.$(field.name) = arr.map(it.time()) }
match field.typ {
[]string { typ.$(field.name) = arr.as_strings() }
[]int { typ.$(field.name) = arr.map(it.int()) }
[]i64 { typ.$(field.name) = arr.map(it.i64()) }
[]u64 { typ.$(field.name) = arr.map(it.u64()) }
[]f32 { typ.$(field.name) = arr.map(it.f32()) }
[]f64 { typ.$(field.name) = arr.map(it.f64()) }
[]bool { typ.$(field.name) = arr.map(it.bool()) }
[]DateTime { typ.$(field.name) = arr.map(it.datetime()) }
[]Date { typ.$(field.name) = arr.map(it.date()) }
[]Time { typ.$(field.name) = arr.map(it.time()) }
else {}
}
} $else $if field.is_map {
mut mmap := value.as_map()
match typeof(typ.$(field.name)).name {
'map[string]string' {
match field.typ {
map[string]string {
typ.$(field.name) = mmap.as_strings()
}
// Should be cleaned up to use the more modern lambda syntax
// |k, v| k, v.int()
// Unfortunately lambdas have issues with multiple return at the time of writing
'map[string]int' {
map[string]int {
typ.$(field.name) = maps.to_map[string, Any, string, int](mmap, fn (k string, v Any) (string, int) {
return k, v.int()
})
}
'map[string]i64' {
map[string]i64 {
typ.$(field.name) = maps.to_map[string, Any, string, i64](mmap, fn (k string, v Any) (string, i64) {
return k, v.i64()
})
}
'map[string]u64' {
map[string]u64 {
typ.$(field.name) = maps.to_map[string, Any, string, u64](mmap, fn (k string, v Any) (string, u64) {
return k, v.u64()
})
}
'map[string]f32' {
map[string]f32 {
typ.$(field.name) = maps.to_map[string, Any, string, f32](mmap, fn (k string, v Any) (string, f32) {
return k, v.f32()
})
}
'map[string]f64' {
map[string]f64 {
typ.$(field.name) = maps.to_map[string, Any, string, f64](mmap, fn (k string, v Any) (string, f64) {
return k, v.f64()
})
}
'map[string]bool' {
map[string]bool {
typ.$(field.name) = maps.to_map[string, Any, string, bool](mmap, fn (k string, v Any) (string, bool) {
return k, v.bool()
})
}
'map[string]toml.DateTime' {
map[string]DateTime {
typ.$(field.name) = maps.to_map[string, Any, string, DateTime](mmap,
fn (k string, v Any) (string, DateTime) {
return k, v.datetime()
})
}
'map[string]toml.Date' {
map[string]Date {
typ.$(field.name) = maps.to_map[string, Any, string, Date](mmap, fn (k string, v Any) (string, Date) {
return k, v.date()
})
}
'map[string]toml.Time' {
map[string]Time {
typ.$(field.name) = maps.to_map[string, Any, string, Time](mmap, fn (k string, v Any) (string, Time) {
return k, v.time()
})

View File

@ -322,7 +322,15 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym
c.error('struct instances cannot be matched by type name, they can only be matched to other instances of the same struct type',
branch.pos)
}
if mut expr is ast.TypeNode && cond_sym.is_primitive() {
mut is_comptime := false
if c.comptime.inside_comptime_for {
// it is a compile-time field.typ checking
if mut node.cond is ast.SelectorExpr {
is_comptime = node.cond.expr_type == c.field_data_type
&& node.cond.field_name == 'typ'
}
}
if mut expr is ast.TypeNode && cond_sym.is_primitive() && !is_comptime {
c.error('matching by type can only be done for sum types, generics, interfaces, `${node.cond}` is none of those',
branch.pos)
}
@ -442,7 +450,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym
expect_str := c.table.type_to_str(node.cond_type)
c.error('cannot match alias type `${expect_str}` with `${expr_str}`',
expr.pos())
} else if !c.check_types(expr_type, node.cond_type) {
} else if !c.check_types(expr_type, node.cond_type) && !is_comptime {
expr_str := c.table.type_to_str(expr_type)
expect_str := c.table.type_to_str(node.cond_type)
c.error('cannot match `${expect_str}` with `${expr_str}`', expr.pos())

View File

@ -212,6 +212,8 @@ fn (mut p Parser) is_only_array_type() bool {
next_kind := p.peek_token(i + 1).kind
if next_kind == .name {
return true
} else if next_kind == .question && p.peek_token(i + 2).kind == .name {
return true
} else if next_kind == .lsbr {
continue
} else {

View File

@ -603,7 +603,7 @@ fn (mut p Parser) parse_any_type(language ast.Language, is_ptr bool, check_dot b
p.next()
p.check(.dot)
}
if !p.known_import(mod) && !p.pref.is_fmt {
if mod != p.mod && !p.known_import(mod) && !p.pref.is_fmt {
mut msg := 'unknown module `${mod}`'
if mod.len > mod_last_part.len && p.known_import(mod_last_part) {
msg += '; did you mean `${mod_last_part}`?'

View File

@ -0,0 +1,21 @@
struct Test {
a int
b []int
c map[int]string
d []?int
}
fn test_main() {
mut i := 1
$for f in Test.fields {
type_name := typeof(f.$(f.name)).name
match f.typ {
int { assert i == 1, '1. ${f.name} is ${type_name}' }
[]int { assert i == 2, '2. ${f.name} is ${type_name}' }
map[int]string { assert i == 3, '3. ${f.name} is ${type_name}' }
[]?int { assert i == 4, '4. ${f.name} is ${type_name}' }
else {}
}
i++
}
}

View File

@ -179,7 +179,7 @@ fn decode_struct[T](_ T, res map[string]Any) !T {
} $else $if field.typ is i16 {
typ.$(field.name) = res[json_name]!.int()
} $else $if field.typ is i32 {
typ.$(field.name) = i32(res[field.name]!.int())
typ.$(field.name) = i32(res[field.name]!.i32())
} $else $if field.typ is i64 {
typ.$(field.name) = res[json_name]!.i64()
} $else $if field.typ is ?u8 {
@ -250,38 +250,40 @@ fn decode_struct[T](_ T, res map[string]Any) !T {
}
} $else $if field.is_array {
arr := res[field.name]! as []Any
match typeof(typ.$(field.name)).name {
'[]bool' { typ.$(field.name) = arr.map(it.bool()) }
'[]?bool' { typ.$(field.name) = arr.map(?bool(it.bool())) }
'[]f32' { typ.$(field.name) = arr.map(it.f32()) }
'[]?f32' { typ.$(field.name) = arr.map(?f32(it.f32())) }
'[]f64' { typ.$(field.name) = arr.map(it.f64()) }
'[]?f64' { typ.$(field.name) = arr.map(?f64(it.f64())) }
'[]i8' { typ.$(field.name) = arr.map(it.i8()) }
'[]?i8' { typ.$(field.name) = arr.map(?i8(it.i8())) }
'[]i16' { typ.$(field.name) = arr.map(it.i16()) }
'[]?i16' { typ.$(field.name) = arr.map(?i16(it.i16())) }
'[]i64' { typ.$(field.name) = arr.map(it.i64()) }
'[]?i64' { typ.$(field.name) = arr.map(?i64(it.i64())) }
'[]int' { typ.$(field.name) = arr.map(it.int()) }
'[]?int' { typ.$(field.name) = arr.map(?int(it.int())) }
'[]string' { typ.$(field.name) = arr.map(it.str()) }
'[]?string' { typ.$(field.name) = arr.map(?string(it.str())) }
// vfmt off
match field.typ {
[]bool { typ.$(field.name) = arr.map(it.bool()) }
[]?bool { typ.$(field.name) = arr.map(?bool(it.bool())) }
[]f32 { typ.$(field.name) = arr.map(it.f32()) }
[]?f32 { typ.$(field.name) = arr.map(?f32(it.f32())) }
[]f64 { typ.$(field.name) = arr.map(it.f64()) }
[]?f64 { typ.$(field.name) = arr.map(?f64(it.f64())) }
[]i8 { typ.$(field.name) = arr.map(it.i8()) }
[]?i8 { typ.$(field.name) = arr.map(?i8(it.i8())) }
[]i16 { typ.$(field.name) = arr.map(it.i16()) }
[]?i16 { typ.$(field.name) = arr.map(?i16(it.i16())) }
[]i32 { typ.$(field.name) = arr.map(it.i32()) }
[]?i32 { typ.$(field.name) = arr.map(?i32(it.i32())) }
[]i64 { typ.$(field.name) = arr.map(it.i64()) }
[]?i64 { typ.$(field.name) = arr.map(?i64(it.i64())) }
[]int { typ.$(field.name) = arr.map(it.int()) }
[]?int { typ.$(field.name) = arr.map(?int(it.int())) }
[]string { typ.$(field.name) = arr.map(it.str()) }
[]?string { typ.$(field.name) = arr.map(?string(it.str())) }
// NOTE: Using `!` on `to_time()` inside the array method causes a builder error - 2024/04/01.
'[]time.Time' { typ.$(field.name) = arr.map(it.to_time() or { time.Time{} }) }
// vfmt off
'[]?time.Time' { typ.$(field.name) = arr.map(?time.Time(it.to_time() or { time.Time{} })) }
// vfmt on
'[]u8' { typ.$(field.name) = arr.map(it.u64()) }
'[]?u8' { typ.$(field.name) = arr.map(?u8(it.u64())) }
'[]u16' { typ.$(field.name) = arr.map(it.u64()) }
'[]?u16' { typ.$(field.name) = arr.map(?u16(it.u64())) }
'[]u32' { typ.$(field.name) = arr.map(it.u64()) }
'[]?u32' { typ.$(field.name) = arr.map(?u32(it.u64())) }
'[]u64' { typ.$(field.name) = arr.map(it.u64()) }
'[]?u64' { typ.$(field.name) = arr.map(?u64(it.u64())) }
[]time.Time { typ.$(field.name) = arr.map(it.to_time() or { time.Time{} }) }
[]?time.Time { typ.$(field.name) = arr.map(?time.Time(it.to_time() or { time.Time{} })) }
[]u8 { typ.$(field.name) = arr.map(it.u64()) }
[]?u8 { typ.$(field.name) = arr.map(?u8(it.u64())) }
[]u16 { typ.$(field.name) = arr.map(it.u64()) }
[]?u16 { typ.$(field.name) = arr.map(?u16(it.u64())) }
[]u32 { typ.$(field.name) = arr.map(it.u64()) }
[]?u32 { typ.$(field.name) = arr.map(?u32(it.u64())) }
[]u64 { typ.$(field.name) = arr.map(it.u64()) }
[]?u64 { typ.$(field.name) = arr.map(?u64(it.u64())) }
else {}
}
// vfmt on
} $else $if field.is_struct {
typ.$(field.name) = decode_struct(typ.$(field.name), res[field.name]!.as_map())!
} $else $if field.is_alias {

View File

@ -5,13 +5,13 @@ module json2
import time
// i8 - TODO
// i8 uses `Any` as a 16-bit integer.
pub fn (f Any) i8() i8 {
match f {
i8 {
return f
}
i16, int, i64, u8, u16, u32, u64, f32, f64, bool {
i16, i32, int, i64, u8, u16, u32, u64, f32, f64, bool {
return i8(f)
}
string {
@ -23,13 +23,13 @@ pub fn (f Any) i8() i8 {
}
}
// i16 - TODO
// i16 uses `Any` as a 16-bit integer.
pub fn (f Any) i16() i16 {
match f {
i16 {
return f
}
i8, int, i64, u8, u16, u32, u64, f32, f64, bool {
i8, i32, int, i64, u8, u16, u32, u64, f32, f64, bool {
return i16(f)
}
string {
@ -47,7 +47,7 @@ pub fn (f Any) int() int {
int {
return f
}
i8, i16, i64, u8, u16, u32, u64, f32, f64, bool {
i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool {
return int(f)
}
string {
@ -59,13 +59,31 @@ pub fn (f Any) int() int {
}
}
// i32 uses `Any` as a 32-bit integer.
pub fn (f Any) i32() i32 {
match f {
i32 {
return f
}
i8, i16, int, i64, u8, u16, u32, u64, f32, f64, bool {
return i32(f)
}
string {
return f.i32()
}
else {
return 0
}
}
}
// i64 uses `Any` as a 64-bit integer.
pub fn (f Any) i64() i64 {
match f {
i64 {
return f
}
i8, i16, int, u8, u16, u32, u64, f32, f64, bool {
i8, i16, i32, int, u8, u16, u32, u64, f32, f64, bool {
return i64(f)
}
string {
@ -83,7 +101,7 @@ pub fn (f Any) u64() u64 {
u64 {
return f
}
u8, u16, u32, i8, i16, int, i64, f32, f64, bool {
u8, u16, u32, i8, i16, i32, int, i64, f32, f64, bool {
return u64(f)
}
string {
@ -101,7 +119,7 @@ pub fn (f Any) f32() f32 {
f32 {
return f
}
bool, i8, i16, int, i64, u8, u16, u32, u64, f64 {
bool, i8, i16, i32, int, i64, u8, u16, u32, u64, f64 {
return f32(f)
}
string {
@ -119,7 +137,7 @@ pub fn (f Any) f64() f64 {
f64 {
return f
}
i8, i16, int, i64, u8, u16, u32, u64, f32 {
i8, i16, i32, int, i64, u8, u16, u32, u64, f32 {
return f64(f)
}
string {
@ -150,7 +168,7 @@ pub fn (f Any) bool() bool {
return false
}
}
i8, i16, int, i64 {
i8, i16, i32, int, i64 {
return i64(f) != 0
}
u8, u16, u32, u64 {
@ -266,6 +284,8 @@ pub fn map_from[T](t T) map[string]Any {
m[field.name] = t.$(field.name).str().i8()
} $else $if field.typ is i16 {
m[field.name] = t.$(field.name).str().i16()
} $else $if field.typ is i32 {
m[field.name] = t.$(field.name).str().i32()
} $else $if field.typ is int {
m[field.name] = t.$(field.name).str().int()
} $else $if field.typ is i64 {

View File

@ -9,6 +9,7 @@ pub type Any = Null
| f32
| f64
| i16
| i32
| i64
| i8
| int