mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
parser: allow multiple types in match branch (#6505)
This commit is contained in:
parent
18be7b115a
commit
324d547cdb
@ -1017,6 +1017,7 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
|
||||
call_expr.left_type = left_type
|
||||
left_type_sym := c.table.get_type_symbol(c.unwrap_generic(left_type))
|
||||
method_name := call_expr.name
|
||||
mut unknown_method_msg := 'unknown method: `${left_type_sym.source_name}.$method_name`'
|
||||
if left_type.has_flag(.optional) {
|
||||
c.error('optional type cannot be called directly', call_expr.left.position())
|
||||
return table.void_type
|
||||
@ -1213,6 +1214,11 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
|
||||
}
|
||||
call_expr.return_type = method.return_type
|
||||
return method.return_type
|
||||
} else {
|
||||
if left_type_sym.kind == .aggregate {
|
||||
// the error message contains the problematic type
|
||||
unknown_method_msg = err
|
||||
}
|
||||
}
|
||||
// TODO: str methods
|
||||
if method_name == 'str' {
|
||||
@ -1246,8 +1252,7 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
|
||||
}
|
||||
if left_type != table.void_type {
|
||||
suggestion := util.new_suggestion(method_name, left_type_sym.methods.map(it.name))
|
||||
c.error(suggestion.say('unknown method: `${left_type_sym.source_name}.$method_name`'),
|
||||
call_expr.pos)
|
||||
c.error(suggestion.say(unknown_method_msg), call_expr.pos)
|
||||
}
|
||||
return table.void_type
|
||||
}
|
||||
@ -1651,19 +1656,24 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.T
|
||||
return table.int_type
|
||||
}
|
||||
}
|
||||
mut unknown_field_msg := 'type `$sym.source_name` has no field or method `$field_name`'
|
||||
if field := c.table.struct_find_field(sym, field_name) {
|
||||
if sym.mod != c.mod && !field.is_pub {
|
||||
c.error('field `${sym.source_name}.$field_name` is not public', selector_expr.pos)
|
||||
}
|
||||
selector_expr.typ = field.typ
|
||||
return field.typ
|
||||
} else {
|
||||
if sym.kind == .aggregate {
|
||||
unknown_field_msg = err
|
||||
}
|
||||
}
|
||||
if sym.kind != .struct_ {
|
||||
if sym.kind !in [.struct_, .aggregate] {
|
||||
if sym.kind != .placeholder {
|
||||
c.error('`$sym.source_name` is not a struct', selector_expr.pos)
|
||||
}
|
||||
} else {
|
||||
c.error('type `$sym.source_name` has no field or method `$field_name`', selector_expr.pos)
|
||||
c.error(unknown_field_msg, selector_expr.pos)
|
||||
}
|
||||
return table.void_type
|
||||
}
|
||||
|
14
vlib/v/checker/tests/match_sumtype_multiple_types.out
Normal file
14
vlib/v/checker/tests/match_sumtype_multiple_types.out
Normal file
@ -0,0 +1,14 @@
|
||||
vlib/v/checker/tests/match_sumtype_multiple_types.vv:25:13: error: type `Charlie` has no field or method `char`
|
||||
23 | match NATOAlphabet(a) as l {
|
||||
24 | Alfa, Charlie {
|
||||
25 | assert l.char == `a`
|
||||
| ~~~~
|
||||
26 | assert l.letter() == 'a'
|
||||
27 | }
|
||||
vlib/v/checker/tests/match_sumtype_multiple_types.vv:26:13: error: unknown method: `Charlie.letter`
|
||||
24 | Alfa, Charlie {
|
||||
25 | assert l.char == `a`
|
||||
26 | assert l.letter() == 'a'
|
||||
| ~~~~~~~~~
|
||||
27 | }
|
||||
28 | Bravo {
|
32
vlib/v/checker/tests/match_sumtype_multiple_types.vv
Normal file
32
vlib/v/checker/tests/match_sumtype_multiple_types.vv
Normal file
@ -0,0 +1,32 @@
|
||||
struct Alfa {
|
||||
char rune
|
||||
}
|
||||
|
||||
fn (a Alfa) letter() rune {
|
||||
return a.char
|
||||
}
|
||||
|
||||
struct Bravo {
|
||||
char rune
|
||||
}
|
||||
|
||||
fn (b Bravo) letter() rune {
|
||||
return b.char
|
||||
}
|
||||
|
||||
struct Charlie {}
|
||||
|
||||
type NATOAlphabet = Alfa | Bravo | Charlie
|
||||
|
||||
fn method_not_exists() {
|
||||
a := Alfa{}
|
||||
match NATOAlphabet(a) as l {
|
||||
Alfa, Charlie {
|
||||
assert l.char == `a`
|
||||
assert l.letter() == 'a'
|
||||
}
|
||||
Bravo {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
}
|
@ -283,6 +283,9 @@ pub fn (mut g JsGen) typ(t table.Type) string {
|
||||
.rune {
|
||||
styp = 'any'
|
||||
}
|
||||
.aggregate {
|
||||
panic('TODO: unhandled aggregate in JS')
|
||||
}
|
||||
}
|
||||
/*
|
||||
else {
|
||||
|
@ -6,6 +6,7 @@ module parser
|
||||
import v.ast
|
||||
import v.table
|
||||
import v.token
|
||||
import strings
|
||||
|
||||
fn (mut p Parser) if_expr(is_comptime bool) ast.IfExpr {
|
||||
was_inside_if_expr := p.inside_if_expr
|
||||
@ -210,14 +211,48 @@ fn (mut p Parser) match_expr() ast.MatchExpr {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sum type match
|
||||
typ := p.parse_type()
|
||||
exprs << ast.Type{
|
||||
typ: typ
|
||||
mut types := []table.Type{}
|
||||
for {
|
||||
// Sum type match
|
||||
parsed_type := p.parse_type()
|
||||
types << parsed_type
|
||||
exprs << ast.Type{
|
||||
typ: parsed_type
|
||||
}
|
||||
if p.tok.kind != .comma {
|
||||
break
|
||||
}
|
||||
p.check(.comma)
|
||||
}
|
||||
mut it_typ := table.void_type
|
||||
if types.len == 1 {
|
||||
it_typ = types[0]
|
||||
} else {
|
||||
// there is more than one types, so we must create a type aggregate
|
||||
mut agg_name := strings.new_builder(20)
|
||||
agg_name.write('(')
|
||||
for i, typ in types {
|
||||
if i > 0 {
|
||||
agg_name.write(' | ')
|
||||
}
|
||||
type_str := p.table.type_to_str(typ)
|
||||
agg_name.write(p.prepend_mod(type_str))
|
||||
}
|
||||
agg_name.write(')')
|
||||
name := agg_name.str()
|
||||
it_typ = p.table.register_type_symbol(table.TypeSymbol{
|
||||
name: name
|
||||
source_name: name
|
||||
kind: .aggregate
|
||||
mod: p.mod
|
||||
info: table.Aggregate{
|
||||
types: types
|
||||
}
|
||||
})
|
||||
}
|
||||
p.scope.register('it', ast.Var{
|
||||
name: 'it'
|
||||
typ: typ.to_ptr()
|
||||
typ: it_typ.to_ptr()
|
||||
pos: cond_pos
|
||||
is_used: true
|
||||
is_mut: is_mut
|
||||
@ -226,18 +261,13 @@ fn (mut p Parser) match_expr() ast.MatchExpr {
|
||||
// Register shadow variable or `as` variable with actual type
|
||||
p.scope.register(var_name, ast.Var{
|
||||
name: var_name
|
||||
typ: typ.to_ptr()
|
||||
typ: it_typ.to_ptr()
|
||||
pos: cond_pos
|
||||
is_used: true
|
||||
is_changed: true // TODO mut unchanged warning hack, remove
|
||||
is_mut: is_mut
|
||||
})
|
||||
}
|
||||
// TODO
|
||||
if p.tok.kind == .comma {
|
||||
p.next()
|
||||
p.parse_type()
|
||||
}
|
||||
is_sum_type = true
|
||||
} else {
|
||||
// Expression match
|
||||
|
@ -16,7 +16,7 @@ import strings
|
||||
pub type Type = int
|
||||
|
||||
pub type TypeInfo = Alias | Array | ArrayFixed | Chan | Enum | FnType | GenericStructInst |
|
||||
Interface | Map | MultiReturn | Struct | SumType
|
||||
Interface | Map | MultiReturn | Struct | SumType | Aggregate
|
||||
|
||||
pub enum Language {
|
||||
v
|
||||
@ -366,6 +366,7 @@ pub enum Kind {
|
||||
interface_
|
||||
any_float
|
||||
any_int
|
||||
aggregate
|
||||
}
|
||||
|
||||
pub fn (t &TypeSymbol) str() string {
|
||||
@ -681,6 +682,7 @@ pub fn (k Kind) str() string {
|
||||
.ustring { 'ustring' }
|
||||
.generic_struct_inst { 'generic_struct_inst' }
|
||||
.rune { 'rune' }
|
||||
.aggregate { 'aggregate' }
|
||||
}
|
||||
return k_str
|
||||
}
|
||||
@ -730,6 +732,13 @@ pub:
|
||||
language Language
|
||||
}
|
||||
|
||||
pub struct Aggregate {
|
||||
mut:
|
||||
fields []Field // used for faster lookup inside the module
|
||||
pub:
|
||||
types []Type
|
||||
}
|
||||
|
||||
// NB: FExpr here is a actually an ast.Expr .
|
||||
// It should always be used by casting to ast.Expr, using ast.fe2ex()/ast.ex2fe()
|
||||
// That hack is needed to break an import cycle between v.ast and v.table .
|
||||
@ -749,6 +758,15 @@ pub mut:
|
||||
is_global bool
|
||||
}
|
||||
|
||||
fn (f &Field) equals(o &Field) bool {
|
||||
return f.name == o.name &&
|
||||
f.typ == o.typ &&
|
||||
// TODO Should those be checked ?
|
||||
f.is_pub == o.is_pub &&
|
||||
f.is_mut == o.is_mut &&
|
||||
f.is_global == o.is_global
|
||||
}
|
||||
|
||||
pub struct Array {
|
||||
pub:
|
||||
nr_dims int
|
||||
@ -899,6 +917,15 @@ pub fn (t &TypeSymbol) str_method_info() (bool, bool, int) {
|
||||
return has_str_method, expects_ptr, nr_args
|
||||
}
|
||||
|
||||
fn (a &Aggregate) find_field(name string) ?Field {
|
||||
for field in a.fields {
|
||||
if field.name == name {
|
||||
return field
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
pub fn (s Struct) find_field(name string) ?Field {
|
||||
for field in s.fields {
|
||||
if field.name == name {
|
||||
|
@ -39,6 +39,18 @@ pub mut:
|
||||
name string
|
||||
}
|
||||
|
||||
fn (f &Fn) method_equals(o &Fn) bool {
|
||||
return f.params[1..].equals(o.params[1..]) &&
|
||||
f.return_type == o.return_type &&
|
||||
f.return_type_source_name == o.return_type_source_name &&
|
||||
f.is_variadic == o.is_variadic &&
|
||||
f.language == o.language &&
|
||||
f.is_generic == o.is_generic &&
|
||||
f.is_pub == o.is_pub &&
|
||||
f.mod == o.mod &&
|
||||
f.name == o.name
|
||||
}
|
||||
|
||||
pub struct Param {
|
||||
pub:
|
||||
pos token.Position
|
||||
@ -49,6 +61,26 @@ pub:
|
||||
is_hidden bool // interface first arg
|
||||
}
|
||||
|
||||
fn (p &Param) equals(o &Param) bool {
|
||||
return p.name == o.name
|
||||
&& p.is_mut == o.is_mut
|
||||
&& p.typ == o.typ
|
||||
&& p.type_source_name == o.type_source_name
|
||||
&& p.is_hidden == o.is_hidden
|
||||
}
|
||||
|
||||
fn (p []Param) equals(o []Param) bool {
|
||||
if p.len != o.len {
|
||||
return false
|
||||
}
|
||||
for i in 0..p.len {
|
||||
if !p[i].equals(o[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
pub struct Var {
|
||||
pub:
|
||||
name string
|
||||
@ -139,6 +171,32 @@ pub fn (mut t TypeSymbol) register_method(new_fn Fn) {
|
||||
t.methods << new_fn
|
||||
}
|
||||
|
||||
pub fn (t &Table) register_aggregate_method(mut sym TypeSymbol, name string) ?Fn {
|
||||
if sym.kind != .aggregate {
|
||||
panic('Unexpected type symbol: $sym.kind')
|
||||
}
|
||||
agg_info := sym.info as Aggregate
|
||||
// an aggregate always has at least 2 types
|
||||
mut found_once := false
|
||||
mut new_fn := Fn{}
|
||||
for typ in agg_info.types {
|
||||
ts := t.get_type_symbol(typ)
|
||||
if type_method := ts.find_method(name) {
|
||||
if !found_once {
|
||||
found_once = true
|
||||
new_fn = type_method
|
||||
} else if !new_fn.method_equals(type_method) {
|
||||
return error('method `${t.type_to_str(typ)}.$name` signature is different')
|
||||
}
|
||||
} else {
|
||||
return error('unknown method: `${t.type_to_str(typ)}.$name`')
|
||||
}
|
||||
}
|
||||
// register the method in the aggregate, so lookup is faster next time
|
||||
sym.register_method(new_fn)
|
||||
return new_fn
|
||||
}
|
||||
|
||||
pub fn (t &Table) type_has_method(s &TypeSymbol, name string) bool {
|
||||
// println('type_has_method($s.name, $name) types.len=$t.types.len s.parent_idx=$s.parent_idx')
|
||||
if _ := t.type_find_method(s, name) {
|
||||
@ -155,6 +213,10 @@ pub fn (t &Table) type_find_method(s &TypeSymbol, name string) ?Fn {
|
||||
if method := ts.find_method(name) {
|
||||
return method
|
||||
}
|
||||
if ts.kind == .aggregate {
|
||||
method := t.register_aggregate_method(mut ts, name) ?
|
||||
return method
|
||||
}
|
||||
if ts.parent_idx == 0 {
|
||||
break
|
||||
}
|
||||
@ -163,6 +225,31 @@ pub fn (t &Table) type_find_method(s &TypeSymbol, name string) ?Fn {
|
||||
return none
|
||||
}
|
||||
|
||||
fn (t &Table) register_aggregate_field(mut sym TypeSymbol, name string) ?Field {
|
||||
if sym.kind != .aggregate {
|
||||
panic('Unexpected type symbol: $sym.kind')
|
||||
}
|
||||
mut agg_info := sym.info as Aggregate
|
||||
// an aggregate always has at least 2 types
|
||||
mut found_once := false
|
||||
mut new_field := Field{}
|
||||
for typ in agg_info.types {
|
||||
ts := t.get_type_symbol(typ)
|
||||
if type_field := t.struct_find_field(ts, name) {
|
||||
if !found_once {
|
||||
found_once = true
|
||||
new_field = type_field
|
||||
} else if !new_field.equals(type_field) {
|
||||
return error('field `${t.type_to_str(typ)}.$name` type is different')
|
||||
}
|
||||
} else {
|
||||
return error('type `${t.type_to_str(typ)}` has no field or method `$name`')
|
||||
}
|
||||
}
|
||||
agg_info.fields << new_field
|
||||
return new_field
|
||||
}
|
||||
|
||||
pub fn (t &Table) struct_has_field(s &TypeSymbol, name string) bool {
|
||||
// println('struct_has_field($s.name, $name) types.len=$t.types.len s.parent_idx=$s.parent_idx')
|
||||
if _ := t.struct_find_field(s, name) {
|
||||
@ -176,11 +263,18 @@ pub fn (t &Table) struct_find_field(s &TypeSymbol, name string) ?Field {
|
||||
// println('struct_find_field($s.name, $name) types.len=$t.types.len s.parent_idx=$s.parent_idx')
|
||||
mut ts := s
|
||||
for {
|
||||
if ts.info is Struct {
|
||||
struct_info := ts.info as Struct
|
||||
if ts.info is Struct as struct_info {
|
||||
if field := struct_info.find_field(name) {
|
||||
return field
|
||||
}
|
||||
} else if ts.info is Aggregate as agg_info {
|
||||
if field := agg_info.find_field(name) {
|
||||
return field
|
||||
}
|
||||
field := t.register_aggregate_field(mut ts, name) or {
|
||||
return error(err)
|
||||
}
|
||||
return field
|
||||
}
|
||||
if ts.parent_idx == 0 {
|
||||
break
|
||||
@ -396,7 +490,7 @@ pub fn (t &Table) map_source_name(key_type, value_type Type) string {
|
||||
key_type_sym := t.get_type_symbol(key_type)
|
||||
value_type_sym := t.get_type_symbol(value_type)
|
||||
ptr := if value_type.is_ptr() { '&' } else { '' }
|
||||
return 'map[${key_type_sym.source_name}]$ptr$value_type_sym.source_name'
|
||||
return 'map[$key_type_sym.source_name]$ptr$value_type_sym.source_name'
|
||||
}
|
||||
|
||||
pub fn (mut t Table) find_or_register_chan(elem_type Type, is_mut bool) int {
|
||||
@ -415,7 +509,7 @@ pub fn (mut t Table) find_or_register_chan(elem_type Type, is_mut bool) int {
|
||||
source_name: source_name
|
||||
info: Chan{
|
||||
elem_type: elem_type
|
||||
is_mut: is_mut
|
||||
is_mut: is_mut
|
||||
}
|
||||
}
|
||||
return t.register_type_symbol(chan_typ)
|
||||
|
@ -164,3 +164,40 @@ fn test_sum_type_name() {
|
||||
}
|
||||
assert f(a) == 'A1'
|
||||
}
|
||||
|
||||
struct Alfa {
|
||||
char rune = `a`
|
||||
}
|
||||
|
||||
fn (a Alfa) letter() rune {
|
||||
return a.char
|
||||
}
|
||||
|
||||
struct Bravo {
|
||||
// A field so that Alfa and Bravo structures aren't the same
|
||||
dummy_field int
|
||||
char rune = `b`
|
||||
}
|
||||
|
||||
fn (b Bravo) letter() rune {
|
||||
return b.char
|
||||
}
|
||||
|
||||
struct Charlie {}
|
||||
|
||||
type NATOAlphabet = Alfa | Bravo | Charlie
|
||||
|
||||
fn test_match_sum_type_multiple_type() {
|
||||
a := Alfa{}
|
||||
// TODO This currently works because cgen takes the first type as the type of `l`
|
||||
// it would fail if we `a` was of type `Bravo`
|
||||
match NATOAlphabet(a) as l {
|
||||
Alfa, Bravo {
|
||||
assert l.char == `a`
|
||||
assert l.letter() == `a`
|
||||
}
|
||||
Charlie {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user