Help us shape the future of Go! Take the 2026 Go Developer Survey

Source file src/cmd/compile/internal/types2/validate_test.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package types2_test
     6  
     7  import (
     8  	"cmd/compile/internal/syntax"
     9  	"fmt"
    10  	"strings"
    11  	"testing"
    12  
    13  	. "cmd/compile/internal/types2"
    14  )
    15  
    16  // checkInvariants verifies structural well-formedness and consistency of
    17  // a type-checked package and its recorded type information:
    18  //  1. No Object has a nil Type().
    19  //  2. Every *Func object dynamically has a *Signature type.
    20  //  3. No compound type component (Pointer.Elem, Slice.Elem, Array.Elem,
    21  //     Map.Key, Map.Elem, Chan.Elem, Struct.Field, Tuple.At, Interface.Method,
    22  //     Interface.EmbeddedType, TypeParam.Constraint, Union.Term) is nil
    23  //     (empty Signature.Params and Results are canonically nil *Tuples).
    24  //  4. Accessor methods (Underlying, Rhs, Constraint, Unalias) never panic.
    25  //  5. Typ[Invalid] is never published for typed objects (*Var, *Const, *TypeName,
    26  //     *Func) or non-builtin expression types unless the package reported an error.
    27  //     (Non-typed objects like *PkgName, *Builtin, and *Label, and built-in
    28  //     expressions, have Typ[Invalid] by specification).
    29  func checkInvariants(pkg *Package, info *Info, hasErrors bool) []error {
    30  	v := &validator{
    31  		pkg:       pkg,
    32  		info:      info,
    33  		hasErrors: hasErrors,
    34  		seenTypes: make(map[Type]bool),
    35  		seenObjs:  make(map[Object]bool),
    36  	}
    37  	if pkg != nil {
    38  		v.checkScope(pkg.Scope(), "pkg.Scope()")
    39  	}
    40  	v.checkInfo(info)
    41  	return v.errs
    42  }
    43  
    44  type validator struct {
    45  	pkg       *Package
    46  	info      *Info
    47  	hasErrors bool
    48  	seenTypes map[Type]bool
    49  	seenObjs  map[Object]bool
    50  	errs      []error
    51  }
    52  
    53  func (v *validator) errorf(format string, args ...any) {
    54  	v.errs = append(v.errs, fmt.Errorf(format, args...))
    55  }
    56  
    57  func (v *validator) checkScope(s *Scope, where string) {
    58  	if s == nil {
    59  		return
    60  	}
    61  	for _, name := range s.Names() {
    62  		obj := s.Lookup(name)
    63  		v.checkObject(obj, fmt.Sprintf("%s.Lookup(%q)", where, name))
    64  	}
    65  	for i := 0; i < s.NumChildren(); i++ {
    66  		v.checkScope(s.Child(i), fmt.Sprintf("%s.Child(%d)", where, i))
    67  	}
    68  }
    69  
    70  func (v *validator) checkInfo(info *Info) {
    71  	if info == nil {
    72  		return
    73  	}
    74  	for ident, obj := range info.Defs {
    75  		if obj != nil {
    76  			v.checkObject(obj, fmt.Sprintf("Info.Defs[%s]", ident.Value))
    77  		}
    78  	}
    79  	for ident, obj := range info.Uses {
    80  		if obj != nil {
    81  			v.checkObject(obj, fmt.Sprintf("Info.Uses[%s]", ident.Value))
    82  		}
    83  	}
    84  	for node, obj := range info.Implicits {
    85  		if obj != nil {
    86  			v.checkObject(obj, fmt.Sprintf("Info.Implicits[%T]", node))
    87  		}
    88  	}
    89  	for _, s := range info.Scopes {
    90  		v.checkScope(s, "Info.Scopes")
    91  	}
    92  	for expr, tv := range info.Types {
    93  		if tv.Type == nil {
    94  			v.errorf("Info.Types[%v] has nil Type", expr)
    95  		} else if !tv.IsBuiltin() {
    96  			v.checkType(tv.Type, fmt.Sprintf("Info.Types[%v]", expr))
    97  		}
    98  	}
    99  }
   100  
   101  func (v *validator) checkObject(obj Object, where string) {
   102  	if obj == nil || v.seenObjs[obj] {
   103  		return
   104  	}
   105  	v.seenObjs[obj] = true
   106  
   107  	// Invariant 1: obj.Type() must never be nil.
   108  	typ := obj.Type()
   109  	if typ == nil {
   110  		v.errorf("object %q (%T) at %v has nil Type() in %s", obj.Name(), obj, obj.Pos(), where)
   111  		return
   112  	}
   113  
   114  	// Invariant 2: Func.Type() must dynamically be a *Signature.
   115  	if fn, ok := obj.(*Func); ok {
   116  		if _, ok := typ.(*Signature); !ok {
   117  			v.errorf("func %s has type %T, want *Signature in %s", fn.Name(), typ, where)
   118  		}
   119  	}
   120  
   121  	// PkgName, Builtin, and Label don't have a Go type; their Type()
   122  	// is defined as Typ[Invalid] by specification.
   123  	switch obj.(type) {
   124  	case *PkgName, *Builtin, *Label:
   125  		return
   126  	}
   127  
   128  	v.checkType(typ, fmt.Sprintf("object %s", obj.Name()))
   129  }
   130  
   131  func (v *validator) checkType(typ Type, where string) {
   132  	if typ == nil {
   133  		v.errorf("nil Type encountered at %s", where)
   134  		return
   135  	}
   136  	if v.seenTypes[typ] {
   137  		return
   138  	}
   139  	v.seenTypes[typ] = true
   140  
   141  	// Safe Underlying() call: must never panic.
   142  	var under Type
   143  	func() {
   144  		defer func() {
   145  			if r := recover(); r != nil {
   146  				v.errorf("panic calling Underlying() on %T at %s: %v", typ, where, r)
   147  			}
   148  		}()
   149  		under = typ.Underlying()
   150  	}()
   151  	if under == nil {
   152  		v.errorf("type %T at %s has nil Underlying()", typ, where)
   153  	}
   154  
   155  	switch t := typ.(type) {
   156  	case *Basic:
   157  		// Invariant: Typ[Invalid] is never published without a reported error.
   158  		if t == Typ[Invalid] && !v.hasErrors {
   159  			v.errorf("Typ[Invalid] published at %s without reported package errors", where)
   160  		}
   161  
   162  	case *Pointer:
   163  		if t.Elem() == nil {
   164  			v.errorf("Pointer at %s has nil Elem()", where)
   165  		} else {
   166  			v.checkType(t.Elem(), where+".Elem()")
   167  		}
   168  
   169  	case *Array:
   170  		if t.Elem() == nil {
   171  			v.errorf("Array at %s has nil Elem()", where)
   172  		} else {
   173  			v.checkType(t.Elem(), where+".Elem()")
   174  		}
   175  
   176  	case *Slice:
   177  		if t.Elem() == nil {
   178  			v.errorf("Slice at %s has nil Elem()", where)
   179  		} else {
   180  			v.checkType(t.Elem(), where+".Elem()")
   181  		}
   182  
   183  	case *Map:
   184  		if t.Key() == nil {
   185  			v.errorf("Map at %s has nil Key()", where)
   186  		} else {
   187  			v.checkType(t.Key(), where+".Key()")
   188  		}
   189  		if t.Elem() == nil {
   190  			v.errorf("Map at %s has nil Elem()", where)
   191  		} else {
   192  			v.checkType(t.Elem(), where+".Elem()")
   193  		}
   194  
   195  	case *Chan:
   196  		if t.Elem() == nil {
   197  			v.errorf("Chan at %s has nil Elem()", where)
   198  		} else {
   199  			v.checkType(t.Elem(), where+".Elem()")
   200  		}
   201  
   202  	case *Struct:
   203  		for i := 0; i < t.NumFields(); i++ {
   204  			f := t.Field(i)
   205  			if f == nil {
   206  				v.errorf("Struct at %s has nil Field(%d)", where, i)
   207  			} else {
   208  				v.checkObject(f, fmt.Sprintf("%s.Field(%d:%s)", where, i, f.Name()))
   209  			}
   210  		}
   211  
   212  	case *Tuple:
   213  		for i := 0; i < t.Len(); i++ {
   214  			elem := t.At(i)
   215  			if elem == nil {
   216  				v.errorf("Tuple at %s has nil At(%d)", where, i)
   217  			} else {
   218  				v.checkObject(elem, fmt.Sprintf("%s.At(%d)", where, i))
   219  			}
   220  		}
   221  
   222  	case *Signature:
   223  		if recv := t.Recv(); recv != nil {
   224  			v.checkObject(recv, where+".Recv()")
   225  		}
   226  		if params := t.Params(); params != nil {
   227  			v.checkType(params, where+".Params()")
   228  		}
   229  		if results := t.Results(); results != nil {
   230  			v.checkType(results, where+".Results()")
   231  		}
   232  		v.checkTypeParamList(t.TypeParams(), where+".TypeParams()")
   233  		v.checkTypeParamList(t.RecvTypeParams(), where+".RecvTypeParams()")
   234  
   235  	case *Interface:
   236  		for i := 0; i < t.NumMethods(); i++ {
   237  			m := t.Method(i)
   238  			if m == nil {
   239  				v.errorf("Interface at %s has nil Method(%d)", where, i)
   240  			} else {
   241  				v.checkObject(m, fmt.Sprintf("%s.Method(%s)", where, m.Name()))
   242  			}
   243  		}
   244  		for i := 0; i < t.NumEmbeddeds(); i++ {
   245  			emb := t.EmbeddedType(i)
   246  			if emb == nil {
   247  				v.errorf("Interface at %s has nil EmbeddedType(%d)", where, i)
   248  			} else {
   249  				v.checkType(emb, fmt.Sprintf("%s.EmbeddedType(%d)", where, i))
   250  			}
   251  		}
   252  
   253  	case *Named:
   254  		if t.Obj() == nil {
   255  			v.errorf("Named at %s has nil Obj()", where)
   256  		} else {
   257  			v.checkObject(t.Obj(), where+".Obj()")
   258  		}
   259  		if targs := t.TypeArgs(); targs != nil {
   260  			for i := 0; i < targs.Len(); i++ {
   261  				v.checkType(targs.At(i), fmt.Sprintf("%s.TypeArgs(%d)", where, i))
   262  			}
   263  		}
   264  		// Only check methods on origin types. On instantiated types (t.Origin() != t),
   265  		// methods are lazily expanded from the origin; eagerly traversing methods of
   266  		// instantiated types whose methods return further generic instances (e.g.
   267  		// func (List[E]) Map[F any](...) List[F]) leads to infinite recursive method
   268  		// instantiation.
   269  		if t.Origin() == t {
   270  			for i := 0; i < t.NumMethods(); i++ {
   271  				m := t.Method(i)
   272  				if m == nil {
   273  					v.errorf("Named at %s has nil Method(%d)", where, i)
   274  				} else {
   275  					v.checkObject(m, fmt.Sprintf("%s.Method(%s)", where, m.Name()))
   276  				}
   277  			}
   278  		}
   279  
   280  	case *Alias:
   281  		func() {
   282  			defer func() {
   283  				if r := recover(); r != nil {
   284  					v.errorf("panic calling Rhs() on Alias at %s: %v", where, r)
   285  				}
   286  			}()
   287  			rhs := t.Rhs()
   288  			if rhs == nil {
   289  				v.errorf("Alias at %s has nil Rhs()", where)
   290  			} else {
   291  				v.checkType(rhs, where+".Rhs()")
   292  			}
   293  		}()
   294  		func() {
   295  			defer func() {
   296  				if r := recover(); r != nil {
   297  					v.errorf("panic calling Unalias() on Alias at %s: %v", where, r)
   298  				}
   299  			}()
   300  			if actual := Unalias(t); actual == nil {
   301  				v.errorf("Unalias() returned nil on Alias at %s", where)
   302  			}
   303  		}()
   304  
   305  	case *TypeParam:
   306  		func() {
   307  			defer func() {
   308  				if r := recover(); r != nil {
   309  					v.errorf("panic calling Constraint() on TypeParam at %s: %v", where, r)
   310  				}
   311  			}()
   312  			c := t.Constraint()
   313  			if c == nil {
   314  				v.errorf("TypeParam at %s has nil Constraint()", where)
   315  			} else {
   316  				v.checkType(c, where+".Constraint()")
   317  			}
   318  		}()
   319  		if t.Obj() == nil {
   320  			v.errorf("TypeParam at %s has nil Obj()", where)
   321  		} else {
   322  			v.checkObject(t.Obj(), where+".Obj()")
   323  		}
   324  
   325  	case *Union:
   326  		for i := 0; i < t.Len(); i++ {
   327  			term := t.Term(i)
   328  			if term == nil || term.Type() == nil {
   329  				v.errorf("Union at %s has nil term at %d", where, i)
   330  			} else {
   331  				v.checkType(term.Type(), fmt.Sprintf("%s.Term(%d)", where, i))
   332  			}
   333  		}
   334  	}
   335  }
   336  
   337  func (v *validator) checkTypeParamList(tparams *TypeParamList, where string) {
   338  	if tparams == nil {
   339  		return
   340  	}
   341  	for i := 0; i < tparams.Len(); i++ {
   342  		tp := tparams.At(i)
   343  		if tp == nil {
   344  			v.errorf("%s at index %d is nil", where, i)
   345  		} else {
   346  			v.checkType(tp, fmt.Sprintf("%s.At(%d)", where, i))
   347  		}
   348  	}
   349  }
   350  
   351  func TestBrokenCodeInvariants(t *testing.T) {
   352  	tests := []struct {
   353  		name string
   354  		src  string
   355  	}{
   356  		{
   357  			name: "cyclic aliases",
   358  			src: `package p
   359  				type A = B
   360  				type B = A`,
   361  		},
   362  		{
   363  			name: "cyclic defined types",
   364  			src: `package p
   365  				type A B
   366  				type B A`,
   367  		},
   368  		{
   369  			name: "mutually recursive alias and struct",
   370  			src: `package p
   371  				type A = struct { b B }
   372  				type B = struct { a A }`,
   373  		},
   374  		{
   375  			name: "broken method receiver",
   376  			src: `package p
   377  				func (r Undeclared) M() {}`,
   378  		},
   379  		{
   380  			name: "generic receiver with wrong param count",
   381  			src: `package p
   382  				type T[A, B any] struct{}
   383  				func (r T[A]) M() {}`,
   384  		},
   385  		{
   386  			name: "broken signature parameters and results",
   387  			src: `package p
   388  				func f(a undeclared) (b undeclared) { return }`,
   389  		},
   390  		{
   391  			name: "broken short var decl in func body",
   392  			src: `package p
   393  				func f() {
   394  					x := undeclared
   395  					_ = x
   396  				}`,
   397  		},
   398  		{
   399  			name: "broken struct fields",
   400  			src: `package p
   401  				type S struct {
   402  					f undeclared
   403  				}`,
   404  		},
   405  	}
   406  
   407  	for _, tt := range tests {
   408  		t.Run(tt.name, func(t *testing.T) {
   409  			f, err := syntax.Parse(syntax.NewFileBase("test.go"), strings.NewReader(tt.src), func(error) {}, nil, 0)
   410  			if err != nil {
   411  				t.Fatal(err)
   412  			}
   413  			conf := Config{
   414  				Error: func(error) {}, // ignore expected errors
   415  			}
   416  			info := Info{
   417  				Types:     make(map[syntax.Expr]TypeAndValue),
   418  				Defs:      make(map[*syntax.Name]Object),
   419  				Uses:      make(map[*syntax.Name]Object),
   420  				Implicits: make(map[syntax.Node]Object),
   421  				Scopes:    make(map[syntax.Node]*Scope),
   422  			}
   423  			pkg, _ := conf.Check("p", []*syntax.File{f}, &info)
   424  			if errs := checkInvariants(pkg, &info, true); len(errs) > 0 {
   425  				for _, err := range errs {
   426  					t.Errorf("invariant violated: %v", err)
   427  				}
   428  			}
   429  		})
   430  	}
   431  }
   432  

View as plain text