parser: allow multiple types in match branch (#6505)

pull/6517/head
Enzo 2020-10-01 01:07:36 +02:00 committed by GitHub
parent 18be7b115a
commit 324d547cdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 267 additions and 20 deletions

View File

@ -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
}

View 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 {

View 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
}
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -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
}
}
}