From 8b072aa962e1c2f16ca80474c433e49d5e0a623e Mon Sep 17 00:00:00 2001 From: Vincenzo Palazzo Date: Wed, 23 Mar 2022 08:13:10 +0100 Subject: [PATCH] checker: check if generic values have the same types (#13718) --- vlib/v/ast/types.v | 19 +++++++++++ vlib/v/checker/check_types.v | 34 ++++++++++++++++++- vlib/v/checker/fn.v | 4 --- .../tests/generic_parameter_on_method.out | 7 ++++ .../tests/generic_parameter_on_method.vv | 17 ++++++++++ 5 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 vlib/v/checker/tests/generic_parameter_on_method.out create mode 100644 vlib/v/checker/tests/generic_parameter_on_method.vv diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index 35a9a0a3e1..842c304629 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -980,6 +980,12 @@ pub fn (mytable &Table) type_to_code(t Type) string { } } +// clean type name from generics form. From Type -> Type +pub fn (t &Table) clean_generics_type_str(typ Type) string { + result := t.type_to_str(typ) + return result.all_before('<') +} + // import_aliases is a map of imported symbol aliases 'module.Type' => 'Type' pub fn (t &Table) type_to_str_using_aliases(typ Type, import_aliases map[string]string) string { sym := t.sym(typ) @@ -1222,6 +1228,19 @@ pub fn (t &Table) fn_signature_using_aliases(func &Fn, import_aliases map[string return sb.str() } +// Get the name of the complete quanlified name of the type +// without the generic parts. +pub fn (t &TypeSymbol) symbol_name_except_generic() string { + // main.Abc + mut embed_name := t.name + // remove generic part from name + // main.Abc => main.Abc + if embed_name.contains('<') { + embed_name = embed_name.all_before('<') + } + return embed_name +} + pub fn (t &TypeSymbol) embed_name() string { // main.Abc => Abc mut embed_name := t.name.split('.').last() diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 355a89d86d..74f46081ec 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -188,7 +188,39 @@ pub fn (mut c Checker) check_expected_call_arg(got ast.Type, expected_ ast.Type, return } } - return error('cannot use `${c.table.type_to_str(got.clear_flag(.variadic))}` as `${c.table.type_to_str(expected.clear_flag(.variadic))}`') + + // Check on Generics types, there are some case where we have the following case + // `&Type == &Type<>`. This is a common case we are implementing a function + // with generic parameters like `compare(bst Bst node) {}` + got_typ_sym := c.table.sym(got) + got_typ_str := c.table.type_to_str(got.clear_flag(.variadic)) + expected_typ_sym := c.table.sym(expected_) + expected_typ_str := c.table.type_to_str(expected.clear_flag(.variadic)) + + if got_typ_sym.symbol_name_except_generic() == expected_typ_sym.symbol_name_except_generic() { + // Check if we are making a comparison between two different types of + // the same type like `Type and &Type<>` + if (got.is_ptr() != expected.is_ptr()) || !c.check_same_module(got, expected) { + return error('cannot use `$got_typ_str` as `$expected_typ_str`') + } + return + } + return error('cannot use `$got_typ_str` as `$expected_typ_str`') +} + +// helper method to check if the type is of the same module. +// FIXME(vincenzopalazzo) This is a work around to the issue +// explained in the https://github.com/vlang/v/pull/13718#issuecomment-1074517800 +fn (c Checker) check_same_module(got ast.Type, expected ast.Type) bool { + clean_got_typ := c.table.clean_generics_type_str(got.clear_flag(.variadic)).all_before('<') + clean_expected_typ := c.table.clean_generics_type_str(expected.clear_flag(.variadic)).all_before('<') + if clean_got_typ == clean_expected_typ { + return true + // The following if confition should catch the bugs descripted in the issue + } else if clean_expected_typ.all_after('.') == clean_got_typ.all_after('.') { + return true + } + return false } pub fn (mut c Checker) check_basic(got ast.Type, expected ast.Type) bool { diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 61e3f00096..355be70ecf 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -1243,10 +1243,6 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { final_arg_sym = c.table.sym(final_arg_typ) } if exp_arg_typ.has_flag(.generic) { - if concrete_types.len == 0 { - continue - } - if exp_utyp := c.table.resolve_generic_to_concrete(exp_arg_typ, method.generic_names, concrete_types) { diff --git a/vlib/v/checker/tests/generic_parameter_on_method.out b/vlib/v/checker/tests/generic_parameter_on_method.out new file mode 100644 index 0000000000..4d82cccbdf --- /dev/null +++ b/vlib/v/checker/tests/generic_parameter_on_method.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/generic_parameter_on_method.vv:15:15: error: cannot use `&Type` as `Type<>` in argument 1 to `ContainerType.contains` + 13 | fn main() { + 14 | con := ContainerType{typ: &Type{0}} + 15 | con.contains(con.typ) + | ~~~~~~~ + 16 | println(con) + 17 | } diff --git a/vlib/v/checker/tests/generic_parameter_on_method.vv b/vlib/v/checker/tests/generic_parameter_on_method.vv new file mode 100644 index 0000000000..719dbe892e --- /dev/null +++ b/vlib/v/checker/tests/generic_parameter_on_method.vv @@ -0,0 +1,17 @@ +struct Type { + value T +} + +struct ContainerType { + typ &Type +} + +fn (instance &ContainerType) contains(typ Type) { + println(typ) +} + +fn main() { + con := ContainerType{typ: &Type{0}} + con.contains(con.typ) + println(con) +}