Repository: goplus/gop
Pull request #1759 (2024-02-19 08:15:50)

record types scope

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/goplus/gop/pull/1759?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goplus) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Comparison is base [(`23917a4`)](https://app.codecov.io/gh/goplus/gop/commit/23917a4447a1db4cc53f1b9c4d4ebc4270f93efb?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goplus) 92.12% compared to head [(`fe96f37`)](https://app.codecov.io/gh/goplus/gop/pull/1759?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goplus) 92.81%.
> Report is 4 commits behind head on main.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1759      +/-   ##
==========================================
+ Coverage   92.12%   92.81%   +0.68%     
==========================================
  Files          38       37       -1     
  Lines       10889     8990    -1899     
==========================================
- Hits        10032     8344    -1688     
+ Misses        694      483     -211     
  Partials      163      163              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/goplus/gop/pull/1759?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goplus).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goplus).


Patch:
diff --git a/cl/compile.go b/cl/compile.go
index 9f05b1e2..a602873a 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -556,7 +556,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 			c2goBase: c2goBase(conf.C2goBase), imports: make(map[string]pkgImp), isGopFile: true,
 		}
 		if rec := ctx.rec; rec != nil {
-			rec.Scope(f, fileScope)
+			rec.Scope(f.File, fileScope)
 		}
 		preloadGopFile(p, ctx, f.path, f.File, conf)
 	}
