[vscode] Support for >2 type constraints

The previous implementation failed for this case because we attempted to
use repeated capture groups. When a capture group is repeated, like it
was in the now-removed `comma_separated` helper, only the last match is
retained. Thus, the regex template `(?:${capt_1})(?:,\\s*${capt_2})*`
will fail for a case like `:A, B, C`. This is because `A` will always
match `capt_1`, but both `B` and `C` will be caught by the repeated
`capt_2`, with only the last match being retained. The upshot is that
for cases with >2 matches, only the first and last will have the proper
names applied via tmLanguage's "captures" feature, leaving the middle
instance unhighlighted.

This change uses a different strategy: `type-constraints` are now their
own repository definition, always checked as the last possible
constituent pattern on a `type-constructor`. This allows for repeated
highlighting, allowing constraints lists to be of arbitrary length.

Change-Id: I41b16b8cd1414fcce4e02c66f1e2bf08f6451dd5
Reviewed-on: https://fuchsia-review.googlesource.com/c/fidl-misc/+/662244
Reviewed-by: Mitchell Kember <mkember@google.com>
diff --git a/vscode-language-fidl/syntaxes/fidl.tmLanguage.json b/vscode-language-fidl/syntaxes/fidl.tmLanguage.json
index f639f2a..b709d65 100644
--- a/vscode-language-fidl/syntaxes/fidl.tmLanguage.json
+++ b/vscode-language-fidl/syntaxes/fidl.tmLanguage.json
@@ -62,8 +62,8 @@
         },
         {
             "name": "meta.type-alias.fidl",
-            "match": "\\b(alias)\\b\\s*(\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)\\s*(=)\\s*(\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)\\s*(?::\\s*(?:((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*))|(<)\\s*(?:((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)))(?:,\\s*((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)))*\\s*(>)))?",
-            "captures": {
+            "begin": "\\b(alias)\\b\\s*(\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)\\s*(=)\\s*(\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)",
+            "beginCaptures": {
                 "1": {
                     "name": "keyword.control"
                 },
@@ -75,50 +75,19 @@
                 },
                 "4": {
                     "name": "entity.name.type"
-                },
-                "5": {
-                    "name": "storage.type.constraint"
-                },
-                "6": {
-                    "name": "constant.numeric"
-                },
-                "7": {
-                    "name": "constant.language"
-                },
-                "8": {
-                    "name": "string.quoted.double"
-                },
-                "9": {
-                    "name": "punctuation.bracket.angle"
-                },
-                "10": {
-                    "name": "storage.type.constraint"
-                },
-                "11": {
-                    "name": "constant.numeric"
-                },
-                "12": {
-                    "name": "constant.language"
-                },
-                "13": {
-                    "name": "string.quoted.double"
-                },
-                "14": {
-                    "name": "storage.type.constraint"
-                },
-                "15": {
-                    "name": "constant.numeric"
-                },
-                "16": {
-                    "name": "constant.language"
-                },
-                "17": {
-                    "name": "string.quoted.double"
-                },
-                "18": {
-                    "name": "punctuation.bracket.angle"
                 }
-            }
+            },
+            "end": "(;)",
+            "endCaptures": {
+                "1": {
+                    "name": "punctuation.terminator"
+                }
+            },
+            "patterns": [
+                {
+                    "include": "#type-constructor"
+                }
+            ]
         },
         {
             "name": "meta.const.fidl",
@@ -419,6 +388,9 @@
                 },
                 {
                     "include": "#type-constructor-reference"
+                },
+                {
+                    "include": "#type-constraints"
                 }
             ]
         },
@@ -453,51 +425,8 @@
                             "name": "entity.name.type"
                         }
                     },
