Add cmd/intrinsic-gen resolver and sem
Part of the new intrinsic definition parser. Bug: tint:832 Change-Id: I701072def1a4ca723d10d08b44c1e271b9458212 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/52540 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: David Neto <dneto@google.com> Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
parent
646f4a9958
commit
16e86d3225
|
@ -0,0 +1,584 @@
|
||||||
|
// Copyright 2021 The Tint Authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/ast"
|
||||||
|
"dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/sem"
|
||||||
|
"dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/tok"
|
||||||
|
)
|
||||||
|
|
||||||
|
type resolver struct {
|
||||||
|
a *ast.AST
|
||||||
|
s *sem.Sem
|
||||||
|
|
||||||
|
globals scope
|
||||||
|
functions map[string]*sem.Function
|
||||||
|
enumEntryMatchers map[*sem.EnumEntry]*sem.EnumMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve processes the AST
|
||||||
|
func Resolve(a *ast.AST) (*sem.Sem, error) {
|
||||||
|
r := resolver{
|
||||||
|
a: a,
|
||||||
|
s: sem.New(),
|
||||||
|
globals: newScope(nil),
|
||||||
|
functions: map[string]*sem.Function{},
|
||||||
|
enumEntryMatchers: map[*sem.EnumEntry]*sem.EnumMatcher{},
|
||||||
|
}
|
||||||
|
// Declare and resolve all the enumerators
|
||||||
|
for _, e := range a.Enums {
|
||||||
|
if err := r.enum(e); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Declare and resolve all the ty types
|
||||||
|
for _, p := range a.Types {
|
||||||
|
if err := r.ty(p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Declare and resolve the type matchers
|
||||||
|
for _, m := range a.Matchers {
|
||||||
|
if err := r.matcher(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Declare and resolve the functions
|
||||||
|
for _, f := range a.Functions {
|
||||||
|
if err := r.function(f); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// enum() resolves an enum declaration.
|
||||||
|
// The resulting sem.Enum is appended to Sem.Enums, and the enum and all its
|
||||||
|
// entries are registered with the global scope.
|
||||||
|
func (r *resolver) enum(e ast.EnumDecl) error {
|
||||||
|
s := &sem.Enum{
|
||||||
|
Decl: e,
|
||||||
|
Name: e.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the enum
|
||||||
|
r.s.Enums = append(r.s.Enums, s)
|
||||||
|
if err := r.globals.declare(s, e.Source); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register each of the enum entries
|
||||||
|
for _, ast := range e.Entries {
|
||||||
|
entry := &sem.EnumEntry{
|
||||||
|
Name: ast,
|
||||||
|
Enum: s,
|
||||||
|
}
|
||||||
|
if err := r.globals.declare(entry, e.Source); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.Entries = append(s.Entries, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ty() resolves a type declaration.
|
||||||
|
// The resulting sem.Type is appended to Sem.Types, and the type is registered
|
||||||
|
// with the global scope.
|
||||||
|
func (r *resolver) ty(a ast.TypeDecl) error {
|
||||||
|
t := &sem.Type{
|
||||||
|
Decl: a,
|
||||||
|
Name: a.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the type
|
||||||
|
r.s.Types = append(r.s.Types, t)
|
||||||
|
if err := r.globals.declare(t, a.Source); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new scope for resolving template parameters
|
||||||
|
s := newScope(&r.globals)
|
||||||
|
|
||||||
|
// Resolve the type template parameters
|
||||||
|
templateParams, err := r.templateParams(&s, a.TemplateParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.TemplateParams = templateParams
|
||||||
|
|
||||||
|
// Scan for decorations
|
||||||
|
if d := a.Decorations.Take("display"); d != nil {
|
||||||
|
if len(d.Values) != 1 {
|
||||||
|
return fmt.Errorf("%v expected a single value for 'display' decoration", d.Source)
|
||||||
|
}
|
||||||
|
t.DisplayName = d.Values[0]
|
||||||
|
}
|
||||||
|
if len(a.Decorations) != 0 {
|
||||||
|
return fmt.Errorf("%v unknown decoration", a.Decorations[0].Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// matcher() resolves a match declaration to either a sem.TypeMatcher or
|
||||||
|
// sem.EnumMatcher.
|
||||||
|
// The resulting matcher is appended to either Sem.TypeMatchers or
|
||||||
|
// Sem.EnumMatchers, and is registered with the global scope.
|
||||||
|
func (r *resolver) matcher(a ast.MatcherDecl) error {
|
||||||
|
// Determine whether this is a type matcher or enum matcher by resolving the
|
||||||
|
// first option
|
||||||
|
firstOption, err := r.lookupNamed(&r.globals, a.Options[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve to a sem.TypeMatcher or a sem.EnumMatcher
|
||||||
|
switch firstOption := firstOption.(type) {
|
||||||
|
case *sem.Type:
|
||||||
|
options := map[sem.Named]tok.Source{}
|
||||||
|
m := &sem.TypeMatcher{
|
||||||
|
Decl: a,
|
||||||
|
Name: a.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the matcher
|
||||||
|
r.s.TypeMatchers = append(r.s.TypeMatchers, m)
|
||||||
|
if err := r.globals.declare(m, a.Source); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve each of the types in the options list
|
||||||
|
for _, ast := range m.Decl.Options {
|
||||||
|
ty, err := r.lookupType(&r.globals, ast)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Types = append(m.Types, ty)
|
||||||
|
if s, dup := options[ty]; dup {
|
||||||
|
return fmt.Errorf("%v duplicate option '%v' in matcher\nFirst declared here: %v", ast.Source, ast.Name, s)
|
||||||
|
}
|
||||||
|
options[ty] = ast.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *sem.EnumEntry:
|
||||||
|
enum := firstOption.Enum
|
||||||
|
m := &sem.EnumMatcher{
|
||||||
|
Decl: a,
|
||||||
|
Name: a.Name,
|
||||||
|
Enum: enum,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the matcher
|
||||||
|
r.s.EnumMatchers = append(r.s.EnumMatchers, m)
|
||||||
|
if err := r.globals.declare(m, a.Source); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve each of the enums in the options list
|
||||||
|
for _, ast := range m.Decl.Options {
|
||||||
|
entry := enum.FindEntry(ast.Name)
|
||||||
|
if entry == nil {
|
||||||
|
return fmt.Errorf("%v enum '%v' does not contain '%v'", ast.Source, enum.Name, ast.Name)
|
||||||
|
}
|
||||||
|
m.Options = append(m.Options, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("'%v' cannot be used for matcher", a.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// function() resolves a function overload declaration.
|
||||||
|
// The the first overload for the function creates and appends the sem.Function
|
||||||
|
// to Sem.Functions. Subsequent overloads append their resolved overload to the
|
||||||
|
// sem.Function.Overloads list.
|
||||||
|
func (r *resolver) function(a ast.FunctionDecl) error {
|
||||||
|
// If this is the first overload of the function, create and register the
|
||||||
|
// semantic function.
|
||||||
|
f := r.functions[a.Name]
|
||||||
|
if f == nil {
|
||||||
|
f = &sem.Function{Name: a.Name}
|
||||||
|
r.functions[a.Name] = f
|
||||||
|
r.s.Functions = append(r.s.Functions, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new scope for resolving template parameters
|
||||||
|
s := newScope(&r.globals)
|
||||||
|
|
||||||
|
// Resolve the declared template parameters
|
||||||
|
templateParams, err := r.templateParams(&s, a.TemplateParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the semantic overload and append it to the function
|
||||||
|
overload := &sem.Overload{
|
||||||
|
Decl: a,
|
||||||
|
Parameters: make([]sem.Parameter, len(a.Parameters)),
|
||||||
|
TemplateParams: templateParams,
|
||||||
|
}
|
||||||
|
f.Overloads = append(f.Overloads, overload)
|
||||||
|
|
||||||
|
// Sort the template parameters by resolved type. Append these to
|
||||||
|
// sem.Overload.OpenTypes or sem.Overload.OpenNumbers based on their kind.
|
||||||
|
for _, param := range templateParams {
|
||||||
|
switch param := param.(type) {
|
||||||
|
case *sem.TemplateTypeParam:
|
||||||
|
overload.OpenTypes = append(overload.OpenTypes, param)
|
||||||
|
case *sem.TemplateEnumParam, *sem.TemplateNumberParam:
|
||||||
|
overload.OpenNumbers = append(overload.OpenNumbers, param)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update high-water marks of open types / numbers
|
||||||
|
if r.s.MaxOpenTypes < len(overload.OpenTypes) {
|
||||||
|
r.s.MaxOpenTypes = len(overload.OpenTypes)
|
||||||
|
}
|
||||||
|
if r.s.MaxOpenNumbers < len(overload.OpenNumbers) {
|
||||||
|
r.s.MaxOpenNumbers = len(overload.OpenNumbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the parameters
|
||||||
|
for i, p := range a.Parameters {
|
||||||
|
usage, err := r.fullyQualifiedName(&s, p.Type)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
overload.Parameters[i] = sem.Parameter{
|
||||||
|
Name: p.Name,
|
||||||
|
Type: usage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the return type
|
||||||
|
if a.ReturnType != nil {
|
||||||
|
usage, err := r.fullyQualifiedName(&s, *a.ReturnType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch usage.Target.(type) {
|
||||||
|
case *sem.Type, *sem.TemplateTypeParam:
|
||||||
|
overload.ReturnType = &usage
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%v cannot use '%v' as return type. Must be a type or template type", a.ReturnType.Source, a.ReturnType.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fullyQualifiedName() resolves the ast.TemplatedName to a sem.FullyQualifiedName.
|
||||||
|
func (r *resolver) fullyQualifiedName(s *scope, arg ast.TemplatedName) (sem.FullyQualifiedName, error) {
|
||||||
|
target, err := r.lookupNamed(s, arg)
|
||||||
|
if err != nil {
|
||||||
|
return sem.FullyQualifiedName{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry, ok := target.(*sem.EnumEntry); ok {
|
||||||
|
// The target resolved to an enum entry.
|
||||||
|
// Automagically transform this into a synthetic matcher with a single
|
||||||
|
// option. i.e.
|
||||||
|
// This:
|
||||||
|
// enum E{ a b c }
|
||||||
|
// fn F(b)
|
||||||
|
// Becomes:
|
||||||
|
// enum E{ a b c }
|
||||||
|
// matcher b
|
||||||
|
// fn F(b)
|
||||||
|
// We don't really care right now that we have a symbol collision
|
||||||
|
// between E.b and b, as the generators return different names for
|
||||||
|
// these.
|
||||||
|
matcher, ok := r.enumEntryMatchers[entry]
|
||||||
|
if !ok {
|
||||||
|
matcher = &sem.EnumMatcher{
|
||||||
|
Name: entry.Name,
|
||||||
|
Enum: entry.Enum,
|
||||||
|
Options: []*sem.EnumEntry{entry},
|
||||||
|
}
|
||||||
|
r.enumEntryMatchers[entry] = matcher
|
||||||
|
r.s.EnumMatchers = append(r.s.EnumMatchers, matcher)
|
||||||
|
}
|
||||||
|
target = matcher
|
||||||
|
}
|
||||||
|
|
||||||
|
fqn := sem.FullyQualifiedName{
|
||||||
|
Target: target,
|
||||||
|
TemplateArguments: make([]sem.FullyQualifiedName, len(arg.TemplateArgs)),
|
||||||
|
}
|
||||||
|
for i, a := range arg.TemplateArgs {
|
||||||
|
arg, err := r.fullyQualifiedName(s, a)
|
||||||
|
if err != nil {
|
||||||
|
return sem.FullyQualifiedName{}, err
|
||||||
|
}
|
||||||
|
fqn.TemplateArguments[i] = arg
|
||||||
|
}
|
||||||
|
return fqn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// templateParams() resolves the ast.TemplateParams into list of sem.TemplateParam.
|
||||||
|
// Each sem.TemplateParam is registered with the scope s.
|
||||||
|
func (r *resolver) templateParams(s *scope, l ast.TemplateParams) ([]sem.TemplateParam, error) {
|
||||||
|
out := []sem.TemplateParam{}
|
||||||
|
for _, ast := range l {
|
||||||
|
param, err := r.templateParam(ast)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.declare(param, ast.Source)
|
||||||
|
out = append(out, param)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// templateParams() resolves the ast.TemplateParam into sem.TemplateParam, which
|
||||||
|
// is either a sem.TemplateEnumParam or a sem.TemplateTypeParam.
|
||||||
|
func (r *resolver) templateParam(a ast.TemplateParam) (sem.TemplateParam, error) {
|
||||||
|
if a.Type.Name == "num" {
|
||||||
|
return &sem.TemplateNumberParam{Name: a.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Type.Name != "" {
|
||||||
|
resolved, err := r.lookupNamed(&r.globals, a.Type)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch r := resolved.(type) {
|
||||||
|
case *sem.Enum:
|
||||||
|
return &sem.TemplateEnumParam{Name: a.Name, Enum: r}, nil
|
||||||
|
case *sem.EnumMatcher:
|
||||||
|
return &sem.TemplateEnumParam{Name: a.Name, Enum: r.Enum, Matcher: r}, nil
|
||||||
|
case *sem.TypeMatcher:
|
||||||
|
return &sem.TemplateTypeParam{Name: a.Name, Type: r}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%v invalid template parameter type '%v'", a.Source, a.Type.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sem.TemplateTypeParam{Name: a.Name}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookupType() searches the scope `s` and its ancestors for the sem.Type with
|
||||||
|
// the given name.
|
||||||
|
func (r *resolver) lookupType(s *scope, a ast.TemplatedName) (*sem.Type, error) {
|
||||||
|
resolved, err := r.lookupNamed(s, a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Something with the given name was found...
|
||||||
|
if ty, ok := resolved.(*sem.Type); ok {
|
||||||
|
return ty, nil
|
||||||
|
}
|
||||||
|
// ... but that something was not a sem.Type
|
||||||
|
return nil, fmt.Errorf("%v '%v' resolves to %v but type is expected", a.Source, a.Name, describe(resolved))
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookupNamed() searches `s` and its ancestors for the sem.Named object with
|
||||||
|
// the given name. If there are template arguments for the name `a`, then
|
||||||
|
// lookupNamed() performs basic validation that those arguments can be passed
|
||||||
|
// to the named object.
|
||||||
|
func (r *resolver) lookupNamed(s *scope, a ast.TemplatedName) (sem.Named, error) {
|
||||||
|
target := s.lookup(a.Name)
|
||||||
|
if target == nil {
|
||||||
|
return nil, fmt.Errorf("%v cannot resolve '%v'", a.Source, a.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Something with the given name was found...
|
||||||
|
var params []sem.TemplateParam
|
||||||
|
var ty sem.ResolvableType
|
||||||
|
switch target := target.object.(type) {
|
||||||
|
case *sem.Type:
|
||||||
|
ty = target
|
||||||
|
params = target.TemplateParams
|
||||||
|
case *sem.TypeMatcher:
|
||||||
|
ty = target
|
||||||
|
params = target.TemplateParams
|
||||||
|
case sem.TemplateParam:
|
||||||
|
if len(a.TemplateArgs) != 0 {
|
||||||
|
return nil, fmt.Errorf("%v '%v' template parameters do not accept template arguments", a.Source, a.Name)
|
||||||
|
}
|
||||||
|
return target.(sem.Named), nil
|
||||||
|
case sem.Named:
|
||||||
|
return target, nil
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("Unknown resolved type %T", target))
|
||||||
|
}
|
||||||
|
// ... and that something takes template parameters
|
||||||
|
// Check the number of templated name template arguments match the number of
|
||||||
|
// templated parameters for the target.
|
||||||
|
args := a.TemplateArgs
|
||||||
|
if len(params) != len(args) {
|
||||||
|
return nil, fmt.Errorf("%v '%v' requires %d template arguments, but %d were provided", a.Source, a.Name, len(params), len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check templated name template argument kinds match the parameter kinds
|
||||||
|
for i, ast := range args {
|
||||||
|
param := params[i]
|
||||||
|
arg, err := r.lookupNamed(s, args[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkCompatible(arg, param); err != nil {
|
||||||
|
return nil, fmt.Errorf("%v %w", ast.Source, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ty, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// describe() returns a string describing a sem.Named
|
||||||
|
func describe(n sem.Named) string {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *sem.Type:
|
||||||
|
return "type '" + n.Name + "'"
|
||||||
|
case *sem.TypeMatcher:
|
||||||
|
return "type matcher '" + n.Name + "'"
|
||||||
|
case *sem.Enum:
|
||||||
|
return "enum '" + n.Name + "'"
|
||||||
|
case *sem.EnumMatcher:
|
||||||
|
return "enum matcher '" + n.Name + "'"
|
||||||
|
case *sem.TemplateTypeParam:
|
||||||
|
return "template type"
|
||||||
|
case *sem.TemplateEnumParam:
|
||||||
|
return "template enum '" + n.Enum.Name + "'"
|
||||||
|
case *sem.EnumEntry:
|
||||||
|
return "enum entry '" + n.Enum.Name + "." + n.Name + "'"
|
||||||
|
case *sem.TemplateNumberParam:
|
||||||
|
return "template number"
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("unhandled type %T", n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkCompatible() returns an error if `arg` cannot be used as an argument for
|
||||||
|
// a parameter of `param`.
|
||||||
|
func checkCompatible(arg, param sem.Named) error {
|
||||||
|
// asEnum() returns the underlying sem.Enum if n is a enum matcher,
|
||||||
|
// templated enum parameter or an enum entry, otherwise nil
|
||||||
|
asEnum := func(n sem.Named) *sem.Enum {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *sem.EnumMatcher:
|
||||||
|
return n.Enum
|
||||||
|
case *sem.TemplateEnumParam:
|
||||||
|
return n.Enum
|
||||||
|
case *sem.EnumEntry:
|
||||||
|
return n.Enum
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg := asEnum(arg); arg != nil {
|
||||||
|
param := asEnum(param)
|
||||||
|
if arg == param {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyNumber := "any number"
|
||||||
|
// asNumber() returns anyNumber if n is a TemplateNumberParam.
|
||||||
|
// TODO(bclayton): Once we support number ranges [e.g.: fn F<N: 1..4>()], we
|
||||||
|
// should check number ranges are compatible
|
||||||
|
asNumber := func(n sem.Named) interface{} {
|
||||||
|
switch n.(type) {
|
||||||
|
case *sem.TemplateNumberParam:
|
||||||
|
return anyNumber
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg := asNumber(arg); arg != nil {
|
||||||
|
param := asNumber(param)
|
||||||
|
if arg == param {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyType := &sem.Type{}
|
||||||
|
// asNumber() returns the sem.Type, sem.TypeMatcher if the named object
|
||||||
|
// resolves to one of these, or anyType if n is a unconstrained template
|
||||||
|
// type parameter.
|
||||||
|
asResolvableType := func(n sem.Named) sem.ResolvableType {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *sem.TemplateTypeParam:
|
||||||
|
if n.Type != nil {
|
||||||
|
return n.Type
|
||||||
|
}
|
||||||
|
return anyType
|
||||||
|
case *sem.Type:
|
||||||
|
return n
|
||||||
|
case *sem.TypeMatcher:
|
||||||
|
return n
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg := asResolvableType(arg); arg != nil {
|
||||||
|
param := asResolvableType(param)
|
||||||
|
if arg == param || param == anyType {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("cannot use %v as %v", describe(arg), describe(param))
|
||||||
|
}
|
||||||
|
|
||||||
|
// scope is a basic hierarchical name to object table
|
||||||
|
type scope struct {
|
||||||
|
objects map[string]objectAndSource
|
||||||
|
parent *scope
|
||||||
|
}
|
||||||
|
|
||||||
|
// objectAndSource is a sem.Named object with a source
|
||||||
|
type objectAndSource struct {
|
||||||
|
object sem.Named
|
||||||
|
source tok.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
// newScope returns a newly initalized scope
|
||||||
|
func newScope(parent *scope) scope {
|
||||||
|
return scope{objects: map[string]objectAndSource{}, parent: parent}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup() searches the scope and then its parents for the symbol with the
|
||||||
|
// given name.
|
||||||
|
func (s *scope) lookup(name string) *objectAndSource {
|
||||||
|
if o, found := s.objects[name]; found {
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
if s.parent == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return s.parent.lookup(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// declare() declares the symbol with the given name, erroring on symbol
|
||||||
|
// collision.
|
||||||
|
func (s *scope) declare(object sem.Named, source tok.Source) error {
|
||||||
|
name := object.GetName()
|
||||||
|
if existing := s.lookup(name); existing != nil {
|
||||||
|
return fmt.Errorf("%v '%v' already declared\nFirst declared here: %v", source, name, existing.source)
|
||||||
|
}
|
||||||
|
s.objects[name] = objectAndSource{object, source}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,330 @@
|
||||||
|
// Copyright 2021 The Tint Authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package resolver_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/parser"
|
||||||
|
"dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolver(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
src string
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
success := ""
|
||||||
|
for _, test := range []test{
|
||||||
|
{
|
||||||
|
`type X`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`enum E {}`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`enum E {A B C}`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`type X`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`[[display("Y")]] type X`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type x
|
||||||
|
match y: x`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e {a b c}
|
||||||
|
match y: c | a | b`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`fn f()`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`fn f<T>()`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type f32
|
||||||
|
fn f<N: num>()`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e { a b c }
|
||||||
|
fn f<N: e>()`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type f32
|
||||||
|
fn f<T>(T) -> f32`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type f32
|
||||||
|
type P<T>
|
||||||
|
match m: f32
|
||||||
|
fn f<T: m>(P<T>) -> T`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type f32
|
||||||
|
type P<T>
|
||||||
|
match m: f32
|
||||||
|
fn f(P<m>)`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e { a }
|
||||||
|
fn f(a)`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e { a b }
|
||||||
|
type T<E: e>
|
||||||
|
match m: a
|
||||||
|
fn f<E: m>(T<E>)`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e { a b }
|
||||||
|
type T<E: e>
|
||||||
|
match m: a
|
||||||
|
fn f(T<m>)`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e { a }
|
||||||
|
type T<E: e>
|
||||||
|
fn f(T<a>)`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type T<E: num>
|
||||||
|
fn f<E: num>(T<E>)`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`fn f<T>(T)`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e { a b }
|
||||||
|
fn f<E: e>()`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e { a b }
|
||||||
|
match m: a | b
|
||||||
|
fn f<E: m>()`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type f32
|
||||||
|
type T<x>
|
||||||
|
fn f(T<T<f32>>)`,
|
||||||
|
success,
|
||||||
|
}, {
|
||||||
|
`enum E {A A}`,
|
||||||
|
`
|
||||||
|
file.txt:1:6 'A' already declared
|
||||||
|
First declared here: file.txt:1:6
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`type X type X`,
|
||||||
|
`
|
||||||
|
file.txt:1:13 'X' already declared
|
||||||
|
First declared here: file.txt:1:6`,
|
||||||
|
}, {
|
||||||
|
`[[meow]] type X`,
|
||||||
|
`
|
||||||
|
file.txt:1:3 unknown decoration
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
`[[display("Y", "Z")]] type X`,
|
||||||
|
`
|
||||||
|
file.txt:1:3 expected a single value for 'display' decoration`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e { a }
|
||||||
|
enum e { b }`,
|
||||||
|
`
|
||||||
|
file.txt:2:6 'e' already declared
|
||||||
|
First declared here: file.txt:1:6`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type X
|
||||||
|
match X : X`,
|
||||||
|
`
|
||||||
|
file.txt:2:7 'X' already declared
|
||||||
|
First declared here: file.txt:1:6`,
|
||||||
|
}, {
|
||||||
|
`type T<X>
|
||||||
|
match M : T`,
|
||||||
|
`file.txt:2:11 'T' requires 1 template arguments, but 0 were provided`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
match x: y`,
|
||||||
|
`
|
||||||
|
file.txt:1:10 cannot resolve 'y'
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type a
|
||||||
|
match x: a | b`,
|
||||||
|
`
|
||||||
|
file.txt:2:14 cannot resolve 'b'
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type a
|
||||||
|
enum e { b }
|
||||||
|
match x: a | b`,
|
||||||
|
`
|
||||||
|
file.txt:3:14 'b' resolves to enum entry 'e.b' but type is expected
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type a
|
||||||
|
type b
|
||||||
|
match x: a | b | a`,
|
||||||
|
`
|
||||||
|
file.txt:3:18 duplicate option 'a' in matcher
|
||||||
|
First declared here: file.txt:3:10
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e { a c }
|
||||||
|
match x: a | b | c`,
|
||||||
|
`
|
||||||
|
file.txt:2:14 enum 'e' does not contain 'b'
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e { a }
|
||||||
|
match x: a
|
||||||
|
match x: a`,
|
||||||
|
`
|
||||||
|
file.txt:3:7 'x' already declared
|
||||||
|
First declared here: file.txt:2:7
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type t
|
||||||
|
match x: t
|
||||||
|
match y: x`,
|
||||||
|
`
|
||||||
|
'y' cannot be used for matcher
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
`fn f(u)`,
|
||||||
|
`file.txt:1:6 cannot resolve 'u'`,
|
||||||
|
}, {
|
||||||
|
`fn f() -> u`,
|
||||||
|
`file.txt:1:11 cannot resolve 'u'`,
|
||||||
|
}, {
|
||||||
|
`fn f<T: u>()`,
|
||||||
|
`file.txt:1:9 cannot resolve 'u'`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum e { a }
|
||||||
|
fn f() -> e`,
|
||||||
|
`file.txt:2:11 cannot use 'e' as return type. Must be a type or template type`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type T<x>
|
||||||
|
fn f(T<u>)`,
|
||||||
|
`file.txt:2:8 cannot resolve 'u'`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type x
|
||||||
|
fn f<T>(T<x>)`,
|
||||||
|
`file.txt:2:9 'T' template parameters do not accept template arguments`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type A<N: num>
|
||||||
|
type B
|
||||||
|
fn f(A<B>)`,
|
||||||
|
`file.txt:3:8 cannot use type 'B' as template number`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type A<N>
|
||||||
|
enum E { b }
|
||||||
|
fn f(A<b>)`,
|
||||||
|
`file.txt:3:8 cannot use enum entry 'E.b' as template type`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type T
|
||||||
|
type P<N: num>
|
||||||
|
match m: T
|
||||||
|
fn f(P<m>)`,
|
||||||
|
`file.txt:4:8 cannot use type matcher 'm' as template number`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type P<N: num>
|
||||||
|
enum E { b }
|
||||||
|
fn f(P<E>)`,
|
||||||
|
`file.txt:3:8 cannot use enum 'E' as template number`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type P<N: num>
|
||||||
|
enum E { a b }
|
||||||
|
match m: a | b
|
||||||
|
fn f(P<m>)`,
|
||||||
|
`file.txt:4:8 cannot use enum matcher 'm' as template number`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
type P<N: num>
|
||||||
|
enum E { a b }
|
||||||
|
match m: a | b
|
||||||
|
fn f<M: m>(P<M>)`,
|
||||||
|
`file.txt:4:14 cannot use template enum 'E' as template number`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum E { a }
|
||||||
|
type T<X: a>`,
|
||||||
|
`file.txt:2:8 invalid template parameter type 'a'`,
|
||||||
|
}, {
|
||||||
|
`
|
||||||
|
enum E { a }
|
||||||
|
fn f<M: a>()`,
|
||||||
|
`file.txt:2:6 invalid template parameter type 'a'`,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
|
||||||
|
ast, err := parser.Parse(strings.TrimSpace(string(test.src)), "file.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected parser error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
expectErr := strings.TrimSpace(test.err)
|
||||||
|
_, err = resolver.Resolve(ast)
|
||||||
|
if err != nil {
|
||||||
|
gotErr := strings.TrimSpace(fmt.Sprint(err))
|
||||||
|
if gotErr != expectErr {
|
||||||
|
t.Errorf("While parsing:\n%s\nGot error:\n%s\nExpected:\n%s", test.src, gotErr, expectErr)
|
||||||
|
}
|
||||||
|
} else if expectErr != success {
|
||||||
|
t.Errorf("While parsing:\n%s\nGot no error, expected error:\n%s", test.src, expectErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
// Copyright 2021 The Tint Authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sem is the root of the semantic tree
|
||||||
|
type Sem struct {
|
||||||
|
Enums []*Enum
|
||||||
|
Types []*Type
|
||||||
|
TypeMatchers []*TypeMatcher
|
||||||
|
EnumMatchers []*EnumMatcher
|
||||||
|
Functions []*Function
|
||||||
|
// Maximum number of open-types used across all intrinsics
|
||||||
|
MaxOpenTypes int
|
||||||
|
// Maximum number of open-numbers used across all intrinsics
|
||||||
|
MaxOpenNumbers int
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Sem
|
||||||
|
func New() *Sem {
|
||||||
|
return &Sem{
|
||||||
|
Enums: []*Enum{},
|
||||||
|
Types: []*Type{},
|
||||||
|
TypeMatchers: []*TypeMatcher{},
|
||||||
|
EnumMatchers: []*EnumMatcher{},
|
||||||
|
Functions: []*Function{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enum describes an enumerator
|
||||||
|
type Enum struct {
|
||||||
|
Decl ast.EnumDecl
|
||||||
|
Name string
|
||||||
|
Entries []*EnumEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindEntry returns the enum entry with the given name
|
||||||
|
func (e *Enum) FindEntry(name string) *EnumEntry {
|
||||||
|
for _, entry := range e.Entries {
|
||||||
|
if entry.Name == name {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumEntry is an entry in an enumerator
|
||||||
|
type EnumEntry struct {
|
||||||
|
Enum *Enum
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type declares a type
|
||||||
|
type Type struct {
|
||||||
|
TemplateParams []TemplateParam
|
||||||
|
Decl ast.TypeDecl
|
||||||
|
Name string
|
||||||
|
DisplayName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeMatcher declares a type matcher
|
||||||
|
type TypeMatcher struct {
|
||||||
|
TemplateParams []TemplateParam
|
||||||
|
Decl ast.MatcherDecl
|
||||||
|
Name string
|
||||||
|
Types []*Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumMatcher declares a enum matcher
|
||||||
|
type EnumMatcher struct {
|
||||||
|
TemplateParams []TemplateParam
|
||||||
|
Decl ast.MatcherDecl
|
||||||
|
Name string
|
||||||
|
Enum *Enum
|
||||||
|
Options []*EnumEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateEnumParam is a template enum parameter
|
||||||
|
type TemplateEnumParam struct {
|
||||||
|
Name string
|
||||||
|
Enum *Enum
|
||||||
|
Matcher *EnumMatcher // Optional
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateTypeParam is a template type parameter
|
||||||
|
type TemplateTypeParam struct {
|
||||||
|
Name string
|
||||||
|
Type ResolvableType
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateNumberParam is a template type parameter
|
||||||
|
type TemplateNumberParam struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function describes the overloads of an intrinsic function
|
||||||
|
type Function struct {
|
||||||
|
Name string
|
||||||
|
Overloads []*Overload
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload describes a single overload of a function
|
||||||
|
type Overload struct {
|
||||||
|
Decl ast.FunctionDecl
|
||||||
|
TemplateParams []TemplateParam
|
||||||
|
OpenTypes []*TemplateTypeParam
|
||||||
|
OpenNumbers []TemplateParam
|
||||||
|
ReturnType *FullyQualifiedName
|
||||||
|
Parameters []Parameter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameter describes a single parameter of a function overload
|
||||||
|
type Parameter struct {
|
||||||
|
Name string
|
||||||
|
Type FullyQualifiedName
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullyQualifiedName is the usage of a Type, TypeMatcher or TemplateTypeParam
|
||||||
|
type FullyQualifiedName struct {
|
||||||
|
Target Named
|
||||||
|
TemplateArguments []FullyQualifiedName
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateParam is a TemplateEnumParam, TemplateTypeParam or TemplateNumberParam
|
||||||
|
type TemplateParam interface {
|
||||||
|
Named
|
||||||
|
isTemplateParam()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*TemplateEnumParam) isTemplateParam() {}
|
||||||
|
func (*TemplateTypeParam) isTemplateParam() {}
|
||||||
|
func (*TemplateNumberParam) isTemplateParam() {}
|
||||||
|
|
||||||
|
// ResolvableType is a Type, TypeMatcher or TemplateTypeParam
|
||||||
|
type ResolvableType interface {
|
||||||
|
Named
|
||||||
|
isResolvableType()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Type) isResolvableType() {}
|
||||||
|
func (*TypeMatcher) isResolvableType() {}
|
||||||
|
func (*TemplateTypeParam) isResolvableType() {}
|
||||||
|
|
||||||
|
// Named is something that can be looked up by name
|
||||||
|
type Named interface {
|
||||||
|
isNamed()
|
||||||
|
GetName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Enum) isNamed() {}
|
||||||
|
func (*EnumEntry) isNamed() {}
|
||||||
|
func (*Type) isNamed() {}
|
||||||
|
func (*TypeMatcher) isNamed() {}
|
||||||
|
func (*EnumMatcher) isNamed() {}
|
||||||
|
func (*TemplateTypeParam) isNamed() {}
|
||||||
|
func (*TemplateEnumParam) isNamed() {}
|
||||||
|
func (*TemplateNumberParam) isNamed() {}
|
||||||
|
|
||||||
|
// GetName returns the name of the Enum
|
||||||
|
func (e *Enum) GetName() string { return e.Name }
|
||||||
|
|
||||||
|
// GetName returns the name of the EnumEntry
|
||||||
|
func (e *EnumEntry) GetName() string { return e.Name }
|
||||||
|
|
||||||
|
// GetName returns the name of the Type
|
||||||
|
func (t *Type) GetName() string { return t.Name }
|
||||||
|
|
||||||
|
// GetName returns the name of the TypeMatcher
|
||||||
|
func (t *TypeMatcher) GetName() string { return t.Name }
|
||||||
|
|
||||||
|
// GetName returns the name of the EnumMatcher
|
||||||
|
func (e *EnumMatcher) GetName() string { return e.Name }
|
||||||
|
|
||||||
|
// GetName returns the name of the TemplateTypeParam
|
||||||
|
func (t *TemplateTypeParam) GetName() string { return t.Name }
|
||||||
|
|
||||||
|
// GetName returns the name of the TemplateEnumParam
|
||||||
|
func (t *TemplateEnumParam) GetName() string { return t.Name }
|
||||||
|
|
||||||
|
// GetName returns the name of the TemplateNumberParam
|
||||||
|
func (t *TemplateNumberParam) GetName() string { return t.Name }
|
Loading…
Reference in New Issue