]> git.ipfire.org Git - thirdparty/vala.git/commitdiff
WIP vala: Support generics constraints
authorRico Tzschichholz <ricotz@ubuntu.com>
Thu, 13 Apr 2023 15:19:04 +0000 (17:19 +0200)
committerRico Tzschichholz <ricotz@ubuntu.com>
Sun, 21 Apr 2024 11:41:43 +0000 (13:41 +0200)
tests/Makefile.am
tests/generics/constraints.vala [new file with mode: 0644]
tests/parser/generics-constraints.vala [new file with mode: 0644]
vala/valadatatype.vala
vala/valamethodcall.vala
vala/valaparser.vala
vala/valascanner.vala
vala/valasymbolresolver.vala
vala/valatokentype.vala
vala/valatypeparameter.vala

index 32fa57abe08285a6699fe0ccde166317ba12d716..207cdb6e5dabce654c285807756dbcf9dcb74133 100644 (file)
@@ -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 (file)
index 0000000..acc88e4
--- /dev/null
@@ -0,0 +1,42 @@
+class Base {
+}
+
+[GenericAccessors]
+interface IFoo<G> where G : Base {
+       public abstract G get (G g);
+}
+
+class Foo<T> : IFoo where T : Base {
+       public Base @get (Base g) {
+               return null;
+       }
+       public T @set (T t) {
+               return null;
+       }
+}
+
+class Bar<T> : 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 r) where R : Base;
+
+S bar<S> (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 (file)
index 0000000..097b192
--- /dev/null
@@ -0,0 +1,21 @@
+class Bar {
+}
+
+interface IFoo<G> where G : Bar {
+}
+
+class Foo<T> : Object, IFoo<T> where T : Bar {
+}
+
+struct FooStruct<T> where T : Bar {
+       public T t;
+}
+
+delegate T FooFunc<T> (T t) where T : Bar;
+
+T foo<T> (T t) where T : Bar {
+       return null;
+}
+
+void main () {
+}
index 939a6a9e73e05a656f3fb5318f387be55d9cf200..a570f9d4803505b98d10669bdea8840b20d9cf8e 100644 (file)
@@ -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;
+       }
 }
index 5b8121297232d9d9fb623fcf07ca775720e4b79f..6f3a83692be31a0d669c6ad3903275f44dec699b 100644 (file)
@@ -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 ());
index 0f2b1004dcc9ff4b64d58b3d92fa3d3a706b61f0..f97fa381193de7a770eea30390cd47d51fed196d 100644 (file)
@@ -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<TypeParameter> 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 : <base class name>
+                               where T : <interface name>
+                               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 {
index f96066a2bafca91708c99c3a8ffa1b0371b931a8..88f4f96d7a36527dac645ca505bbffd2543037da 100644 (file)
@@ -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;
index 40c7c98e9b7ac53af56c2544e6aab54a20686914..887fddbd2f39891e779a2c4f2fe607c57e013fcd 100644 (file)
@@ -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;
index 6097beaff001571a32c7df5052531341c8068c64..e6dd74786d2cc1997313d435d79e7cd37caecbf1 100644 (file)
@@ -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'";
index 4d47475968344d51ed302d83c4fd1efd8a7958dc..8061d88be4e299d97cbc1caf503bc2ec2691ddbb 100644 (file)
@@ -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.
         *