-                    "end": "}\\s*(?::\\s*(?:((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*))|(<)\\s*(?:((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)))(?:,\\s*((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)))*\\s*(>)))?",
-                    "endCaptures": {
-                        "1": {
-                            "name": "storage.type.constraint"
-                        },
-                        "2": {
-                            "name": "constant.numeric"
-                        },
-                        "3": {
-                            "name": "constant.language"
-                        },
-                        "4": {
-                            "name": "string.quoted.double"
-                        },
-                        "5": {
-                            "name": "punctuation.bracket.angle"
-                        },
-                        "6": {
-                            "name": "storage.type.constraint"
-                        },
-                        "7": {
-                            "name": "constant.numeric"
-                        },
-                        "8": {
-                            "name": "constant.language"
-                        },
-                        "9": {
-                            "name": "string.quoted.double"
-                        },
-                        "10": {
-                            "name": "storage.type.constraint"
-                        },
-                        "11": {
-                            "name": "constant.numeric"
-                        },
-                        "12": {
-                            "name": "constant.language"
-                        },
-                        "13": {
-                            "name": "string.quoted.double"
-                        },
-                        "14": {
-                            "name": "punctuation.bracket.angle"
-                        }
-                    },
+                    "end": "}",
+                    "endCaptures": {},
                     "patterns": [
                         {
                             "include": "#comments"
@@ -546,7 +475,7 @@
                             "name": "entity.name.type"
                         }
                     },
