[tint][intrinsics] Add explicit template arguments

Re-introduce the <...> template syntax as a list of explicit template
arguments. These must be provided to match the overload.

Add the explicit-template overloads to the various intrinsic definition
files. The non-WGSL definition files probably don't need both implcit
and explicit overloads for many of these, but this keeps behaviour
consistent.

Change-Id: Ie8078db1ed578fa7c72890f9f47a9ebc8aee420e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/175244
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Ben Clayton <bclayton@google.com>
diff --git a/tools/src/tint/intrinsic/ast/ast.go b/tools/src/tint/intrinsic/ast/ast.go
index 4059fec..7d4f336 100644
--- a/tools/src/tint/intrinsic/ast/ast.go
+++ b/tools/src/tint/intrinsic/ast/ast.go
@@ -146,13 +146,14 @@
 
 // IntrinsicDecl describes a builtin or operator declaration
 type IntrinsicDecl struct {
-	Source         tok.Source
-	Kind           IntrinsicKind
-	Name           string
-	Attributes     Attributes
-	TemplateParams []TemplateParam
-	Parameters     Parameters
-	ReturnType     *TemplatedName
+	Source                 tok.Source
+	Kind                   IntrinsicKind
+	Name                   string
+	Attributes             Attributes
+	ImplicitTemplateParams []TemplateParam
+	ExplicitTemplateParams []TemplateParam
+	Parameters             Parameters
+	ReturnType             *TemplatedName
 }
 
 // Format implements the fmt.Formatter interface
@@ -169,11 +170,16 @@
 	}
 
 	fmt.Fprintf(w, "%v", i.Name)
-	if len(i.TemplateParams) > 0 {
+	if len(i.ExplicitTemplateParams) > 0 {
 		fmt.Fprintf(w, "<")
-		formatList(w, i.TemplateParams)
+		formatList(w, i.ExplicitTemplateParams)
 		fmt.Fprintf(w, ">")
 	}
+	if len(i.ImplicitTemplateParams) > 0 {
+		fmt.Fprintf(w, "[")
+		formatList(w, i.ImplicitTemplateParams)
+		fmt.Fprintf(w, "]")
+	}
 	i.Parameters.Format(w, verb)
 	if i.ReturnType != nil {
 		fmt.Fprintf(w, " -> ")
@@ -379,7 +385,6 @@
 	}
 }
 
