Repository: goplus/gop
Pull request #2355 (2025-05-15 02:50:16)

cl: support aliastypeparams
support Go1.24 aliastypeparams


Patch:
diff --git a/.github/codecov.yml b/.github/codecov.yml
index 3d97bb0c..671ba926 100644
--- a/.github/codecov.yml
+++ b/.github/codecov.yml
@@ -20,6 +20,7 @@ coverage:
     - "cl/internal/spx3"
     - "cl/internal/test"
     - "cl/internal/unit"
+    - "cl/internal/typesalias"
     - "parser/fsx"
     - "parser/parsertest"
     - "x/jsonrpc2"
diff --git a/cl/builtin.go b/cl/builtin.go
index 176f76d5..0ba488b6 100644
--- a/cl/builtin.go
+++ b/cl/builtin.go
@@ -21,6 +21,7 @@ import (
 	"go/types"
 
 	"github.com/goplus/gogen"
+	"github.com/goplus/gop/cl/internal/typesalias"
 )
 
 // -----------------------------------------------------------------------------
@@ -81,7 +82,7 @@ const (
 	osxPkgPath = "github.com/qiniu/x/gop/osx"
 )
 
-func newBuiltinDefault(pkg *gogen.Package, conf *gogen.Config) *types.Package {
+func (ctx *pkgCtx) newBuiltinDefault(pkg *gogen.Package, conf *gogen.Config) *types.Package {
 	builtin := types.NewPackage("", "")
 	fmt := pkg.TryImport("fmt")
 	os := pkg.TryImport("os")
@@ -95,9 +96,12 @@ func newBuiltinDefault(pkg *gogen.Package, conf *gogen.Config) *types.Package {
 	pkg.TryImport("strings")
 	if ng.Types != nil {
 		initMathBig(pkg, conf, ng)
-		if obj := ng.Types.Scope().Lookup("Gop_ninteger"); obj != nil {
-			if _, ok := obj.Type().(*types.Basic); !ok {
-				conf.EnableTypesalias = true
+		if typesalias.Support {
+			if obj := ng.Types.Scope().Lookup("Gop_ninteger"); obj != nil {
+				if _, ok := obj.Type().(*types.Basic); !ok {
+					conf.EnableTypesalias = true
+					ctx.gotypesalias = true
+				}
 			}
 		}
 	}
diff --git a/cl/compile.go b/cl/compile.go
index a345bf1c..b90fd12d 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -29,6 +29,7 @@ import (
 	"github.com/goplus/gogen"
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/ast/fromgo"
+	"github.com/goplus/gop/cl/internal/typesalias"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/mod/modfile"
 	"github.com/qiniu/x/errors"
@@ -349,6 +350,8 @@ type pkgCtx struct {
 
 	goxMain      int // normal gox files with main func
 	goxMainClass string
+
+	gotypesalias bool // support go typesalias
 }
 
 type pkgImp struct {
@@ -523,7 +526,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Packag
 		LoadNamed:       ctx.loadNamed,
 		HandleErr:       ctx.handleErr,
 		NodeInterpreter: interp,
-		NewBuiltin:      newBuiltinDefault,
+		NewBuiltin:      ctx.newBuiltinDefault,
 		DefaultGoFile:   defaultGoFile,
 		NoSkipConstant:  conf.NoSkipConstant,
 		PkgPathIox:      osxPkgPath,
@@ -1078,7 +1081,7 @@ func preloadFile(p *gogen.Package, ctx *blockCtx, f *ast.File, goFile string, ge
 								if debugLoad {
 									log.Println("==> Load > AliasType", name)
 								}
-								aliasType(pkg, t.Pos(), name, toType(ctx, t.Type))
+								aliasType(ctx, pkg, t.Pos(), name, t)
 								return
 							}
 							if debugLoad {
@@ -1310,9 +1313,30 @@ func newType(pkg *types.Package, pos token.Pos, name string) *types.Named {
 	return types.NewNamed(typName, nil, nil)
 }
 
-func aliasType(pkg *types.Package, pos token.Pos, name string, typ types.Type) {
-	o := types.NewTypeName(pos, pkg, name, typ)
-	pkg.Scope().Insert(o)
+func aliasType(ctx *blockCtx, pkg *types.Package, pos token.Pos, name string, t *ast.TypeSpec) {
+	if ctx.gotypesalias {
+		var typeParams []*types.TypeParam
+		if t.TypeParams != nil {
+			typeParams = toTypeParams(ctx, t.TypeParams)
+			ctx.tlookup = &typeParamLookup{typeParams}
+			defer func() {
+				ctx.tlookup = nil
+			}()
+			org := ctx.inInst
+			ctx.inInst = 0
+			defer func() {
+				ctx.inInst = org
+			}()
+		}
+		o := typesalias.NewAlias(types.NewTypeName(pos, pkg, name, nil), toType(ctx, t.Type))
+		if typeParams != nil {
+			typesalias.SetTypeParams(o, typeParams)
+		}
+		pkg.Scope().Insert(o.Obj())
+	} else {
+		o := types.NewTypeName(pos, pkg, name, toType(ctx, t.Type))
+		pkg.Scope().Insert(o)
+	}
 }
 
 func loadFunc(ctx *blockCtx, recv *types.Var, name string, d *ast.FuncDecl, genBody bool) {
diff --git a/cl/internal/typesalias/alias_go121.go b/cl/internal/typesalias/alias_go121.go
new file mode 100644
index 00000000..77d50e5d
--- /dev/null
+++ b/cl/internal/typesalias/alias_go121.go
@@ -0,0 +1,33 @@
+//go:build !go1.22
+// +build !go1.22
+
+package typesalias
+
+import "go/types"
+
+const Support = false
+
+type Alias struct {
+}
+
+const unsupported = "typesAlias are unsupported at this go version"
+
+func (t *Alias) Underlying() types.Type {
+	panic(unsupported)
+}
+
+func (t *Alias) String() string {
+	panic(unsupported)
+}
+
+func (t *Alias) Obj() *types.TypeName {
+	panic(unsupported)
+}
+
+func NewAlias(obj *types.TypeName, rhs types.Type) *Alias {
+	return &Alias{}
+}
+
+func Unalias(t types.Type) types.Type {
+	return t
+}
diff --git a/cl/internal/typesalias/alias_go122.go b/cl/internal/typesalias/alias_go122.go
new file mode 100644
index 00000000..d5f84735
--- /dev/null
+++ b/cl/internal/typesalias/alias_go122.go
@@ -0,0 +1,18 @@
+//go:build go1.22
+// +build go1.22
+
+package typesalias
+
+import "go/types"
+
+const Support = true
+
+type Alias = types.Alias
+
+func NewAlias(obj *types.TypeName, rhs types.Type) *Alias {
+	return types.NewAlias(obj, rhs)
+}
+
+func Unalias(t types.Type) types.Type {
+	return types.Unalias(t)
+}
diff --git a/cl/internal/typesalias/typeparams_go122.go b/cl/internal/typesalias/typeparams_go122.go
new file mode 100644
index 00000000..fc9ea9e8
--- /dev/null
+++ b/cl/internal/typesalias/typeparams_go122.go
@@ -0,0 +1,18 @@
+//go:build !go1.23
+// +build !go1.23
+
+package typesalias
+
+import "go/types"
+
+func TypeArgs(t *Alias) *types.TypeList {
+	return nil
+}
+
+func TypeParams(t *Alias) *types.TypeParamList {
+	return nil
+}
+
+func SetTypeParams(t *Alias, params []*types.TypeParam) {
+	panic("unsupport Alias.SetTypeParams")
+}
diff --git a/cl/internal/typesalias/typeparams_go123.go b/cl/internal/typesalias/typeparams_go123.go
new file mode 100644
index 00000000..58c63c77
--- /dev/null
+++ b/cl/internal/typesalias/typeparams_go123.go
@@ -0,0 +1,18 @@
+//go:build go1.23
+// +build go1.23
+
+package typesalias
+
+import "go/types"
+
+func TypeArgs(t *Alias) *types.TypeList {
+	return t.TypeArgs()
+}
+
+func TypeParams(t *Alias) *types.TypeParamList {
+	return t.TypeParams()
+}
+
+func SetTypeParams(t *Alias, params []*types.TypeParam) {
+	t.SetTypeParams(params)
+}
diff --git a/cl/typeparams.go b/cl/typeparams.go
index ede7ff34..54c3bfbd 100644
--- a/cl/typeparams.go
+++ b/cl/typeparams.go
@@ -20,6 +20,7 @@ import (
 	"go/types"
 
 	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/cl/internal/typesalias"
 	"github.com/goplus/gop/token"
 )
 
@@ -155,8 +156,13 @@ func initType(ctx *blockCtx, named *types.Named, spec *ast.TypeSpec) {
 		ctx.inInst = org
 	}()
 	typ := toType(ctx, spec.Type)
-	if named, ok := typ.(*types.Named); ok {
-		typ = getUnderlying(ctx, named)
+retry:
+	switch t := typ.(type) {
+	case *types.Named:
+		typ = getUnderlying(ctx, t)
+	case *typesalias.Alias:
+		typ = typesalias.Unalias(t)
+		goto retry
 	}
 	named.SetUnderlying(typ)
 }


Test patch:
diff --git a/cl/compile_gop_test.go b/cl/compile_gop_test.go
index fa1c1b7d..73543bda 100644
--- a/cl/compile_gop_test.go
+++ b/cl/compile_gop_test.go
@@ -972,6 +972,30 @@ d := bar.Var(bar.M)
 }
 
 func TestMixedOverloadNamed(t *testing.T) {
+	var expect string
+	if gotypesalias {
+		expect = `package main
+
+var a Var__0[int]
+var b Var__1[M]
+
+func main() {
+	c := Gopx_Var_Cast__0[string]()
+	d := Gopx_Var_Cast__1[M]()
+}
+`
+	} else {
+		expect = `package main
+
+var a Var__0[int]
+var b Var__1[map[string]interface{}]
+
+func main() {
+	c := Gopx_Var_Cast__0[string]()
+	d := Gopx_Var_Cast__1[map[string]interface{}]()
+}
+`
+	}
 	gopMixedClTest(t, "main", `package main
 
 type M = map[string]any
@@ -1000,16 +1024,7 @@ var a Var[int]
 var b Var[M]
 c := Var(string)
 d := Var(M)
-`, `package main
-
-var a Var__0[int]
-var b Var__1[map[string]interface{}]
-
-func main() {
-	c := Gopx_Var_Cast__0[string]()
-	d := Gopx_Var_Cast__1[map[string]interface{}]()
-}
-`)
+`, expect)
 }
 
 func TestStringLitBasic(t *testing.T) {
@@ -1077,6 +1092,28 @@ func main() {
 }
 
 func TestMixedGo(t *testing.T) {
+	var expect string
+	if gotypesalias {
+		expect = `package main
+
+var a [10]int
+var b string = f(n)
+var c foo2
+var d int = c.v
+var e = foo3{}
+var x string = c.Str()
+`
+	} else {
+		expect = `package main
+
+var a [10]int
+var b string = f(n)
+var c foo
+var d int = c.v
+var e = foo3{}
+var x string = c.Str()
+`
+	}
 	gopMixedClTest(t, "main", `package main
 
 import "strconv"
@@ -1111,15 +1148,7 @@ var c foo2
 var d int = c.v
 var e = foo3{}
 var x string = c.str
-`, `package main
-
-var a [10]int
-var b string = f(n)
-var c foo
-var d int = c.v
-var e = foo3{}
-var x string = c.Str()
-`, true)
+`, expect, true)
 	gopMixedClTest(t, "main", `package main
 type Point struct {
 	X int
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 323ff405..95ec8cc9 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -33,6 +33,7 @@ const (
 var (
 	gblConfLine  *cl.Config
 	gotypesalias bool
+	go1point     int
 )
 
 func init() {
@@ -47,6 +48,7 @@ func init() {
 		NoAutoGenMain: true,
 	}
 	gotypesalias = cltest.EnableTypesalias()
+	go1point = cltest.Go1Point()
 }
 
 func gopClNamedTest(t *testing.T, name string, gopcode, expected string) {
diff --git a/cl/typeparams_test.go b/cl/typeparams_test.go
index b55ba4fc..f94d8d43 100644
--- a/cl/typeparams_test.go
+++ b/cl/typeparams_test.go
@@ -146,7 +146,7 @@ type DataString = Data[string]
 type SliceString = Slice[[]string, string]
 
 func main() {
-	fmt.Println(Data[int]{1}.v)
+	fmt.Println(DataInt{1}.v)
 	fmt.Println(DataString{"hello"}.v)
 	fmt.Println(Data[int]{100}.v)
 	fmt.Println(Data[string]{"hello"}.v)
@@ -154,7 +154,7 @@ func main() {
 		X int
 		Y int
 	}]{}.v.X)
-	v1 := Slice[[]int, int]{}
+	v1 := SliceInt{}
 	v2 := SliceString{}
 	v3 := Slice[[]int, int]{}
 	v3.Append([]int{1, 2, 3, 4}...)
@@ -343,7 +343,7 @@ _ = At[int]
 
 func TestTypeParamsErrInferFunc(t *testing.T) {
 	var expect string
-	if cltest.Go1Point() == 24 {
+	if go1point == 24 {
 		expect = `b.gop:2:5: cannot infer T2 (declared at /foo/a.go:4:21)`
 	} else {
 		expect = `b.gop:2:5: cannot infer T2 (/foo/a.go:4:21)`
@@ -844,3 +844,51 @@ p.onCmd string, Message,"hello",(n, msg) => {
 }
 `)
 }
+
+func TestAliasTypeparams(t *testing.T) {
+	if !gotypesalias || go1point < 23 {
+		t.Skip()
+	}
+	t.Setenv("GOEXPERIMENT", "aliastypeparams")
+	gopMixedClTest(t, "main", `package main
+
+type Set[T comparable] = map[T]struct{}
+`, `
+set := Set[string]{
+	"go":  {},
+	"gop": {},
+	"gox": {},
+}
+echo set
+`, `package main
+
+import "fmt"
+
+func main() {
+	set := Set[string]{"go": struct {
+	}{}, "gop": struct {
+	}{}, "gox": struct {
+	}{}}
+	fmt.Println(set)
+}
+`)
+
+	gopMixedClTest(t, "main", `package main
+
+type Pair[T, U any] = struct {
+	First  T
+	Second U
+}
+`, `
+c := &Pair[string, bool]{"hello", true}
+echo c
+`, `package main
+
+import "fmt"
+
+func main() {
+	c := &Pair[string, bool]{"hello", true}
+	fmt.Println(c)
+}
+`)
+}
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
index 15942644..19a1b743 100644
--- a/x/typesutil/info_test.go
+++ b/x/typesutil/info_test.go
@@ -1580,36 +1580,43 @@ d := bar.Var(bar.M)
 }
 
 func TestMixedOverloadNamed(t *testing.T) {
-	testGopInfo(t, `
-var a Var[int]
-var b Var[M]
-c := Var(string)
-d := Var(M)
-`, `
-package main
-
-type M = map[string]any
-
-type basetype interface {
-	string | int | bool | float64
-}
-
-type Var__0[T basetype] struct {
-	val T
-}
-
-type Var__1[T map[string]any] struct {
-	val T
-}
-
-func Gopx_Var_Cast__0[T basetype]() *Var__0[T] {
-	return new(Var__0[T])
-}
-
-func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
-	return new(Var__1[T])
-}
-`, `== types ==
+	var expect string
+	if gotypesalias {
+		expect = `== types ==
+000:  2: 7 | Var                 *ast.Ident                     | type    : main.Var__0[int] | type
+001:  2: 7 | Var[int]            *ast.IndexExpr                 | type    : main.Var__0[int] | type
+002:  2:11 | int                 *ast.Ident                     | type    : int | type
+003:  3: 7 | Var                 *ast.Ident                     | type    : main.Var__1[main.M] | type
+004:  3: 7 | Var[M]              *ast.IndexExpr                 | type    : main.Var__1[main.M] | type
+005:  3:11 | M                   *ast.Ident                     | type    : main.M | type
+006:  4: 6 | Var                 *ast.Ident                     | value   : func[T main.basetype]() *main.Var__0[T] | value
+007:  4: 6 | Var(string)         *ast.CallExpr                  | value   : *main.Var__0[string] | value
+008:  4:10 | string              *ast.Ident                     | type    : string | type
+009:  5: 6 | Var                 *ast.Ident                     | value   : func[T map[string]interface{}]() *main.Var__1[T] | value
+010:  5: 6 | Var(M)              *ast.CallExpr                  | value   : *main.Var__1[main.M] | value
+011:  5:10 | M                   *ast.Ident                     | type    : main.M | type
+== defs ==
+000:  2: 5 | a                   | var main.a main.Var__0[int]
+001:  3: 5 | b                   | var main.b main.Var__1[main.M]
+002:  4: 1 | c                   | var c *main.Var__0[string]
+003:  4: 1 | main                | func main.main()
+004:  5: 1 | d                   | var d *main.Var__1[main.M]
+== uses ==
+000:  2: 7 | Var                 | type main.Var__0[T main.basetype] struct{val T}
+001:  2:11 | int                 | type int
+002:  3: 7 | Var                 | type main.Var__1[T map[string]any] struct{val T}
+003:  3:11 | M                   | type main.M = map[string]any
+004:  4: 6 | Var                 | func main.Gopx_Var_Cast__0[T main.basetype]() *main.Var__0[T]
+005:  4:10 | string              | type string
+006:  5: 6 | Var                 | func main.Gopx_Var_Cast__1[T map[string]any]() *main.Var__1[T]
+007:  5:10 | M                   | type main.M = map[string]any
+== overloads ==
+000:  2: 7 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})
+001:  3: 7 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})
+002:  4: 6 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})
+003:  5: 6 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})`
+	} else {
+		expect = `== types ==
 000:  2: 7 | Var                 *ast.Ident                     | type    : main.Var__0[int] | type
 001:  2: 7 | Var[int]            *ast.IndexExpr                 | type    : main.Var__0[int] | type
 002:  2:11 | int                 *ast.Ident                     | type    : int | type
@@ -1641,15 +1648,14 @@ func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
 000:  2: 7 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})
 001:  3: 7 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})
 002:  4: 6 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})