-                    "end": "(?:,\\s*(?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*))?\\s*>\\s*(?::\\s*(?:((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*))|(<)\\s*(?:((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)))(?:,\\s*((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)))*\\s*(>)))?",
+                    "end": "(?:,\\s*(?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*))?\\s*>",
                     "endCaptures": {
                         "1": {
                             "name": "constant.numeric"
@@ -556,48 +485,6 @@
                         },
                         "3": {
                             "name": "string.quoted.double"
-                        },
-                        "4": {
-                            "name": "storage.type.constraint"
-                        },
-                        "5": {
-                            "name": "constant.numeric"
-                        },
-                        "6": {
-                            "name": "constant.language"
-                        },
-                        "7": {
-                            "name": "string.quoted.double"
-                        },
-                        "8": {
-                            "name": "punctuation.bracket.angle"
-                        },
-                        "9": {
-                            "name": "storage.type.constraint"
-                        },
-                        "10": {
-                            "name": "constant.numeric"
-                        },
-                        "11": {
-                            "name": "constant.language"
-                        },
-                        "12": {
-                            "name": "string.quoted.double"
-                        },
-                        "13": {
-                            "name": "storage.type.constraint"
-                        },
-                        "14": {
-                            "name": "constant.numeric"
-                        },
-                        "15": {
-                            "name": "constant.language"
-                        },
-                        "16": {
-                            "name": "string.quoted.double"
-                        },
-                        "17": {
-                            "name": "punctuation.bracket.angle"
                         }
                     },
                     "patterns": [
@@ -618,7 +505,7 @@
             "patterns": [
                 {
                     "name": "meta.type-constructor-reference.fidl",
-                    "match": "(\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)\\s*(?:(<)\\s*(\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)\\s*(?:(,)\\s*(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*))?\\s*(>))?\\s*(?::\\s*(?:((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*))|(<)\\s*(?:((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)))(?:,\\s*((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)))*\\s*(>)))?",
+                    "match": "(\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)\\s*(?:(<)\\s*(\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*)\\s*(?:(,)\\s*(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*))?\\s*(>))?",
                     "captures": {
                         "1": {
                             "name": "entity.name.type"
@@ -637,48 +524,54 @@
                         },
                         "6": {
                             "name": "punctuation.bracket.angle"
-                        },
-                        "7": {
+                        }
+                    }
+                }
+            ]
+        },
+        "type-constraints": {
+            "patterns": [
+                {
+                    "include": "#type-constraints-list"
+                },
+                {
+                    "include": "#type-constraints-singleton"
+                }
+            ]
+        },
+        "type-constraints-list": {
+            "patterns": [
+                {
+                    "name": "meta.type-constraints-list.fidl",
+                    "begin": ":<",
+                    "beginCaptures": {},
+                    "end": ">",
+                    "endCaptures": {},
+                    "patterns": [
+                        {
+                            "include": "#const-value"
+                        }
+                    ]
+                }
+            ]
+        },
+        "type-constraints-singleton": {
+            "patterns": [
+                {
+                    "name": "meta.type-constraints-singleton.fidl",
+                    "match": ":\\s*((?:(?:(-?\\b(?:(?:0(?:x|X)[0-9a-fA-F]*)|(?:0(?:b|B)[01]*)|(?:(?:[0-9]+\\.?[0-9]*)|(?:\\.[0-9]+))(?:(?:e|E)(?:\\+|-)?[0-9]+)?)\\b)|(\\b(?:true|false)\\b)|(\"(?:[^\\\"]|\\.)*\"))|\\b[a-zA-Z_][0-9a-zA-Z_]*\\b(?:\\.\\b[a-zA-Z_][0-9a-zA-Z_]*\\b)*))",
+                    "captures": {
+                        "1": {
                             "name": "storage.type.constraint"
                         },
-                        "8": {
+                        "2": {
                             "name": "constant.numeric"
                         },
-                        "9": {
+                        "3": {
                             "name": "constant.language"
                         },
-                        "10": {
+                        "4": {
                             "name": "string.quoted.double"
-                        },
-                        "11": {
-                            "name": "punctuation.bracket.angle"
-                        },
-                        "12": {
-                            "name": "storage.type.constraint"
-                        },
-                        "13": {
-                            "name": "constant.numeric"
-                        },
-                        "14": {
-                            "name": "constant.language"
-                        },
-                        "15": {
-                            "name": "string.quoted.double"
-                        },
-                        "16": {
-                            "name": "storage.type.constraint"
-                        },
-                        "17": {
-                            "name": "constant.numeric"
-                        },
-                        "18": {
-                            "name": "constant.language"
-                        },
-                        "19": {
-                            "name": "string.quoted.double"
-                        },
-                        "20": {
-                            "name": "punctuation.bracket.angle"
                         }
                     }
                 }
diff --git a/vscode-language-fidl/test.test.fidl b/vscode-language-fidl/test.test.fidl
index e7ef5ce..77f26e6 100644
--- a/vscode-language-fidl/test.test.fidl
+++ b/vscode-language-fidl/test.test.fidl
@@ -25,8 +25,19 @@
 @available(deprecated=1000)
 @available(removed=1001, note="bar")
 alias Foo = foo.bar.baz;
+alias Bar = vector<string>;
+alias Baz = Bar:10;
 
-alias OptionalChannel = zx.handle:<CHANNEL, zx.rights.DUPLICATE, optional>;
+alias OptionalChannel = zx.handle:optional;
+alias OptionalChannelWithRights = zx.handle:<CHANNEL, zx.rights.DUPLICATE, optional>;
+
+type HandleConstraints = resource struct {
+    a zx.handle:VMO;
+    b zx.handle:<VMO>;
+    c zx.handle:<VMO, zx.rights.DUPLICATE | zx.rights.READ>;
+    d zx.handle:<VMO | CHANNEL, zx.rights.DUPLICATE | zx.rights.READ>;
+    e zx.handle:<VMO | CHANNEL, zx.rights.DUPLICATE | zx.rights.READ, 25, optional>;
+};
 
 // hi
 type Foo = struct {};
@@ -100,6 +111,7 @@
         f1 uint8;
     }) -> (struct {
         f2 uint8;
+        f3 string:optional = "abc";
     });
 
     Foo(struct {
diff --git a/vscode-language-fidl/tools/generate-syntax.ts b/vscode-language-fidl/tools/generate-syntax.ts
index da6c2fb..2258b38 100644
--- a/vscode-language-fidl/tools/generate-syntax.ts
+++ b/vscode-language-fidl/tools/generate-syntax.ts
@@ -178,13 +178,6 @@
   return word(one_of(...words));
 }
 
-function comma_separated(pat: Pattern): NamedPattern {
-  return new NamedPattern(`(?:${pat})(?:,\\s*${pat})*`, [
-    ..._names(pat),
-    ..._names(pat),
-  ]);
-}
-
 function zero_or_more(pat: Pattern): NamedPattern {
   return new NamedPattern(`(?:${pat}\\s*)*`, _names(pat));
 }
@@ -291,13 +284,6 @@
   )
 )));
 