-// formatList writes the comma separated list to w.
 func formatList[T any](w fmt.State, list []T) {
 	for i, v := range list {
 		if i > 0 {
diff --git a/tools/src/tint/intrinsic/gen/gen.go b/tools/src/tint/intrinsic/gen/gen.go
index 0037252..79ff6c9 100644
--- a/tools/src/tint/intrinsic/gen/gen.go
+++ b/tools/src/tint/intrinsic/gen/gen.go
@@ -96,9 +96,12 @@
 type Overload struct {
 	// Total number of parameters for the overload
 	NumParameters int
-	// Total number of templates for the overload
+	// Total number of explicit templates for the overload
+	NumExplicitTemplates int
+	// Total number of explicit and implicit templates for the overload
 	NumTemplates int
 	// Index to the first template in IntrinsicTable.Templates
+	// This is a list of template starting with the explicit templates, then the implicit templates.
 	TemplatesOffset int
 	// Index to the first parameter in IntrinsicTable.Parameters
 	ParametersOffset int
@@ -163,8 +166,10 @@
 	// Map of TemplateParam to templatesBuilder
 	templateBuilders map[sem.TemplateParam]*templateBuilder
 	// Templates used by the overload
+	// This is a list of explicit template types followed by the implicit template types.
 	templates []Template
 	// Index to the first template in IntrinsicTable.Templates
+	// This is a list of template starting with the explicit templates, then the implicit templates.
 	templateOffset *int
 	// Builders for all parameters
 	parameterBuilders []parameterBuilder
@@ -217,11 +222,11 @@
 // - b.constEvalFunctionOffset
 func (b *overloadBuilder) processStage0() error {
 	// Calculate the template matcher indices
-	for _, t := range b.overload.Templates {
+	for _, t := range b.overload.AllTemplates() {
 		b.templateBuilders[t] = &templateBuilder{matcherIndex: len(b.templateBuilders)}
 	}
 
-	for _, t := range b.overload.Templates {
+	for _, t := range b.overload.AllTemplates() {
 		switch t := t.(type) {
 		case *sem.TemplateTypeParam:
 			if t.Type != nil {
@@ -279,7 +284,7 @@
 // - b.parametersOffset
 func (b *overloadBuilder) processStage1() error {
 	b.templates = []Template{}
-	for _, t := range b.overload.Templates {
+	for _, t := range b.overload.AllTemplates() {
 		b.templates = append(b.templates, Template{
 			Name:                 t.GetName(),
 			Kind:                 t.TemplateKind(),
@@ -302,7 +307,8 @@
 func (b *overloadBuilder) build() (Overload, error) {
 	return Overload{
 		NumParameters:              len(b.parameterBuilders),
-		NumTemplates:               len(b.overload.Templates),
+		NumExplicitTemplates:       len(b.overload.ExplicitTemplates),
+		NumTemplates:               len(b.overload.ExplicitTemplates) + len(b.overload.ImplicitTemplates),
 		TemplatesOffset:            loadOrMinusOne(b.templateOffset),
 		ParametersOffset:           loadOrMinusOne(b.parametersOffset),
 		ConstEvalFunctionOffset:    loadOrMinusOne(b.constEvalFunctionOffset),
diff --git a/tools/src/tint/intrinsic/gen/permutate.go b/tools/src/tint/intrinsic/gen/permutate.go
index 7d35a19..4a79968 100644
--- a/tools/src/tint/intrinsic/gen/permutate.go
+++ b/tools/src/tint/intrinsic/gen/permutate.go
@@ -63,9 +63,10 @@
 
 // Permutation describes a single permutation of an overload
 type Permutation struct {
-	sem.Overload        // The permuted overload signature
-	Desc         string // Description of the overload
-	Hash         string // Hash of the overload
+	sem.Overload         // The permuted overload signature
+	ExplicitTemplateArgs []sem.FullyQualifiedName
+	Desc                 string // Description of the overload
+	Hash                 string // Hash of the overload
 }
 
 // Permute generates a set of permutations for the given intrinsic overload
@@ -114,14 +115,25 @@
 			o.ReturnType = &retTys[0]
 		}
 
+		explicitTemplateArgs := make([]sem.FullyQualifiedName, len(overload.ExplicitTemplates))
+		for i, t := range overload.ExplicitTemplates {
+			ty := state.templateTypes[t]
+			explicitTemplateArgs[i] = ty
+			o.ExplicitTemplates = append(o.ExplicitTemplates, &sem.TemplateTypeParam{
+				Name: t.GetName(),
+				Type: &ty,
+			})
+		}
+
 		desc := fmt.Sprint(o)
 		hash := sha256.Sum256([]byte(desc))
 		const hashLength = 6
 		shortHash := hex.EncodeToString(hash[:])[:hashLength]
 		out = append(out, Permutation{
-			Overload: o,
-			Desc:     desc,
-			Hash:     shortHash,
+			Overload:             o,
+			ExplicitTemplateArgs: explicitTemplateArgs,
+			Desc:                 desc,
+			Hash:                 shortHash,
 		})
 
 		// Check for hash collisions
@@ -157,7 +169,7 @@
 		}
 	}
 
-	for _, t := range overload.Templates {
+	for _, t := range overload.AllTemplates() {
 		t := t          // Capture iterator values for anonymous function
 		next := permute // Permutation chaining
 		switch t := t.(type) {
diff --git a/tools/src/tint/intrinsic/parser/parser.go b/tools/src/tint/intrinsic/parser/parser.go
index bd2f055..fb06b75 100644
--- a/tools/src/tint/intrinsic/parser/parser.go
+++ b/tools/src/tint/intrinsic/parser/parser.go
@@ -228,7 +228,7 @@
 		Attributes: decos,
 		Name:       string(name.Runes),
 	}
-	f.TemplateParams = p.intrinsicTemplateParams()
+	f.ExplicitTemplateParams, f.ImplicitTemplateParams = p.intrinsicTemplateParams()
 	f.Parameters = p.parameters()
 	if p.match(tok.Arrow) != nil {
 		ret := p.templatedName()
@@ -246,7 +246,7 @@
 		Attributes: decos,
 		Name:       string(name.Runes),
 	}
-	f.TemplateParams = p.intrinsicTemplateParams()
+	f.ExplicitTemplateParams, f.ImplicitTemplateParams = p.intrinsicTemplateParams()
 	f.Parameters = p.parameters()
 	if p.match(tok.Arrow) != nil {
 		ret := p.templatedName()
@@ -264,7 +264,7 @@
 		Attributes: decos,
 		Name:       string(name.Runes),
 	}
-	f.TemplateParams = p.intrinsicTemplateParams()
+	f.ExplicitTemplateParams, f.ImplicitTemplateParams = p.intrinsicTemplateParams()
 	f.Parameters = p.parameters()
 	if p.match(tok.Arrow) != nil {
 		ret := p.templatedName()
@@ -282,7 +282,7 @@
 		Attributes: decos,
 		Name:       string(name.Runes),
 	}
-	f.TemplateParams = p.intrinsicTemplateParams()
+	f.ExplicitTemplateParams, f.ImplicitTemplateParams = p.intrinsicTemplateParams()
 	f.Parameters = p.parameters()
 	if p.match(tok.Arrow) != nil {
 		ret := p.templatedName()
@@ -371,16 +371,20 @@
 	return t
 }
 
-func (p *parser) intrinsicTemplateParams() []ast.TemplateParam {
-	if p.match(tok.Lbracket) == nil {
-		return nil
+func (p *parser) intrinsicTemplateParams() (explicit, implicit []ast.TemplateParam) {
+	if p.match(tok.Lt) != nil { // <...>
+		for p.err == nil && p.peekIs(0, tok.Identifier) {
+			explicit = append(explicit, p.templateParam())
+		}
+		p.expect(tok.Gt, "explicit template parameter list")
 	}
-	out := []ast.TemplateParam{}
-	for p.err == nil && p.peekIs(0, tok.Identifier) {
-		out = append(out, p.templateParam())
+	if p.match(tok.Lbracket) != nil { // [...]
+		for p.err == nil && p.peekIs(0, tok.Identifier) {
+			implicit = append(implicit, p.templateParam())
+		}
+		p.expect(tok.Rbracket, "implicit template parameter list")
 	}
-	p.expect(tok.Rbracket, "intrinsic template parameter list")
-	return out
+	return explicit, implicit
 }
 
 func (p *parser) templateParam() ast.TemplateParam {
diff --git a/tools/src/tint/intrinsic/parser/parser_test.go b/tools/src/tint/intrinsic/parser/parser_test.go
index 9dec76b..21b4e92 100644
--- a/tools/src/tint/intrinsic/parser/parser_test.go
+++ b/tools/src/tint/intrinsic/parser/parser_test.go
@@ -251,12 +251,12 @@
 			},
 		}, { ///////////////////////////////////////////////////////////////////
 			fileutils.ThisLine(),
-			"fn F[A : B<C> ]()",
+			"fn F<A : B<C> >()",
 			ast.AST{
 				Builtins: []ast.IntrinsicDecl{{
 					Kind: ast.Builtin,
 					Name: "F",
-					TemplateParams: []ast.TemplateParam{
+					ExplicitTemplateParams: []ast.TemplateParam{
 						{
 							Name: "A", Type: ast.TemplatedName{
 								Name: "B",
@@ -271,19 +271,102 @@
 			},
 		}, { ///////////////////////////////////////////////////////////////////
 			fileutils.ThisLine(),
+			"fn F<T>(a: X, b: Y<T>)",
+			ast.AST{
+				Builtins: []ast.IntrinsicDecl{{
+					Kind: ast.Builtin,
+					Name: "F",
+					ExplicitTemplateParams: []ast.TemplateParam{
+						{Name: "T"},
+					},
+					Parameters: ast.Parameters{
+						{Name: "a", Type: ast.TemplatedName{Name: "X"}},
+						{Name: "b", Type: ast.TemplatedName{
+							Name:         "Y",
+							TemplateArgs: ast.TemplatedNames{{Name: "T"}},
+						}},
+					},
+				}},
+			},
+		}, { ///////////////////////////////////////////////////////////////////
+			fileutils.ThisLine(),
+			"fn F[A : B<C>]()",
+			ast.AST{
+				Builtins: []ast.IntrinsicDecl{{
+					Kind: ast.Builtin,
+					Name: "F",
+					ImplicitTemplateParams: []ast.TemplateParam{
+						{
+							Name: "A",
+							Type: ast.TemplatedName{
+								Name: "B",
+								TemplateArgs: ast.TemplatedNames{
+									{Name: "C"},
+								},
+							},
+						},
+					},
+					Parameters: ast.Parameters{},
+				}},
+			},
+		}, { ///////////////////////////////////////////////////////////////////
+			fileutils.ThisLine(),
 			"fn F[T](a: X, b: Y<T>)",
 			ast.AST{
 				Builtins: []ast.IntrinsicDecl{{
 					Kind: ast.Builtin,
 					Name: "F",
-					TemplateParams: []ast.TemplateParam{
+					ImplicitTemplateParams: []ast.TemplateParam{
 						{Name: "T"},
 					},
 					Parameters: ast.Parameters{
 						{Name: "a", Type: ast.TemplatedName{Name: "X"}},
 						{Name: "b", Type: ast.TemplatedName{
 							Name:         "Y",
-							TemplateArgs: []ast.TemplatedName{{Name: "T"}},
+							TemplateArgs: ast.TemplatedNames{{Name: "T"}},
+						}},
+					},
+				}},
+			},
+		}, { ///////////////////////////////////////////////////////////////////
+			fileutils.ThisLine(),
+			"fn F<A>[B: C<D>]()",
+			ast.AST{
+				Builtins: []ast.IntrinsicDecl{{
+					Kind: ast.Builtin,
+					Name: "F",
+					ExplicitTemplateParams: []ast.TemplateParam{
+						{Name: "A"},
+					},
+					ImplicitTemplateParams: []ast.TemplateParam{
+						{
+							Name: "B",
+							Type: ast.TemplatedName{
+								Name: "C",
+								TemplateArgs: ast.TemplatedNames{
+									{Name: "D"},
+								},
+							},
+						},
+					},
+					Parameters: ast.Parameters{},
+				}},
+			},
+		}, { ///////////////////////////////////////////////////////////////////
+			fileutils.ThisLine(),
+			"fn F[T](a: X, b: Y<T>)",
+			ast.AST{
+				Builtins: []ast.IntrinsicDecl{{
+					Kind: ast.Builtin,
+					Name: "F",
+					ImplicitTemplateParams: []ast.TemplateParam{
+						{Name: "T"},
+					},
+					Parameters: ast.Parameters{
+						{Name: "a", Type: ast.TemplatedName{Name: "X"}},
+						{Name: "b", Type: ast.TemplatedName{
+							Name:         "Y",
+							TemplateArgs: ast.TemplatedNames{{Name: "T"}},
 						}},
 					},
 				}},
@@ -308,7 +391,7 @@
 					Name: "F",
 					ReturnType: &ast.TemplatedName{
 						Name:         "X",
-						TemplateArgs: []ast.TemplatedName{{Name: "T"}},
+						TemplateArgs: ast.TemplatedNames{{Name: "T"}},
 					},
 					Parameters: ast.Parameters{},
 				}},
@@ -389,12 +472,51 @@
 			},
 		}, { ///////////////////////////////////////////////////////////////////
 			fileutils.ThisLine(),
-			"op F[A : B<C> ]()",
+			"op F<A : B<C> >()",
 			ast.AST{
 				Operators: []ast.IntrinsicDecl{{
 					Kind: ast.Operator,
 					Name: "F",
-					TemplateParams: []ast.TemplateParam{
+					ExplicitTemplateParams: []ast.TemplateParam{
+						{
+							Name: "A", Type: ast.TemplatedName{
+								Name: "B",
+								TemplateArgs: ast.TemplatedNames{
+									{Name: "C"},
+								},
+							},
+						},
+					},
+					Parameters: ast.Parameters{},
+				}},
+			},
+		}, { ///////////////////////////////////////////////////////////////////
+			fileutils.ThisLine(),
+			"op F<T>(a: X, b: Y<T>)",
+			ast.AST{
+				Operators: []ast.IntrinsicDecl{{
+					Kind: ast.Operator,
+					Name: "F",
+					ExplicitTemplateParams: []ast.TemplateParam{
+						{Name: "T"},
+					},
+					Parameters: ast.Parameters{
+						{Name: "a", Type: ast.TemplatedName{Name: "X"}},
+						{Name: "b", Type: ast.TemplatedName{
+							Name:         "Y",
+							TemplateArgs: ast.TemplatedNames{{Name: "T"}},
+						}},
+					},
+				}},
+			},
+		}, { ///////////////////////////////////////////////////////////////////
+			fileutils.ThisLine(),
+			"op F[A : B<C>]()",
+			ast.AST{
+				Operators: []ast.IntrinsicDecl{{
+					Kind: ast.Operator,
+					Name: "F",
+					ImplicitTemplateParams: []ast.TemplateParam{
 						{
 							Name: "A", Type: ast.TemplatedName{
 								Name: "B",
@@ -414,20 +536,43 @@
 				Operators: []ast.IntrinsicDecl{{
 					Kind: ast.Operator,
 					Name: "F",
-					TemplateParams: []ast.TemplateParam{
+					ImplicitTemplateParams: []ast.TemplateParam{
 						{Name: "T"},
 					},
 					Parameters: ast.Parameters{
 						{Name: "a", Type: ast.TemplatedName{Name: "X"}},
 						{Name: "b", Type: ast.TemplatedName{
 							Name:         "Y",
-							TemplateArgs: []ast.TemplatedName{{Name: "T"}},
+							TemplateArgs: ast.TemplatedNames{{Name: "T"}},
 						}},
 					},
 				}},
 			},
 		}, { ///////////////////////////////////////////////////////////////////
 			fileutils.ThisLine(),
+			"op F<A : B<C> >[D]()",
+			ast.AST{
+				Operators: []ast.IntrinsicDecl{{
+					Kind: ast.Operator,
+					Name: "F",
+					ExplicitTemplateParams: []ast.TemplateParam{
+						{
+							Name: "A", Type: ast.TemplatedName{
+								Name: "B",
+								TemplateArgs: ast.TemplatedNames{
+									{Name: "C"},
+								},
+							},
+						},
+					},
+					ImplicitTemplateParams: []ast.TemplateParam{
+						{Name: "D"},
+					},
+					Parameters: ast.Parameters{},
+				}},
+			},
+		}, { ///////////////////////////////////////////////////////////////////
+			fileutils.ThisLine(),
 			"op F() -> X",
 			ast.AST{
 				Operators: []ast.IntrinsicDecl{{
@@ -446,7 +591,7 @@
 					Name: "F",
 					ReturnType: &ast.TemplatedName{
 						Name:         "X",
-						TemplateArgs: []ast.TemplatedName{{Name: "T"}},
+						TemplateArgs: ast.TemplatedNames{{Name: "T"}},
 					},
 					Parameters: ast.Parameters{},
 				}},
@@ -513,12 +658,12 @@
 			},
 		}, { ///////////////////////////////////////////////////////////////////
 			fileutils.ThisLine(),
-			"ctor F[A : B<C> ]()",
+			"ctor F<A : B<C> >()",
 			ast.AST{
 				Constructors: []ast.IntrinsicDecl{{
 					Kind: ast.Constructor,
 					Name: "F",
-					TemplateParams: []ast.TemplateParam{
+					ExplicitTemplateParams: []ast.TemplateParam{
 						{
 							Name: "A", Type: ast.TemplatedName{
 								Name: "B",
@@ -533,19 +678,19 @@
 			},
 		}, { ///////////////////////////////////////////////////////////////////
 			fileutils.ThisLine(),
-			"ctor F[T](a: X, b: Y<T>)",
+			"ctor F<T>(a: X, b: Y<T>)",
 			ast.AST{
 				Constructors: []ast.IntrinsicDecl{{
 					Kind: ast.Constructor,
 					Name: "F",
-					TemplateParams: []ast.TemplateParam{
+					ExplicitTemplateParams: []ast.TemplateParam{
 						{Name: "T"},
 					},
 					Parameters: ast.Parameters{
 						{Name: "a", Type: ast.TemplatedName{Name: "X"}},
 						{Name: "b", Type: ast.TemplatedName{
 							Name:         "Y",
-							TemplateArgs: []ast.TemplatedName{{Name: "T"}},
+							TemplateArgs: ast.TemplatedNames{{Name: "T"}},
 						}},
 					},
 				}},
@@ -570,7 +715,7 @@
 					Name: "F",
 					ReturnType: &ast.TemplatedName{
 						Name:         "X",
-						TemplateArgs: []ast.TemplatedName{{Name: "T"}},
+						TemplateArgs: ast.TemplatedNames{{Name: "T"}},
 					},
 					Parameters: ast.Parameters{},
 				}},
@@ -637,12 +782,12 @@
 			},
 		}, { ///////////////////////////////////////////////////////////////////
 			fileutils.ThisLine(),
-			"conv F[A : B<C> ]()",
+			"conv F<A : B<C> >()",
 			ast.AST{
 				Converters: []ast.IntrinsicDecl{{
 					Kind: ast.Converter,
 					Name: "F",
-					TemplateParams: []ast.TemplateParam{
+					ExplicitTemplateParams: []ast.TemplateParam{
 						{
 							Name: "A", Type: ast.TemplatedName{
 								Name: "B",
@@ -657,19 +802,19 @@
 			},
 		}, { ///////////////////////////////////////////////////////////////////
 			fileutils.ThisLine(),
-			"conv F[T](a: X, b: Y<T>)",
+			"conv F<T>(a: X, b: Y<T>)",
 			ast.AST{
 				Converters: []ast.IntrinsicDecl{{
 					Kind: ast.Converter,
 					Name: "F",
-					TemplateParams: []ast.TemplateParam{
+					ExplicitTemplateParams: []ast.TemplateParam{
 						{Name: "T"},
 					},
 					Parameters: ast.Parameters{
 						{Name: "a", Type: ast.TemplatedName{Name: "X"}},
 						{Name: "b", Type: ast.TemplatedName{
 							Name:         "Y",
-							TemplateArgs: []ast.TemplatedName{{Name: "T"}},
+							TemplateArgs: ast.TemplatedNames{{Name: "T"}},
 						}},
 					},
 				}},
@@ -694,7 +839,7 @@
 					Name: "F",
 					ReturnType: &ast.TemplatedName{
 						Name:         "X",
-						TemplateArgs: []ast.TemplatedName{{Name: "T"}},
+						TemplateArgs: ast.TemplatedNames{{Name: "T"}},
 					},
 					Parameters: ast.Parameters{},
 				}},
diff --git a/tools/src/tint/intrinsic/resolver/resolve.go b/tools/src/tint/intrinsic/resolver/resolve.go
index 63e14df..abd5e44 100644
--- a/tools/src/tint/intrinsic/resolver/resolve.go
+++ b/tools/src/tint/intrinsic/resolver/resolve.go
@@ -328,11 +328,18 @@
 	}
 
 	// Resolve the declared template parameters
-	templates, err := r.templateParams(&s, a.TemplateParams)
+	// Explicit template types can use implicit templates, so resolve implicit first
+	implicitTemplates, err := r.templateParams(&s, a.ImplicitTemplateParams)
 	if err != nil {
 		return err
 	}
-	overload.Templates = templates
+	overload.ImplicitTemplates = implicitTemplates
+
+	explicitTemplates, err := r.templateParams(&s, a.ExplicitTemplateParams)
+	if err != nil {
+		return err
+	}
+	overload.ExplicitTemplates = explicitTemplates
 
 	// Process overload attributes
 	if stageDeco := a.Attributes.Take("stage"); stageDeco != nil {
@@ -401,8 +408,12 @@
 	// Append the overload to the intrinsic
 	intrinsic.Overloads = append(intrinsic.Overloads, overload)
 
+	for _, num := range overload.ExplicitTemplates.Numbers() {
+		return fmt.Errorf("%v explicit number template parameters are not supported", num.AST().Source)
+	}
+
 	// Update high-water mark of templates
-	if n := len(overload.Templates); r.s.MaxTemplates < n {
+	if n := len(overload.AllTemplates()); r.s.MaxTemplates < n {
 		r.s.MaxTemplates = n
 	}
 
@@ -485,7 +496,9 @@
 		if err != nil {
 			return nil, err
 		}
-		s.declare(param, ast.Source)
+		if err := s.declare(param, ast.Source); err != nil {
+			return nil, err
+		}
 		out = append(out, param)
 	}
 	return out, nil
diff --git a/tools/src/tint/intrinsic/resolver/resolver_test.go b/tools/src/tint/intrinsic/resolver/resolver_test.go
index c73fd4b..b5d3cb8 100644
--- a/tools/src/tint/intrinsic/resolver/resolver_test.go
+++ b/tools/src/tint/intrinsic/resolver/resolver_test.go
@@ -78,6 +78,11 @@
 		}, {
 			`
 type f32
+fn f<T>(T) -> f32`,
+			success,
+		}, {
+			`
+type f32
 fn f[N: num]()`,
 			success,
 		}, {
@@ -93,6 +98,16 @@
 		}, {
 			`
 type f32
+fn f<T: f32>[N: num]()`,
+			success,
+		}, {
+			`
+type f32
+fn f[T: f32](T: f32) -> f32`,
+			success,
+		}, {
+			`
+type f32
 type P<T>
 match m: f32
 fn f[T: m](P<T>) -> T`,
@@ -108,13 +123,6 @@
 enum e { a b }
 type T<E: e>
 match m: e.a
-fn f[E: m](T<E>)`,
-			success,
-		}, {
-			`
-enum e { a b }
-type T<E: e>
-match m: e.a
 fn f(T<m>)`,
 			success,
 		}, {
@@ -158,6 +166,15 @@
 			success,
 		}, {
 			`
+type a
+type b
+type c
+match S: a | b | c
+type V<N: num, T>
+fn f<I: V<N, T> >[N: num, T: S, U: S](V<N, U>) -> I`,
+			success,
+		}, {
+			`
 type f32
 op -(f32)`,
 			success,
@@ -540,6 +557,28 @@
 			`
 @must_use fn f()`,
 			`file.txt:1:2 @must_use can only be used on a function with a return type`,
+		}, {
+			`
+type f32
+fn f<N: num>()`,
+			`file.txt:2:6 explicit number template parameters are not supported`,
+		}, {
+			`
+enum e { a b c }
+fn f<N: e>()`,
+			`file.txt:2:6 explicit number template parameters are not supported`,
+		}, {
+			`
+enum e { a b }
+type T<E: e>
+match m: e.a
+fn f<E: m>(T<E>)`,
+			`file.txt:4:6 explicit number template parameters are not supported`,
+		}, {
+			`
+fn f<T>[T]()`,
+			`file.txt:1:6 'T' already declared
+First declared here: file.txt:1:9`,
 		},
 	} {
 
diff --git a/tools/src/tint/intrinsic/sem/sem.go b/tools/src/tint/intrinsic/sem/sem.go
index 4b9d8f3..e3ee74c 100644
--- a/tools/src/tint/intrinsic/sem/sem.go
+++ b/tools/src/tint/intrinsic/sem/sem.go
@@ -258,7 +258,8 @@
 type Overload struct {
 	Decl              ast.IntrinsicDecl
 	Intrinsic         *Intrinsic
-	Templates         IntrinsicTemplates
+	ExplicitTemplates IntrinsicTemplates
+	ImplicitTemplates IntrinsicTemplates
 	ReturnType        *FullyQualifiedName
 	Parameters        []Parameter
 	CanBeUsedInStage  StageUses
@@ -267,6 +268,11 @@
 	ConstEvalFunction string // Name of the function used to evaluate the intrinsic at shader creation time
 }
 
+// AllTemplates returns the combined list of explicit and implicit templates
+func (o *Overload) AllTemplates() IntrinsicTemplates {
+	return append(append([]TemplateParam{}, o.ExplicitTemplates...), o.ImplicitTemplates...)
+}
+
 // StageUses describes the stages an overload can be used in
 type StageUses struct {
 	Vertex   bool
@@ -298,11 +304,16 @@
 		fmt.Fprintf(w, "op ")
 	}
 	fmt.Fprintf(w, "%v", o.Intrinsic.Name)
-	if len(o.Templates) > 0 {
+	if len(o.ExplicitTemplates) > 0 {
 		fmt.Fprintf(w, "<")
-		formatList(w, o.Templates)
+		formatList(w, o.ExplicitTemplates)
 		fmt.Fprintf(w, ">")
 	}
+	if len(o.ImplicitTemplates) > 0 {
+		fmt.Fprintf(w, "[")
+		formatList(w, o.ImplicitTemplates)
+		fmt.Fprintf(w, "]")
+	}
 	fmt.Fprint(w, "(")
 	for i, p := range o.Parameters {
 		if i > 0 {
@@ -407,7 +418,6 @@
 // GetName returns the name of the TemplateNumberParam
 func (t *TemplateNumberParam) GetName() string { return t.Name }
 
-// formatList writes the comma separated list to w.
 func formatList[T any](w fmt.State, list []T) {
 	for i, v := range list {
 		if i > 0 {