Repository: goplus/gop
Pull request #1386 (2023-06-20 03:29:50)

parser/cl: classfile support embed type starExpr and selectorExpr
classfile embed type support starExpr and selectorExpr

Rect.gopx
``` 
import "bytes"

var (
	*bytes.Buffer
	My
	x, y int
)
```
generate:
```
type Rect struct {
	*bytes.Buffer
	My
	x int
	y int
}
```



Patch:
diff --git a/ast/ast.go b/ast/ast.go
index 77a55156..544cb8d5 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -16,7 +16,6 @@
 
 // Package ast declares the types used to represent syntax trees for Go+
 // packages.
-//
 package ast
 
 import (
@@ -71,7 +70,6 @@ type Comment = ast.Comment
 
 // A CommentGroup represents a sequence of comments
 // with no other tokens and no empty lines between.
-//
 type CommentGroup = ast.CommentGroup
 
 // ----------------------------------------------------------------------------
@@ -526,7 +524,6 @@ func (x *ChanType) End() token.Pos { return x.Value.End() }
 
 // exprNode() ensures that only expression/type nodes can be
 // assigned to an Expr.
-//
 func (*BadExpr) exprNode()        {}
 func (*Ident) exprNode()          {}
 func (*Ellipsis) exprNode()       {}
@@ -577,7 +574,6 @@ func (x *Ident) String() string {
 
 // A statement is represented by a tree consisting of one
 // or more of the following concrete statement nodes.
-//
 type (
 	// A BadStmt node is a placeholder for statements containing
 	// syntax errors for which no correct statement nodes can be
@@ -910,7 +906,6 @@ func (s *RangeStmt) End() token.Pos { return s.Body.End() }
 
 // stmtNode() ensures that only statement nodes can be
 // assigned to a Stmt.
-//
 func (*BadStmt) stmtNode()        {}
 func (*DeclStmt) stmtNode()       {}
 func (*EmptyStmt) stmtNode()      {}
@@ -938,7 +933,6 @@ func (*RangeStmt) stmtNode()      {}
 
 // A Spec node represents a single (non-parenthesized) import,
 // constant, type, or variable declaration.
-//
 type (
 	// The Spec type stands for any of *ImportSpec, *ValueSpec, and *TypeSpec.
 	Spec interface {
@@ -988,7 +982,12 @@ func (s *ImportSpec) Pos() token.Pos {
 }
 
 // Pos returns position of first character belonging to the node.
-func (s *ValueSpec) Pos() token.Pos { return s.Names[0].Pos() }
+func (s *ValueSpec) Pos() token.Pos {
+	if len(s.Names) == 0 {
+		return s.Type.Pos()
+	}
+	return s.Names[0].Pos()
+}
 
 // Pos returns position of first character belonging to the node.
 func (s *TypeSpec) Pos() token.Pos { return s.Name.Pos() }
@@ -1017,13 +1016,11 @@ func (s *TypeSpec) End() token.Pos { return s.Type.End() }
 
 // specNode() ensures that only spec nodes can be
 // assigned to a Spec.
-//
 func (*ImportSpec) specNode() {}
 func (*ValueSpec) specNode()  {}
 func (*TypeSpec) specNode()   {}
 
 // A declaration is represented by one of the following declaration nodes.
-//
 type (
 	// A BadDecl node is a placeholder for declarations containing
 	// syntax errors for which no correct declaration nodes can be
@@ -1096,7 +1093,6 @@ func (d *FuncDecl) End() token.Pos {
 
 // declNode() ensures that only declaration nodes can be
 // assigned to a Decl.
-//
 func (*BadDecl) declNode()  {}
 func (*GenDecl) declNode()  {}
 func (*FuncDecl) declNode() {}
@@ -1124,7 +1120,6 @@ type FileType = int16
 // interpretation of the syntax tree by the manipulating program: Except for Doc
 // and Comment comments directly associated with nodes, the remaining comments
 // are "free-floating" (see also issues #18593, #20744).
-//
 type File struct {
 	Doc          *CommentGroup   // associated documentation; or nil
 	Package      token.Pos       // position of "package" keyword
@@ -1154,7 +1149,6 @@ func (f *File) End() token.Pos {
 
 // A Package node represents a set of source files
 // collectively building a Go package.
-//
 type Package struct {
 	Name    string               // package name
 	Scope   *Scope               // package scope across all files
diff --git a/cl/compile.go b/cl/compile.go
index d2a93a14..54f9d0aa 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -605,19 +605,13 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 				}
 				for _, v := range specs {
 					spec := v.(*ast.ValueSpec)
-					if len(spec.Values) > 0 {
-						pos := ctx.Position(v.Pos())
-						ctx.handleCodeErrorf(&pos, "cannot assign value to field in class file")
-						continue
-					}
 					var embed bool
-					var typ types.Type
-					if spec.Type == nil {
-						typ = toType(ctx, spec.Names[0])
+					if spec.Names == nil {
 						embed = true
-					} else {
-						typ = toType(ctx, spec.Type)
+						v := parseTypeEmbedName(spec.Type)
+						spec.Names = []*ast.Ident{v}
 					}
+					typ := toType(ctx, spec.Type)
 					for _, name := range spec.Names {
 						if chk.chkRedecl(ctx, name.Name, name.Pos()) {
 							continue
@@ -661,6 +655,20 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 	preloadFile(p, ctx, file, f, true)
 }
 
+func parseTypeEmbedName(typ ast.Expr) *ast.Ident {
+retry:
+	switch t := typ.(type) {
+	case *ast.Ident:
+		return t
+	case *ast.SelectorExpr:
+		return t.Sel
+	case *ast.StarExpr:
+		typ = t.X
+		goto retry
+	}
+	return nil
+}
+
 func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, genCode bool) {
 	parent := ctx.pkgCtx
 	syms := parent.syms
diff --git a/parser/parser.go b/parser/parser.go
index 75d3709b..9ec81c93 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -77,6 +77,8 @@ type parser struct {
 	syncPos token.Pos // last synchronization position
 	syncCnt int       // number of parser.advance calls without progress
 
+	varDeclCnt int // number of var decl
+
 	// Non-syntactic parser control
 	exprLev int  // < 0: in control clause, >= 0: in expression
 	inRHS   bool // if set, the parser is parsing a rhs expression
@@ -104,7 +106,6 @@ func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mod
 
 	p.mode = mode
 	p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently)
-
 	p.next()
 }
 
@@ -3351,26 +3352,64 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota
 	}
 
 	pos := p.pos
-	idents := p.parseIdentList()
-	typ := p.tryType()
+	var idents []*ast.Ident
+	var typ ast.Expr
 	var values []ast.Expr
-	// always permit optional initialization for more tolerant parsing
-	if p.tok == token.ASSIGN {
-		p.next()
-		values = p.parseRHSList()
+	if p.mode&ParseGoPlusClass != 0 && p.varDeclCnt == 1 {
+		var starPos token.Pos
+		if p.tok == token.MUL {
+			starPos = p.pos
+			p.next()
+		}
+		ident := p.parseIdent()
+		if p.tok == token.PERIOD {
+			p.next()
+			typ = &ast.SelectorExpr{
+				X:   ident,
+				Sel: p.parseIdent(),
+			}
+			if starPos != token.NoPos {
+				typ = &ast.StarExpr{
+					Star: starPos,
+					X:    typ,
+				}
+			}
+		} else if starPos != token.NoPos {
+			typ = &ast.StarExpr{
+				Star: starPos,
+				X:    ident,
+			}
+		} else {
+			idents = append(idents, ident)
+			for p.tok == token.COMMA {
+				p.next()
+				idents = append(idents, p.parseIdent())
+			}
+			typ = p.tryType()
+			if len(idents) == 1 && typ == nil {
+				typ = ident
+				idents = nil
+			}
+			if p.tok == token.ASSIGN {
+				p.error(p.pos, "syntax error: cannot assign value to field in class file")
+			}
+		}
+		p.expect(token.SEMICOLON)
+	} else {
+		idents = p.parseIdentList()
+		typ = p.tryType()
+		// always permit optional initialization for more tolerant parsing
+		if p.tok == token.ASSIGN {
+			p.next()
+			values = p.parseRHSList()
+		}
+		p.expectSemi() // call before accessing p.linecomment
 	}
-	p.expectSemi() // call before accessing p.linecomment
 
 	switch keyword {
 	case token.VAR:
 		if typ == nil && values == nil {
-			if p.mode&ParseGoPlusClass != 0 {
-				if len(idents) != 1 {
-					p.error(pos, "syntax error: unexpected newline, expected type")
-				}
-			} else {
-				p.error(pos, "missing variable type or initialization")
-			}
+			p.error(pos, "missing variable type or initialization")
 		}
 	case token.CONST:
 		if values == nil && (iota == 0 || typ != nil) {
@@ -3425,7 +3464,9 @@ func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.Gen
 	if p.trace {
 		defer un(trace(p, "GenDecl("+keyword.String()+")"))
 	}
-
+	if keyword == token.VAR {
+		p.varDeclCnt++
+	}
 	doc := p.leadComment
 	pos := p.expect(keyword)
 	var lparen, rparen token.Pos
diff --git a/printer/nodes.go b/printer/nodes.go
index c575c291..1eaa4b19 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -66,13 +66,13 @@ func SetDebug(flags int) {
 // printed.
 //
 // TODO(gri): linebreak may add too many lines if the next statement at "line"
-//            is preceded by comments because the computation of n assumes
-//            the current position before the comment and the target position
-//            after the comment. Thus, after interspersing such comments, the
-//            space taken up by them is not considered to reduce the number of
-//            linebreaks. At the moment there is no easy way to know about
-//            future (not yet interspersed) comments in this function.
 //
+//	is preceded by comments because the computation of n assumes
+//	the current position before the comment and the target position
+//	after the comment. Thus, after interspersing such comments, the
+//	space taken up by them is not considered to reduce the number of
+//	linebreaks. At the moment there is no easy way to know about
+//	future (not yet interspersed) comments in this function.
 func (p *printer) linebreak(line, min int, ws whiteSpace, newSection bool) (nbreaks int) {
 	n := nlimit(line - p.pos.Line)
 	if n < min {
@@ -153,8 +153,9 @@ const filteredMsg = "contains filtered or unexported fields"
 // expressions.
 //
 // TODO(gri) Consider rewriting this to be independent of []ast.Expr
-//           so that we can use the algorithm for any kind of list
-//           (e.g., pass list via a channel over which to range).
+//
+//	so that we can use the algorithm for any kind of list
+//	(e.g., pass list via a channel over which to range).
 func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, next0 token.Pos, isIncomplete bool) {
 	if len(list) == 0 {
 		if isIncomplete {
@@ -690,6 +691,7 @@ func reduceDepth(depth int) int {
 // (Algorithm suggestion by Russ Cox.)
 //
 // The precedences are:
+//
 //	5             *  /  %  <<  >>  &  &^
 //	4             +  -  |  ^
 //	3             ==  !=  <  <=  >  >=
@@ -702,25 +704,24 @@ func reduceDepth(depth int) int {
 // To choose the cutoff, look at the whole expression but excluding primary
 // expressions (function calls, parenthesized exprs), and apply these rules:
 //
-//	1) If there is a binary operator with a right side unary operand
-//	   that would clash without a space, the cutoff must be (in order):
-//
-//		/*	6
-//		&&	6
-//		&^	6
-//		++	5
-//		--	5
+//  1. If there is a binary operator with a right side unary operand
+//     that would clash without a space, the cutoff must be (in order):
 //
-//         (Comparison operators always have spaces around them.)
+//     /*	6
+//     &&	6
+//     &^	6
+//     ++	5
+//     --	5
 //
-//	2) If there is a mix of level 5 and level 4 operators, then the cutoff
-//	   is 5 (use spaces to distinguish precedence) in Normal mode
-//	   and 4 (never use spaces) in Compact mode.
+//     (Comparison operators always have spaces around them.)
 //
-//	3) If there are no level 4 operators or no level 5 operators, then the
-//	   cutoff is 6 (always use spaces) in Normal mode
-//	   and 4 (never use spaces) in Compact mode.
+//  2. If there is a mix of level 5 and level 4 operators, then the cutoff
+//     is 5 (use spaces to distinguish precedence) in Normal mode
+//     and 4 (never use spaces) in Compact mode.
 //
+//  3. If there are no level 4 operators or no level 5 operators, then the
+//     cutoff is 6 (always use spaces) in Normal mode
+//     and 4 (never use spaces) in Compact mode.
 func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int) {
 	prec := x.Op.Precedence()
 	if prec < prec1 {
@@ -1293,7 +1294,6 @@ func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, po
 // indentList reports whether an expression list would look better if it
 // were indented wholesale (starting with the very first element, rather
 // than starting at the first line break).
-//
 func (p *printer) indentList(list []ast.Expr) bool {
 	// Heuristic: indentList reports whether there are more than one multi-
 	// line element in the list, or if there is any element that is not
@@ -1549,24 +1549,23 @@ type NewlineStmt struct {
 //
 // For example, the declaration:
 //
-//	const (
-//		foobar int = 42 // comment
-//		x          = 7  // comment
-//		foo
-//              bar = 991
-//	)
+//		const (
+//			foobar int = 42 // comment
+//			x          = 7  // comment
+//			foo
+//	             bar = 991
+//		)
 //
 // leads to the type/values matrix below. A run of value columns (V) can
 // be moved into the type column if there is no type for any of the values
 // in that column (we only move entire columns so that they align properly).
 //
-//	matrix        formatted     result
-//                    matrix
-//	T  V    ->    T  V     ->   true      there is a T and so the type
-//	-  V          -  V          true      column must be kept
-//	-  -          -  -          false
-//	-  V          V  -          false     V is moved into T column
-//
+//		matrix        formatted     result
+//	                   matrix
+//		T  V    ->    T  V     ->   true      there is a T and so the type
+//		-  V          -  V          true      column must be kept
+//		-  -          -  -          false
+//		-  V          V  -          false     V is moved into T column
 func keepTypeColumn(specs []ast.Spec) []bool {
 	m := make([]bool, len(specs))
 
@@ -1612,7 +1611,9 @@ func (p *printer) valueSpec(s *ast.ValueSpec, keepType bool) {
 	p.identList(s.Names, false) // always present
 	extraTabs := 3
 	if s.Type != nil || keepType {
-		p.print(vtab)
+		if len(s.Names) > 0 {
+			p.print(vtab)
+		}
 		extraTabs--
 	}
 	if s.Type != nil {
@@ -1677,7 +1678,6 @@ func sanitizeImportPath(lit *ast.BasicLit) *ast.BasicLit {
 // The parameter n is the number of specs in the group. If doIndent is set,
 // multi-line identifier lists in the spec are indented when the first
 // linebreak is encountered.
-//
 func (p *printer) spec(spec ast.Spec, n int, doIndent bool) {
 	switch s := spec.(type) {
 	case *ast.ImportSpec:
@@ -1741,7 +1741,7 @@ func (p *printer) genDecl(d *ast.GenDecl) {
 				var line int
 				for i, s := range d.Specs {
 					if i > 0 {
-						p.linebreak(p.lineFor(s.Pos()), 1, ignore, p.linesFrom(line) > 0)
+						p.linebreak(p.lineFor(s.Pos()), 0, ignore, p.linesFrom(line) > 0)
 					}
 					p.recordLine(&line)
 					p.valueSpec(s.(*ast.ValueSpec), keepType[i])
@@ -1770,7 +1770,6 @@ func (p *printer) genDecl(d *ast.GenDecl) {
 // The result is <= maxSize if the node fits on one line with at
 // most maxSize chars and the formatted output doesn't contain
 // any control chars. Otherwise, the result is > maxSize.
-//
 func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) {
 	// nodeSize invokes the printer, which may invoke nodeSize
 	// recursively. For deep composite literal nests, this can
@@ -1844,7 +1843,6 @@ func (p *printer) bodySize(b *ast.BlockStmt, maxSize int) int {
 // the block is printed on the current line, without line breaks, spaced from the header
 // by sep. Otherwise the block's opening "{" is printed on the current line, followed by
 // lines for the block's statements and its closing "}".
-//
 func (p *printer) funcBody(headerSize int, sep whiteSpace, b *ast.BlockStmt) {
 	if b == nil {
 		return
@@ -1884,7 +1882,6 @@ func (p *printer) funcBody(headerSize int, sep whiteSpace, b *ast.BlockStmt) {
 // the block is printed on the current line, without line breaks, spaced from the header
 // by sep. Otherwise the block's opening "{" is printed on the current line, followed by
 // lines for the block's statements and its closing "}".
-//
 func (p *printer) funcBodyUnnamed(headerSize int, sep whiteSpace, b *ast.BlockStmt) {
 	if b == nil {
 		return


Test patch:
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 2a2f62e7..50bdbbbc 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -120,23 +120,28 @@ func gopSpxErrorTestEx(t *testing.T, msg, gmx, spxcode, gmxfile, spxfile string)
 }
 
 func TestSpxError(t *testing.T) {
-	gopSpxErrorTestEx(t, `./Game.tgmx:4:2: cannot assign value to field in class file`, `
+	gopSpxErrorTestEx(t, `./Game.tgmx:6:2: userScore redeclared
+	./Game.tgmx:5:2 other declaration of userScore`, `
+import "bytes"
 var (
 	Kai Kai
-	userScore int = 100
+	userScore int
+	userScore string
 )
 `, `
 println "hi"
 `, "Game.tgmx", "Kai.tspx")
 
-	gopSpxErrorTestEx(t, `./Kai.tspx:3:2: cannot assign value to field in class file`, `
+	gopSpxErrorTestEx(t, `./Kai.tspx:4:2: id redeclared
+	./Kai.tspx:3:2 other declaration of id`, `
 var (
 	Kai Kai
 	userScore int
 )
 `, `
 var (
-	id = 100
+	id int
+	id string
 )
 println "hi"
 `, "Game.tgmx", "Kai.tspx")
diff --git a/cl/compile_test.go b/cl/compile_test.go
index e799c9cf..695686c9 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -4319,7 +4319,7 @@ func TestClassFileGopx(t *testing.T) {
 var (
 	BaseClass
 	Width, Height float64
-	AggClass
+	*AggClass
 )
 
 type BaseClass struct{
@@ -4343,11 +4343,45 @@ type Rect struct {
 	BaseClass
 	Width  float64
 	Height float64
-	AggClass
+	*AggClass
 }
 
 func (this *Rect) Area() float64 {
 	return this.Width * this.Height
 }
+`, "Rect.gopx")
+	gopClTestFile(t, `
+import "bytes"
+var (
+	bytes.Buffer
+)
+func test(){}
+`, `package main
+
+import bytes "bytes"
+
+type Rect struct {
+	bytes.Buffer
+}
+
+func (this *Rect) test() {
+}
+`, "Rect.gopx")
+	gopClTestFile(t, `
+import "bytes"
+var (
+	*bytes.Buffer
+)
+func test(){}
+`, `package main
+
+import bytes "bytes"
+
+type Rect struct {
+	*bytes.Buffer
+}
+
+func (this *Rect) test() {
+}
 `, "Rect.gopx")
 }
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 31085b14..1af1d36a 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -868,13 +868,6 @@ import (
 }
 
 func TestErrClassFileGopx(t *testing.T) {
-	codeErrorTestEx(t, "main", "Rect.gopx",
-		`./Rect.gopx:3:2: cannot assign value to field in class file`, `
-var (
-	i int = 1
-)
-println "hello"
-`)
 	codeErrorTestEx(t, "main", "Rect.gopx",
 		`./Rect.gopx:5:2: A redeclared
 	./Rect.gopx:3:2 other declaration of A`, `
diff --git a/parser/_testdata/gopxtest/bar.gopx b/parser/_testdata/gopxtest/bar.gopx
index 783e9630..95178287 100644
--- a/parser/_testdata/gopxtest/bar.gopx
+++ b/parser/_testdata/gopxtest/bar.gopx
@@ -1,7 +1,10 @@
 var (
 	A
+	*B
+	x, y string
+	C.A
+	*C.B
 	v int
-	B
 )
 
 type A struct{}
diff --git a/parser/_testdata/gopxtest/parser.expect b/parser/_testdata/gopxtest/parser.expect
index a4a5dabf..7a290bcc 100644
--- a/parser/_testdata/gopxtest/parser.expect
+++ b/parser/_testdata/gopxtest/parser.expect
@@ -5,20 +5,51 @@ ast.GenDecl:
   Tok: var
   Specs:
     ast.ValueSpec:
-      Names:
+      Type:
         ast.Ident:
           Name: A
+    ast.ValueSpec:
+      Type:
+        ast.StarExpr:
+          X:
+            ast.Ident:
+              Name: B
     ast.ValueSpec:
       Names:
         ast.Ident:
-          Name: v
+          Name: x
+        ast.Ident:
+          Name: y
       Type:
         ast.Ident:
-          Name: int
+          Name: string
+    ast.ValueSpec:
+      Type:
+        ast.SelectorExpr:
+          X:
+            ast.Ident:
+              Name: C
+          Sel:
+            ast.Ident:
+              Name: A
+    ast.ValueSpec:
+      Type:
+        ast.StarExpr:
+          X:
+            ast.SelectorExpr:
+              X:
+                ast.Ident:
+                  Name: C
+              Sel:
+                ast.Ident:
+                  Name: B
     ast.ValueSpec:
       Names:
         ast.Ident:
-          Name: B
+          Name: v
+      Type:
+        ast.Ident:
+          Name: int
 ast.GenDecl:
   Tok: type
   Specs:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index d96c6c86..6d2c6aef 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -395,7 +395,30 @@ func TestClassErrCode(t *testing.T) {
 	A,B
 	v int
 )
-`, `/foo/bar.gopx:2:2: syntax error: unexpected newline, expected type`, ``)
+`, `/foo/bar.gopx:2:2: missing variable type or initialization`, ``)
+	testClassErrCode(t, `var (
+	A.*B
+	v int
+)
+`, `/foo/bar.gopx:2:4: expected 'IDENT', found '*' (and 2 more errors)`, ``)
+	testClassErrCode(t, `var (
+	[]A
+	v int
+)
+`, `/foo/bar.gopx:2:2: expected 'IDENT', found '['`, ``)
+	testClassErrCode(t, `var (
+	*[]A
+	v int
+)
+`, `/foo/bar.gopx:2:3: expected 'IDENT', found '['`, ``)
+	testClassErrCode(t, `var (
+	v int = 10
+)
+`, `/foo/bar.gopx:2:8: syntax error: cannot assign value to field in class file`, ``)
+	testClassErrCode(t, `var (
+	v = 10
+)
+`, `/foo/bar.gopx:2:4: syntax error: cannot assign value to field in class file`, ``)
 }
 
 // -----------------------------------------------------------------------------