From: Rico Tzschichholz Date: Thu, 13 Apr 2023 15:19:04 +0000 (+0200) Subject: WIP vala: Support generics constraints X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fdea57c2e095b40301bda626cb441d811c38c393;p=thirdparty%2Fvala.git WIP vala: Support generics constraints --- diff --git a/tests/Makefile.am b/tests/Makefile.am index 32fa57abe..207cdb6e5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -810,6 +810,7 @@ TESTS = \ generics/arrays-not-supported-2.test \ generics/arrays-not-supported-3.test \ generics/class-property-override.vala \ + generics/constraints.vala \ generics/constructor-chain-up.vala \ generics/delegate-return-type-missing.test \ generics/floating-type-cast.vala \ @@ -1020,6 +1021,7 @@ TESTS = \ parser/foreach.vala \ parser/foreach-no-type.test \ parser/function-syntax-error.test \ + parser/generics-constraints.vala \ parser/initializer-list-incomplete.test \ parser/inner-array-size.test \ parser/invalid-brace.test \ diff --git a/tests/generics/constraints.vala b/tests/generics/constraints.vala new file mode 100644 index 000000000..acc88e4ec --- /dev/null +++ b/tests/generics/constraints.vala @@ -0,0 +1,42 @@ +class Base { +} + +[GenericAccessors] +interface IFoo where G : Base { + public abstract G get (G g); +} + +class Foo : IFoo where T : Base { + public Base @get (Base g) { + return null; + } + public T @set (T t) { + return null; + } +} + +class Bar : IFoo where T : G { + public Base @get (Base g) { + return null; + } + public T @set (T t) { + return null; + } +} + +class Manam : Foo { +} + +delegate R FooFunc (R r) where R : Base; + +S bar (S s = null) where S : Base { + return null; +} + +void main () { + var foo = new Foo (); + var faz = new Bar (); + var manam = new Manam (); + FooFunc func = () => { return null; }; + bar (); +} diff --git a/tests/parser/generics-constraints.vala b/tests/parser/generics-constraints.vala new file mode 100644 index 000000000..097b19224 --- /dev/null +++ b/tests/parser/generics-constraints.vala @@ -0,0 +1,21 @@ +class Bar { +} + +interface IFoo where G : Bar { +} + +class Foo : Object, IFoo where T : Bar { +} + +struct FooStruct where T : Bar { + public T t; +} + +delegate T FooFunc (T t) where T : Bar; + +T foo (T t) where T : Bar { + return null; +} + +void main () { +} diff --git a/vala/valadatatype.vala b/vala/valadatatype.vala index 939a6a9e7..a570f9d48 100644 --- a/vala/valadatatype.vala +++ b/vala/valadatatype.vala @@ -570,7 +570,7 @@ public abstract class Vala.DataType : CodeNode { } } - return null; + return get_constrained_type (type_param); } /** @@ -699,9 +699,22 @@ public abstract class Vala.DataType : CodeNode { } if ((!allow_none || n_type_args > 0) && n_type_args < expected_n_type_args) { - error = true; - Report.error (source_reference, "too few type arguments for `%s'", type_symbol.to_string ()); - return false; + var type_params = ((GenericSymbol) type_symbol).get_type_parameters (); + bool mitigated = true; + foreach (var t in type_params) { + var ct = get_constrained_type (t); + if (ct != null) { + Report.notice (source_reference, "`%s' requires type arguments, constraining `%s' to `%s'", type_symbol.to_string (), t.name, ct.to_qualified_string ()); + add_type_argument (ct); + } else { + mitigated = false; + } + } + if (!mitigated) { + error = true; + Report.error (source_reference, "too few type arguments for `%s'", type_symbol.to_string ()); + return false; + } } else if ((!allow_none || n_type_args > 0) && n_type_args > expected_n_type_args) { error = true; Report.error (source_reference, "too many type arguments for `%s'", type_symbol.to_string ()); @@ -716,4 +729,13 @@ public abstract class Vala.DataType : CodeNode { return true; } + + DataType? get_constrained_type (TypeParameter type_param) { + unowned DataType? type = type_param.type_constraint; + if (type != null) { + return type.copy (); + } + + return null; + } } diff --git a/vala/valamethodcall.vala b/vala/valamethodcall.vala index 5b8121297..6f3a83692 100644 --- a/vala/valamethodcall.vala +++ b/vala/valamethodcall.vala @@ -613,6 +613,12 @@ public class Vala.MethodCall : Expression, CallableExpression { type_arg = m.return_type.infer_type_argument (type_param, target_type); } + unowned DataType? ct = type_param.type_constraint; + if (ct != null) { + Report.notice (source_reference, "`%s' requires type arguments, constraining `%s' to `%s'", m.to_string (), type_param.name, ct.to_qualified_string ()); + type_arg = ct.copy (); + } + if (type_arg == null) { error = true; Report.error (ma.source_reference, "cannot infer generic type argument for type parameter `%s'", type_param.get_full_name ()); diff --git a/vala/valaparser.vala b/vala/valaparser.vala index 0f2b1004d..f97fa3811 100644 --- a/vala/valaparser.vala +++ b/vala/valaparser.vala @@ -310,6 +310,7 @@ public class Vala.Parser : CodeVisitor { case TokenType.VOID: case TokenType.VOLATILE: case TokenType.WEAK: + case TokenType.WHERE: case TokenType.WHILE: case TokenType.WITH: case TokenType.YIELD: @@ -2775,6 +2776,29 @@ public class Vala.Parser : CodeVisitor { break; } break; + case TokenType.WHERE: + next (); + if (accept (TokenType.OPEN_PARENS)) { + rollback (begin); + parse_method_declaration (parent, attrs); + return; + } else { + rollback (begin); + switch (last_keyword) { + case TokenType.CLASS: + parse_class_declaration (parent, attrs); + return; + case TokenType.INTERFACE: + parse_interface_declaration (parent, attrs); + return; + case TokenType.STRUCT: + parse_struct_declaration (parent, attrs); + return; + default: + break; + } + } + break; case TokenType.OPEN_PARENS: rollback (begin); if (!(parent is TypeSymbol)) { @@ -2900,6 +2924,7 @@ public class Vala.Parser : CodeVisitor { case TokenType.STRUCT: case TokenType.VIRTUAL: case TokenType.VOLATILE: + case TokenType.WHERE: return RecoveryState.DECLARATION_BEGIN; case TokenType.BREAK: case TokenType.CONTINUE: @@ -2991,6 +3016,7 @@ public class Vala.Parser : CodeVisitor { base_types.add (parse_type (true, false)); } while (accept (TokenType.COMMA)); } + parse_type_parameter_constraints (type_param_list); var cl = new Class (sym.name, get_src (begin), comment); cl.access = access; @@ -3300,6 +3326,7 @@ public class Vala.Parser : CodeVisitor { method.add_postcondition (parse_expression ()); expect (TokenType.CLOSE_PARENS); } + parse_type_parameter_constraints (type_param_list); if (!accept (TokenType.SEMICOLON)) { method.body = parse_block (); method.external = false; @@ -3502,6 +3529,8 @@ public class Vala.Parser : CodeVisitor { if (accept (TokenType.COLON)) { base_type = parse_type (true, false); } + parse_type_parameter_constraints (type_param_list); + var st = new Struct (sym.name, get_src (begin), comment); st.access = access; if (ModifierFlags.EXTERN in flags) { @@ -3545,6 +3574,8 @@ public class Vala.Parser : CodeVisitor { base_types.add (type); } while (accept (TokenType.COMMA)); } + parse_type_parameter_constraints (type_param_list); + var iface = new Interface (sym.name, get_src (begin), comment); iface.access = access; if (ModifierFlags.EXTERN in flags) { @@ -3956,6 +3987,7 @@ public class Vala.Parser : CodeVisitor { d.add_error_type (parse_type (true, false)); } while (accept (TokenType.COMMA)); } + parse_type_parameter_constraints (type_param_list); expect (TokenType.SEMICOLON); Symbol result = d; @@ -3990,6 +4022,36 @@ public class Vala.Parser : CodeVisitor { } } + void parse_type_parameter_constraints (List type_params) throws ParseError { + while (accept (TokenType.WHERE)) { + var begin = get_location (); + string id = parse_identifier (); + TypeParameter? type_param = null; + foreach (var t in type_params) { + if (t.name == id) { + type_param = t; + break; + } + } + if (type_param == null) { + Report.error (get_src (begin), "Unknown type parameter `%s'", id); + type_param = new TypeParameter (id, get_src (begin)); + type_param.error = true; + } + expect (TokenType.COLON); + do { + /* + where T : + where T : + where T : U + */ + begin = get_location (); + var type = parse_type (true, false); + type_param.type_constraint = type; + } while (accept (TokenType.COMMA)); + } + } + void skip_type_argument_list () throws ParseError { if (accept (TokenType.OP_LT)) { do { diff --git a/vala/valascanner.vala b/vala/valascanner.vala index f96066a2b..88f4f96d7 100644 --- a/vala/valascanner.vala +++ b/vala/valascanner.vala @@ -443,7 +443,14 @@ public class Vala.Scanner { if (matches (begin, "using")) return TokenType.USING; break; case 'w': - if (matches (begin, "while")) return TokenType.WHILE; + switch (begin[2]) { + case 'e': + if (matches (begin, "where")) return TokenType.WHERE; + break; + case 'i': + if (matches (begin, "while")) return TokenType.WHILE; + break; + } break; case 'y': if (matches (begin, "yield")) return TokenType.YIELD; diff --git a/vala/valasymbolresolver.vala b/vala/valasymbolresolver.vala index 40c7c98e9..887fddbd2 100644 --- a/vala/valasymbolresolver.vala +++ b/vala/valasymbolresolver.vala @@ -250,6 +250,13 @@ public class Vala.SymbolResolver : CodeVisitor { d.accept_children (this); } + public override void visit_type_parameter (TypeParameter p) { + if (p.checked) { + return; + } + p.accept_children (this); + } + public override void visit_block (Block b) { if (b.checked) { return; diff --git a/vala/valatokentype.vala b/vala/valatokentype.vala index 6097beaff..e6dd74786 100644 --- a/vala/valatokentype.vala +++ b/vala/valatokentype.vala @@ -154,6 +154,7 @@ public enum Vala.TokenType { VOID, VOLATILE, WEAK, + WHERE, WHILE, WITH, YIELD; @@ -290,6 +291,7 @@ public enum Vala.TokenType { case VOID: return "`void'"; case VOLATILE: return "`volatile'"; case WEAK: return "`weak'"; + case WHERE: return "`where'"; case WHILE: return "`while'"; case WITH: return "`with'"; case YIELD: return "`yield'"; diff --git a/vala/valatypeparameter.vala b/vala/valatypeparameter.vala index 4d4747596..8061d88be 100644 --- a/vala/valatypeparameter.vala +++ b/vala/valatypeparameter.vala @@ -26,6 +26,23 @@ using GLib; * Represents a generic type parameter in the source code. */ public class Vala.TypeParameter : TypeSymbol { + public DataType? type_constraint { + get { + if (_type_constraint is GenericType) { + return ((GenericType) _type_constraint).type_parameter.type_constraint; + } + return _type_constraint; + } + set { + _type_constraint = value; + if (_type_constraint != null) { + _type_constraint.parent_node = this; + } + } + } + + DataType? _type_constraint; + /** * Creates a new generic type parameter. * @@ -42,6 +59,18 @@ public class Vala.TypeParameter : TypeSymbol { visitor.visit_type_parameter (this); } + public override void accept_children (CodeVisitor visitor) { + if (_type_constraint != null) { + _type_constraint.accept (visitor); + } + } + + public override void replace_type (DataType old_type, DataType new_type) { + if (_type_constraint == old_type) { + type_constraint = new_type; + } + } + /** * Checks two type parameters for equality. *