@@ -1235,7 +1235,7 @@ func loadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool) {
 	commentFunc(ctx, fn, d)
 	if rec := ctx.recorder(); rec != nil {
 		rec.Def(d.Name, fn.Func)
-		if recv == nil {
+		if recv == nil && d.Name.Name != "_" {
 			ctx.fileScope.Insert(fn.Func)
 		}
 	}
@@ -1308,6 +1308,14 @@ var unaryGopNames = map[string]string{
 func loadFuncBody(ctx *blockCtx, fn *gox.Func, body *ast.BlockStmt, src ast.Node) {
 	cb := fn.BodyStart(ctx.pkg, body)
 	compileStmts(ctx, body.List)
+	if rec := ctx.recorder(); rec != nil {
+		switch fn := src.(type) {
+		case *ast.FuncDecl:
+			rec.Scope(fn.Type, cb.Scope())
+		case *ast.FuncLit:
+			rec.Scope(fn.Type, cb.Scope())
+		}
+	}
 	cb.End(src)
 }
 
@@ -1354,6 +1362,9 @@ func loadImport(ctx *blockCtx, spec *ast.ImportSpec) {
 		} else {
 			rec.Implicit(spec, pkgName)
 		}
+		if name != "_" {
+			ctx.fileScope.Insert(pkgName)
+		}
 	}
 
 	if specName != nil {
diff --git a/cl/stmt.go b/cl/stmt.go
index 109a9129..498f06a5 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -151,6 +151,9 @@ func compileStmt(ctx *blockCtx, stmt ast.Stmt) {
 	case *ast.BlockStmt:
 		ctx.cb.Block()
 		compileStmts(ctx, v.List)
+		if rec := ctx.recorder(); rec != nil {
+			rec.Scope(v, ctx.cb.Scope())
+		}
 		ctx.cb.End()
 		return
 	case *ast.SelectStmt:
@@ -363,8 +366,14 @@ func compileRangeStmt(ctx *blockCtx, v *ast.RangeStmt) {
 	if len(defineNames) > 0 {
 		defNames(ctx, defineNames, cb.Scope())
 	}
+	if rec := ctx.recorder(); rec != nil {
+		rec.Scope(v, cb.Scope())
+	}
 	cb.VBlock()
 	compileStmts(ctx, v.Body.List)
+	if rec := ctx.recorder(); rec != nil {
+		rec.Scope(v.Body, cb.Scope())
+	}
 	cb.End(v.Body)
 	cb.SetComments(comments, once)
 	setBodyHandler(ctx)
@@ -509,6 +518,9 @@ func compileForStmt(ctx *blockCtx, v *ast.ForStmt) {
 	cb := ctx.cb
 	comments, once := cb.BackupComments()
 	cb.For(v)
+	if rec := ctx.recorder(); rec != nil {
+		rec.Scope(v, cb.Scope())
+	}
 	if v.Init != nil {
 		compileStmt(ctx, v.Init)
 	}
@@ -518,6 +530,9 @@ func compileForStmt(ctx *blockCtx, v *ast.ForStmt) {
 		cb.None()
 	}
 	cb.Then(v.Body)
+	if rec := ctx.recorder(); rec != nil {
+		rec.Scope(v.Body, cb.Scope())
+	}
 	compileStmts(ctx, v.Body.List)
 	if v.Post != nil {
 		cb.Post()
@@ -541,6 +556,9 @@ func compileIfStmt(ctx *blockCtx, v *ast.IfStmt) {
 		compileStmt(ctx, v.Init)
 	}
 	compileExpr(ctx, v.Cond)
+	if rec := ctx.recorder(); rec != nil {
+		rec.Scope(v, cb.Scope())
+	}
 	cb.Then(v.Body)
 	compileStmts(ctx, v.Body.List)
 	if e := v.Else; e != nil {
@@ -552,6 +570,9 @@ func compileIfStmt(ctx *blockCtx, v *ast.IfStmt) {
 		}
 	}
 	cb.SetComments(comments, once)
+	if rec := ctx.recorder(); rec != nil {
+		rec.Scope(v.Body, cb.Scope())
+	}
 	cb.End(v)
 }
 
@@ -586,6 +607,9 @@ func compileTypeSwitchStmt(ctx *blockCtx, v *ast.TypeSwitchStmt) {
 		panic("TODO: type switch syntax error, please use x.(type)")
 	}
 	cb.TypeSwitch(name, v)
+	if rec := ctx.recorder(); rec != nil {
+		rec.Scope(v, cb.Scope())
+	}
 	if v.Init != nil {
 		compileStmt(ctx, v.Init)
 	}
@@ -636,6 +660,9 @@ func compileTypeSwitchStmt(ctx *blockCtx, v *ast.TypeSwitchStmt) {
 		cb.Then()
 		compileStmts(ctx, c.Body)
 		commentStmt(ctx, stmt)
+		if rec := ctx.recorder(); rec != nil {
+			rec.Scope(c, cb.Scope())
+		}
 		cb.End(c)
 	}
 	cb.SetComments(comments, once)
@@ -666,6 +693,9 @@ func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) {
 	} else {
 		cb.None() // switch {...}
 	}
+	if rec := ctx.recorder(); rec != nil {
+		rec.Scope(v, cb.Scope())
+	}
 	cb.Then(v.Body)
 	seen := make(valueMap)
 	var firstDefault ast.Stmt
@@ -715,6 +745,9 @@ func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) {
 			cb.Fallthrough()
 		}
 		commentStmt(ctx, stmt)
+		if rec := ctx.recorder(); rec != nil {
+			rec.Scope(c, cb.Scope())
+		}
 		cb.End(c)
 	}
 	cb.SetComments(comments, once)
@@ -764,6 +797,9 @@ func compileSelectStmt(ctx *blockCtx, v *ast.SelectStmt) {
 		cb.Then()
 		compileStmts(ctx, c.Body)
 		commentStmt(ctx, stmt)
+		if rec := ctx.recorder(); rec != nil {
+			rec.Scope(c, cb.Scope())
+		}
 		cb.End(c)
 	}
 	cb.SetComments(comments, once)


Test patch:
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
index 14a31fb0..bd1d4584 100644
--- a/x/typesutil/info_test.go
+++ b/x/typesutil/info_test.go
@@ -61,7 +61,7 @@ func spxParserConf() parser.Config {
 	}
 }
 
-func parserMixedSource(mod *gopmod.Module, fset *token.FileSet, name, src string, goname string, gosrc string, parserConf parser.Config) (*typesutil.Info, *types.Info, error) {
+func parseMixedSource(mod *gopmod.Module, fset *token.FileSet, name, src string, goname string, gosrc string, parserConf parser.Config) (*typesutil.Info, *types.Info, error) {
 	f, err := parser.ParseEntry(fset, name, src, parserConf)
 	if err != nil {
 		return nil, nil, err
@@ -78,7 +78,7 @@ func parserMixedSource(mod *gopmod.Module, fset *token.FileSet, name, src string
 	conf := &types.Config{}
 	conf.Importer = gop.NewImporter(nil, &env.Gop{Root: "../..", Version: "1.0"}, fset)
 	chkOpts := &typesutil.Config{
-		Types: types.NewPackage("main", "main"),
+		Types: types.NewPackage("main", f.Name.Name),
 		Fset:  fset,
 		Mod:   mod,
 	}
@@ -104,18 +104,19 @@ func parserMixedSource(mod *gopmod.Module, fset *token.FileSet, name, src string
 	return info, ginfo, err
 }
 
-func parserSource(fset *token.FileSet, filename string, src interface{}, mode parser.Mode) (*typesutil.Info, error) {
+func parseSource(fset *token.FileSet, filename string, src interface{}, mode parser.Mode) (*types.Package, *typesutil.Info, error) {
 	f, err := parser.ParseEntry(fset, filename, src, parser.Config{
 		Mode: mode,
 	})
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
+	pkg := types.NewPackage("", f.Name.Name)
 	conf := &types.Config{}
 	conf.Importer = importer.Default()
 	chkOpts := &typesutil.Config{
-		Types: types.NewPackage("main", "main"),
+		Types: pkg,
 		Fset:  fset,
 		Mod:   gopmod.Default,
 	}
@@ -130,13 +131,13 @@ func parserSource(fset *token.FileSet, filename string, src interface{}, mode pa
 	}
 	check := typesutil.NewChecker(conf, chkOpts, nil, info)
 	err = check.Files(nil, []*ast.File{f})
-	return info, err
+	return pkg, info, err
 }
 
-func parserGoSource(fset *token.FileSet, filename string, src interface{}, mode goparser.Mode) (*types.Info, error) {
+func parseGoSource(fset *token.FileSet, filename string, src interface{}, mode goparser.Mode) (*types.Package, *types.Info, error) {
 	f, err := goparser.ParseFile(fset, filename, src, mode)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	conf := &types.Config{}
@@ -149,10 +150,10 @@ func parserGoSource(fset *token.FileSet, filename string, src interface{}, mode
 		Selections: make(map[*goast.SelectorExpr]*types.Selection),
 		Scopes:     make(map[goast.Node]*types.Scope),
 	}
-	pkg := types.NewPackage("main", "main")
+	pkg := types.NewPackage("", f.Name.Name)
 	check := types.NewChecker(conf, fset, pkg, info)
 	err = check.Files([]*goast.File{f})
-	return info, err
+	return pkg, info, err
 }
 
 func testGopInfo(t *testing.T, src string, gosrc string, expect string) {
@@ -165,7 +166,7 @@ func testSpxInfo(t *testing.T, name string, src string, expect string) {
 
 func testGopInfoEx(t *testing.T, mod *gopmod.Module, name string, src string, goname string, gosrc string, expect string, parseConf parser.Config) {
 	fset := token.NewFileSet()
-	info, _, err := parserMixedSource(mod, fset, name, src, goname, gosrc, parseConf)
+	info, _, err := parseMixedSource(mod, fset, name, src, goname, gosrc, parseConf)
 	if err != nil {
 		t.Fatal("parserMixedSource error", err)
 	}
@@ -185,11 +186,11 @@ func testGopInfoEx(t *testing.T, mod *gopmod.Module, name string, src string, go
 
 func testInfo(t *testing.T, src interface{}) {
 	fset := token.NewFileSet()
-	info, err := parserSource(fset, "main.gop", src, parser.ParseComments)
+	_, info, err := parseSource(fset, "main.gop", src, parser.ParseComments)
 	if err != nil {
 		t.Fatal("parserSource error", err)
 	}
-	goinfo, err := parserGoSource(fset, "main.go", src, goparser.ParseComments)
+	_, goinfo, err := parseGoSource(fset, "main.go", src, goparser.ParseComments)
 	if err != nil {
 		t.Fatal("parserGoSource error", err)
 	}
@@ -1670,3 +1671,132 @@ func onCloned() {
 009: 15: 9 | info                | type main.info struct{x int; y int}
 010: 19: 2 | say                 | func (*github.com/goplus/gop/cl/internal/spx.Sprite).Say(msg string, secs ...float64)`)
 }
+
+func TestScopesInfo(t *testing.T) {
+	var tests = []struct {
+		src    string
+		scopes []string // list of scope descriptors of the form kind:varlist
+	}{
+		{`package p0`, []string{
+			"file:",
+		}},
+		{`package p1; import ( "fmt"; m "math"; _ "os" ); var ( _ = fmt.Println; _ = m.Pi )`, []string{
+			"file:fmt m",
+		}},
+		{`package p2; func _() {}`, []string{
+			"file:", "func:",
+		}},
+		{`package p3; func _(x, y int) {}`, []string{
+			"file:", "func:x y",
+		}},
+		{`package p4; func _(x, y int) { x, z := 1, 2; _ = z }`, []string{
+			"file:", "func:x y z", // redeclaration of x
+		}},
+		{`package p5; func _(x, y int) (u, _ int) { return }`, []string{
+			"file:", "func:u x y",
+		}},
+		{`package p6; func _() { { var x int; _ = x } }`, []string{
+			"file:", "func:", "block:x",
+		}},
+		{`package p7; func _() { if true {} }`, []string{
+			"file:", "func:", "if:", "block:",
+		}},
+		{`package p8; func _() { if x := 0; x < 0 { y := x; _ = y } }`, []string{
+			"file:", "func:", "if:x", "block:y",
+		}},
+		{`package p9; func _() { switch x := 0; x {} }`, []string{
+			"file:", "func:", "switch:x",
+		}},
+		{`package p10; func _() { switch x := 0; x { case 1: y := x; _ = y; default: }}`, []string{
+			"file:", "func:", "switch:x", "case:y", "case:",
+		}},
+		{`package p11; func _(t interface{}) { switch t.(type) {} }`, []string{
+			"file:", "func:t", "type switch:",
+		}},
+		{`package p12; func _(t interface{}) { switch t := t; t.(type) {} }`, []string{
+			"file:", "func:t", "type switch:t",
+		}},
+		{`package p13; func _(t interface{}) { switch x := t.(type) { case int: _ = x } }`, []string{
+			"file:", "func:t", "type switch:", "case:x", // x implicitly declared
+		}},
+		{`package p14; func _() { select{} }`, []string{
+			"file:", "func:",
+		}},
+		{`package p15; func _(c chan int) { select{ case <-c: } }`, []string{
+			"file:", "func:c", "comm:",
+		}},
+		{`package p16; func _(c chan int) { select{ case i := <-c: x := i; _ = x} }`, []string{
+			"file:", "func:c", "comm:i x",
+		}},
+		{`package p17; func _() { for{} }`, []string{
+			"file:", "func:", "for:", "block:",
+		}},
+		{`package p18; func _(n int) { for i := 0; i < n; i++ { _ = i } }`, []string{
+			"file:", "func:n", "for:i", "block:",
+		}},
+		{`package p19; func _(a []int) { for i := range a { _ = i} }`, []string{
+			"file:", "func:a", "range:i", "block:",
+		}},
+		{`package p20; var s int; func _(a []int) { for i, x := range a { s += x; _ = i } }`, []string{
+			"file:", "func:a", "range:i x", "block:",
+		}},
+		{`package p21; var s int; func _(a []int) { for i, x := range a { c := i; println(c) } }`, []string{
+			"file:", "func:a", "range:i x", "block:c",
+		}},
+	}
+
+	for _, test := range tests {
+		pkg, info, err := parseSource(token.NewFileSet(), "src.gop", test.src, 0)
+		if err != nil {
+			t.Fatalf("parse source failed: %v", test.src)
+		}
+		name := pkg.Name()
+		// number of scopes must match
+		if len(info.Scopes) != len(test.scopes) {
+			t.Errorf("package %s: got %d scopes; want %d\n%v\n%v", name, len(info.Scopes), len(test.scopes),
+				test.scopes, info.Scopes)
+		}
+
+		// scope descriptions must match
+		for node, scope := range info.Scopes {
+			kind := "<unknown node kind>"
+			switch node.(type) {
+			case *ast.File:
+				kind = "file"
+			case *ast.FuncType:
+				kind = "func"
+			case *ast.BlockStmt:
+				kind = "block"
+			case *ast.IfStmt:
+				kind = "if"
+			case *ast.SwitchStmt:
+				kind = "switch"
+			case *ast.TypeSwitchStmt:
+				kind = "type switch"
+			case *ast.CaseClause:
+				kind = "case"
+			case *ast.CommClause:
+				kind = "comm"
+			case *ast.ForStmt:
+				kind = "for"
+			case *ast.RangeStmt:
+				kind = "range"
+			default:
+				kind = fmt.Sprintf("<unknown node kind> %T", node)
+			}
+
+			// look for matching scope description
+			desc := kind + ":" + strings.Join(scope.Names(), " ")
+			found := false
+			for _, d := range test.scopes {
+				if desc == d {
+					found = true
+					break
+				}
+			}
+			if !found {
+				t.Errorf("package %s: no matching scope found for %s", name, desc)
+			}
+		}
+	}
+}