-003:  5: 6 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})`)
-}
+003:  5: 6 | Var                 | type main.Var = func(__gop_overload_args__ interface{_()})`
+	}
 
-func TestMixedRawNamed(t *testing.T) {
 	testGopInfo(t, `
-var a Var__0[int]
-var b Var__1[M]
-c := Gopx_Var_Cast__0[string]
-d := Gopx_Var_Cast__1[M]
+var a Var[int]
+var b Var[M]
+c := Var(string)
+d := Var(M)
 `, `
 package main
 
@@ -1674,7 +1680,42 @@ func Gopx_Var_Cast__0[T basetype]() *Var__0[T] {
 func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
 	return new(Var__1[T])
 }
-`, `== types ==
+`, expect)
+}
+
+func TestMixedRawNamed(t *testing.T) {
+	var expect string
+	if gotypesalias {
+		expect = `== types ==
+000:  2: 7 | Var__0              *ast.Ident                     | type    : main.Var__0[int] | type
+001:  2: 7 | Var__0[int]         *ast.IndexExpr                 | type    : main.Var__0[int] | type
+002:  2:14 | int                 *ast.Ident                     | type    : int | type
+003:  3: 7 | Var__1              *ast.Ident                     | type    : main.Var__1[main.M] | type
+004:  3: 7 | Var__1[M]           *ast.IndexExpr                 | type    : main.Var__1[main.M] | type
+005:  3:14 | M                   *ast.Ident                     | type    : main.M | type
+006:  4: 6 | Gopx_Var_Cast__0    *ast.Ident                     | value   : func[T main.basetype]() *main.Var__0[T] | value
+007:  4: 6 | Gopx_Var_Cast__0[string] *ast.IndexExpr                 | var     : func() *main.Var__0[string] | variable
+008:  4:23 | string              *ast.Ident                     | type    : string | type
+009:  5: 6 | Gopx_Var_Cast__1    *ast.Ident                     | value   : func[T map[string]interface{}]() *main.Var__1[T] | value
+010:  5: 6 | Gopx_Var_Cast__1[M] *ast.IndexExpr                 | var     : func() *main.Var__1[main.M] | variable
+011:  5:23 | M                   *ast.Ident                     | type    : main.M | type
+== defs ==
+000:  2: 5 | a                   | var main.a main.Var__0[int]
+001:  3: 5 | b                   | var main.b main.Var__1[main.M]
+002:  4: 1 | c                   | var c func() *main.Var__0[string]
+003:  4: 1 | main                | func main.main()
+004:  5: 1 | d                   | var d func() *main.Var__1[main.M]
+== uses ==
+000:  2: 7 | Var__0              | type main.Var__0[T main.basetype] struct{val T}
+001:  2:14 | int                 | type int
+002:  3: 7 | Var__1              | type main.Var__1[T map[string]any] struct{val T}
+003:  3:14 | M                   | type main.M = map[string]any
+004:  4: 6 | Gopx_Var_Cast__0    | func main.Gopx_Var_Cast__0[T main.basetype]() *main.Var__0[T]
+005:  4:23 | string              | type string
+006:  5: 6 | Gopx_Var_Cast__1    | func main.Gopx_Var_Cast__1[T map[string]any]() *main.Var__1[T]
+007:  5:23 | M                   | type main.M = map[string]any`
+	} else {
+		expect = `== types ==
 000:  2: 7 | Var__0              *ast.Ident                     | type    : main.Var__0[int] | type
 001:  2: 7 | Var__0[int]         *ast.IndexExpr                 | type    : main.Var__0[int] | type
 002:  2:14 | int                 *ast.Ident                     | type    : int | type
@@ -1701,7 +1742,38 @@ func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
 004:  4: 6 | Gopx_Var_Cast__0    | func main.Gopx_Var_Cast__0[T main.basetype]() *main.Var__0[T]
 005:  4:23 | string              | type string
 006:  5: 6 | Gopx_Var_Cast__1    | func main.Gopx_Var_Cast__1[T map[string]any]() *main.Var__1[T]
-007:  5:23 | M                   | type main.M = map[string]any`)
+007:  5:23 | M                   | type main.M = map[string]any`
+	}
+	testGopInfo(t, `
+var a Var__0[int]
+var b Var__1[M]
+c := Gopx_Var_Cast__0[string]
+d := Gopx_Var_Cast__1[M]
+`, `
+package main
+
+type M = map[string]any
+
+type basetype interface {
+	string | int | bool | float64
+}
+
+type Var__0[T basetype] struct {
+	val T
+}
+
+type Var__1[T map[string]any] struct {
+	val T
+}
+
+func Gopx_Var_Cast__0[T basetype]() *Var__0[T] {
+	return new(Var__0[T])
+}
+
+func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
+	return new(Var__1[T])
+}
+`, expect)
 }
 
 func TestSpxInfo(t *testing.T) {