-const TYPE_CONSTRAINT = named(CONSTANT, "storage.type.constraint");
-
-const TYPE_CONSTRAINTS = seq(
-  ":",
-  one_of(TYPE_CONSTRAINT, angle_brackets(comma_separated(TYPE_CONSTRAINT)))
-);
-
 // Checks
 
 const INLINE_LAYOUT_PREFIX = seq(
@@ -307,8 +293,6 @@
   "{"
 );
 
-comma_separated("[a-zA-Z]+").assert("foo, bar");
-
 COMPOUND_IDENTIFIER.assert("foo");
 COMPOUND_IDENTIFIER.assert("foo_bar");
 COMPOUND_IDENTIFIER.assert("foo.bar.baz");
@@ -331,12 +315,6 @@
 TYPE_PARAMETERS.assert("<Foo, 3>");
 TYPE_PARAMETERS.assert("<Foo>");
 
-TYPE_CONSTRAINTS.assert(":optional");
-TYPE_CONSTRAINTS.assert(":zx.rights.DUPLICATE");
-TYPE_CONSTRAINTS.assert(":MAX_SIZE");
-TYPE_CONSTRAINTS.assert(":<MAX_SIZE, optional>");
-TYPE_CONSTRAINTS.assert(":<CHANNEL, zx.rights.DUPLICATE, optional>");
-
 INLINE_LAYOUT_PREFIX.assert("resource flexible struct {");
 INLINE_LAYOUT_PREFIX.assert("strict enum : int32 {");
 
@@ -347,7 +325,6 @@
   include("attributes"),
 ];
 
-// TODO: support handle rights
 const tmLanguage: TmLanguage = {
   $schema: tmSchema,
   name: "FIDL",
@@ -366,16 +343,17 @@
     ),
 
     // Aliases: an aliased type can only be a layout reference, and cannot be re-parameterized
-    match(
-      "meta.type-alias",
-      seq(
+    block({
+      name: "meta.type-alias",
+      begin: seq(
         keyword("alias"),
         named(IDENTIFIER, "variable.alias"),
         separator("="),
         LAYOUT_REFERENCE,
-        optional(TYPE_CONSTRAINTS)
-      )
-    ),
+      ),
+      end: EOL,
+      patterns: [include("type-constructor")],
+    }),
 
     // Const declaration
     block({
@@ -490,6 +468,8 @@
         include("type-constructor-inline"),
         include("type-constructor-inline-arg"),
         include("type-constructor-reference"),
+        // this must go last, so that we attempt to match on all unconstrained constructors first
+        include("type-constraints"),
       ],
     },
     // a type constructor that contains an inline layout, e.g. `foo struct { ... };
@@ -500,10 +480,6 @@
           begin: seq(INLINE_LAYOUT_PREFIX),
           end: seq(
             "}",
-            // no user-defined layouts currently accept parameters, so don't
-            // worry about syntax highlighting for them yet; just check for
-            // constraints
-            optional(TYPE_CONSTRAINTS),
           ),
           patterns: [...WITH_COMMENTS_AND_ATTRIBUTES, include("layout-member")],
         }),
@@ -526,7 +502,6 @@
             // the rest of the layout parameters (i.e. array size)
             optional(seq(",", CONSTANT)),
             ">",
-            optional(TYPE_CONSTRAINTS),
           ),
           patterns: [...WITH_COMMENTS_AND_ATTRIBUTES, include("layout-member")],
         }),
@@ -540,7 +515,33 @@
           seq(
             LAYOUT_REFERENCE,
             optional(TYPE_PARAMETERS),
-            optional(TYPE_CONSTRAINTS),
+          )
+        ),
+      ],
+    },
+    "type-constraints": {
+      patterns: [
+        include("type-constraints-list"),
+        include("type-constraints-singleton"),
+      ],
+    },
+    "type-constraints-list": {
+      patterns: [
+        block({
+          name: "meta.type-constraints-list",
+          begin: ":<",
+          end: ">",
+          patterns: [include("const-value")],
+        }),
+      ],
+    },
+    "type-constraints-singleton": {
+      patterns: [
+        match(
+          "meta.type-constraints-singleton",
+          seq(
+            ":",
+            named(CONSTANT, "storage.type.constraint"),
           )
         ),
       ],