Skip to content

Commit 0734f62

Browse files
committed
internal/gcimporter: support type parameterized aliases in indexed format
Adds support for type parameterized aliases in indexed format to gcimporter. Updates golang/go#68778 Change-Id: I475ab30fee8d1d273f678496a1c2c12b011b8a95 Reviewed-on: https://go-review.googlesource.com/c/tools/+/606335 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Findley <rfindley@google.com>
1 parent b5f24ec commit 0734f62

File tree

3 files changed

+177
-12
lines changed

3 files changed

+177
-12
lines changed

internal/gcimporter/iexport.go

+22-3
Original file line numberDiff line numberDiff line change
@@ -741,9 +741,22 @@ func (p *iexporter) doDecl(obj types.Object) {
741741
}
742742

743743
if obj.IsAlias() {
744-
w.tag(aliasTag)
744+
alias, materialized := t.(*aliases.Alias) // may fail when aliases are not enabled
745+
746+
var tparams *types.TypeParamList
747+
if materialized {
748+
tparams = aliases.TypeParams(alias)
749+
}
750+
if tparams.Len() == 0 {
751+
w.tag(aliasTag)
752+
} else {
753+
w.tag(genericAliasTag)
754+
}
745755
w.pos(obj.Pos())
746-
if alias, ok := t.(*aliases.Alias); ok {
756+
if tparams.Len() > 0 {
757+
w.tparamList(obj.Name(), tparams, obj.Pkg())
758+
}
759+
if materialized {
747760
// Preserve materialized aliases,
748761
// even of non-exported types.
749762
t = aliases.Rhs(alias)
@@ -963,7 +976,13 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
963976
}
964977
switch t := t.(type) {
965978
case *aliases.Alias:
966-
// TODO(adonovan): support parameterized aliases, following *types.Named.
979+
if targs := aliases.TypeArgs(t); targs.Len() > 0 {
980+
w.startType(instanceType)
981+
w.pos(t.Obj().Pos())
982+
w.typeList(targs, pkg)
983+
w.typ(aliases.Origin(t), pkg)
984+
return
985+
}
967986
w.startType(aliasType)
968987
w.qualifiedType(t.Obj())
969988

internal/gcimporter/iexport_test.go

+147
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ import (
1616
"go/ast"
1717
"go/build"
1818
"go/constant"
19+
"go/importer"
1920
"go/parser"
2021
"go/token"
2122
"go/types"
2223
"io"
2324
"math/big"
2425
"os"
26+
"path/filepath"
2527
"reflect"
2628
"runtime"
2729
"sort"
@@ -454,3 +456,148 @@ func TestUnexportedStructFields(t *testing.T) {
454456
type importerFunc func(path string) (*types.Package, error)
455457

456458
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
459+
460+
// TestIExportDataTypeParameterizedAliases tests IExportData
461+
// on both declarations and uses of type parameterized aliases.
462+
func TestIExportDataTypeParameterizedAliases(t *testing.T) {
463+
testenv.NeedsGo1Point(t, 23)
464+
465+
testenv.NeedsGoExperiment(t, "aliastypeparams")
466+
t.Setenv("GODEBUG", "gotypesalias=1")
467+
468+
// High level steps:
469+
// * parse and typecheck
470+
// * export the data for the importer (via IExportData),
471+
// * import the data (via either x/tools or GOROOT's gcimporter), and
472+
// * check the imported types.
473+
474+
const src = `package a
475+
476+
type A[T any] = *T
477+
type B[R any, S *R] = []S
478+
type C[U any] = B[U, A[U]]
479+
480+
type Named int
481+
type Chained = C[Named] // B[Named, A[Named]] = B[Named, *Named] = []*Named
482+
`
483+
484+
// parse and typecheck
485+
fset1 := token.NewFileSet()
486+
f, err := parser.ParseFile(fset1, "a", src, 0)
487+
if err != nil {
488+
t.Fatal(err)
489+
}
490+
var conf types.Config
491+
pkg1, err := conf.Check("a", fset1, []*ast.File{f}, nil)
492+
if err != nil {
493+
t.Fatal(err)
494+
}
495+
496+
testcases := map[string]func(t *testing.T) *types.Package{
497+
// Read the result of IExportData through x/tools/internal/gcimporter.IImportData.
498+
"tools": func(t *testing.T) *types.Package {
499+
// export
500+
exportdata, err := iexport(fset1, gcimporter.IExportVersion, pkg1)
501+
if err != nil {
502+
t.Fatal(err)
503+
}
504+
505+
// import
506+
imports := make(map[string]*types.Package)
507+
fset2 := token.NewFileSet()
508+
_, pkg2, err := gcimporter.IImportData(fset2, imports, exportdata, pkg1.Path())
509+
if err != nil {
510+
t.Fatalf("IImportData(%s): %v", pkg1.Path(), err)
511+
}
512+
return pkg2
513+
},
514+
// Read the result of IExportData through $GOROOT/src/internal/gcimporter.IImportData.
515+
//
516+
// This test fakes creating an old go object file in indexed format.
517+
// This means that it can be loaded by go/importer or go/types.
518+
// This step is not supported, but it does give test coverage for stdlib.
519+
"goroot": func(t *testing.T) *types.Package {
520+
t.Skip("Fix bug in src/internal/gcimporter.IImportData for aliasType then reenable")
521+
522+
// Write indexed export data file contents.
523+
//
524+
// TODO(taking): Slightly unclear to what extent this step should be supported by go/importer.
525+
var buf bytes.Buffer
526+
buf.WriteString("go object \n$$B\n") // object file header
527+
if err := gcexportdata.Write(&buf, fset1, pkg1); err != nil {
528+
t.Fatal(err)
529+
}
530+
531+
// Write export data to temporary file
532+
out := t.TempDir()
533+
name := filepath.Join(out, "a.out")
534+
if err := os.WriteFile(name+".a", buf.Bytes(), 0644); err != nil {
535+
t.Fatal(err)
536+
}
537+
pkg2, err := importer.Default().Import(name)
538+
if err != nil {
539+
t.Fatal(err)
540+
}
541+
return pkg2
542+
},
543+
}
544+
545+
for name, importer := range testcases {
546+
t.Run(name, func(t *testing.T) {
547+
pkg := importer(t)
548+
549+
obj := pkg.Scope().Lookup("A")
550+
if obj == nil {
551+
t.Fatalf("failed to find %q in package %s", "A", pkg)
552+
}
553+
554+
// Check that A is type A[T any] = *T.
555+
// TODO(taking): fix how go/types prints parameterized aliases to simplify tests.
556+
alias, ok := obj.Type().(*aliases.Alias)
557+
if !ok {
558+
t.Fatalf("Obj %s is not an Alias", obj)
559+
}
560+
561+
targs := aliases.TypeArgs(alias)
562+
if targs.Len() != 0 {
563+
t.Errorf("%s has %d type arguments. expected 0", alias, targs.Len())
564+
}
565+
566+
tparams := aliases.TypeParams(alias)
567+
if tparams.Len() != 1 {
568+
t.Fatalf("%s has %d type arguments. expected 1", alias, targs.Len())
569+
}
570+
tparam := tparams.At(0)
571+
if got, want := tparam.String(), "T"; got != want {
572+
t.Errorf("(%q).TypeParams().At(0)=%q. want %q", alias, got, want)
573+
}
574+
575+
anyt := types.Universe.Lookup("any").Type()
576+
if c := tparam.Constraint(); !types.Identical(anyt, c) {
577+
t.Errorf("(%q).Constraint()=%q. expected %q", tparam, c, anyt)
578+
}
579+
580+
ptparam := types.NewPointer(tparam)
581+
if rhs := aliases.Rhs(alias); !types.Identical(ptparam, rhs) {
582+
t.Errorf("(%q).Rhs()=%q. expected %q", alias, rhs, ptparam)
583+
}
584+
585+
// TODO(taking): add tests for B and C once it is simpler to write tests.
586+
587+
chained := pkg.Scope().Lookup("Chained")
588+
if chained == nil {
589+
t.Fatalf("failed to find %q in package %s", "Chained", pkg)
590+
}
591+
592+
named, _ := pkg.Scope().Lookup("Named").(*types.TypeName)
593+
if named == nil {
594+
t.Fatalf("failed to find %q in package %s", "Named", pkg)
595+
}
596+
597+
want := types.NewSlice(types.NewPointer(named.Type()))
598+
if got := chained.Type(); !types.Identical(got, want) {
599+
t.Errorf("(%q).Type()=%q which should be identical to %q", chained, got, want)
600+
}
601+
})
602+
}
603+
}

internal/gcimporter/iimport.go

+8-9
Original file line numberDiff line numberDiff line change
@@ -562,15 +562,14 @@ func (r *importReader) obj(name string) {
562562
pos := r.pos()
563563

564564
switch tag {
565-
case aliasTag:
566-
typ := r.typ()
567-
// TODO(adonovan): support generic aliases:
568-
// if tag == genericAliasTag {
569-
// tparams := r.tparamList()
570-
// alias.SetTypeParams(tparams)
571-
// }
565+
case aliasTag, genericAliasTag:
572566
var tparams []*types.TypeParam
573-
r.declare(aliases.NewAlias(r.p.aliases, pos, r.currPkg, name, typ, tparams))
567+
if tag == genericAliasTag {
568+
tparams = r.tparamList()
569+
}
570+
typ := r.typ()
571+
obj := aliases.NewAlias(r.p.aliases, pos, r.currPkg, name, typ, tparams)
572+
r.declare(obj)
574573

575574
case constTag:
576575
typ, val := r.value()
@@ -863,7 +862,7 @@ func (r *importReader) string() string { return r.p.stringAt(r.uint64()) }
863862
func (r *importReader) doType(base *types.Named) (res types.Type) {
864863
k := r.kind()
865864
if debug {
866-
r.p.trace("importing type %d (base: %s)", k, base)
865+
r.p.trace("importing type %d (base: %v)", k, base)
867866
r.p.indent++
868867
defer func() {
869868
r.p.indent--

0 commit comments

Comments
 (0)