diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0add4d505..cdf19d39f 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -27,9 +27,6 @@ jobs: git config --global user.name "build robot" go test -v cmd/make_test.go - - name: Compile gop and related tools - run: go install ./... - - name: Run testcases run: go test -v -coverprofile="coverage.txt" -covermode=atomic ./... diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 6f0a55ab5..2e61bcb09 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -10,6 +10,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Snapcraft + uses: samuelmeuli/action-snapcraft@v2 - name: Checkout tag run: | @@ -35,12 +40,23 @@ jobs: with: go-version: "1.21" - - name: Install goreleaser - run: | - go install github.com/goreleaser/goreleaser@latest - - - name: Release + - name: Release with goreleaser + uses: goreleaser/goreleaser-action@v5 + with: + # either 'goreleaser' (default) or 'goreleaser-pro' + distribution: goreleaser + version: latest + args: release --clean -p 4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} WINGET_PKGS_PRIVATE_KEY: ${{ secrets.WINGET_PKGS_PRIVATE_KEY }} - run: goreleaser release -p 4 + + - name: Upload deb/rpm to Fury.io + run: | + for file in dist/*.{deb,rpm} + do + echo "Uploading $file to Fury.io" + curl -sS -F package=@$file https://$FURY_TOKEN@push.fury.io/goplus/ + done + env: + FURY_TOKEN: ${{ secrets.FURY_TOKEN }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 10495b0d8..ef020d439 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -68,12 +68,25 @@ changelog: - "^test:" winget: - - name: gop + - name: goplus homepage: "https://goplus.org/" publisher: goplus publisher_url: https://github.com/goplus/gop publisher_support_url: "https://github.com/goplus/gop/issues/new" package_identifier: goplus.gop + path: "manifests/g/goplus/gop/{{.Version}}" + tags: + - golang + - go + - gop + - goplus + - programming + - language + - compiler + - interpreter + - data science + - engineering + - education short_description: The Go+ Programming Language description: | The Go+ programming language is designed for engineering, STEM education, and data science. @@ -101,3 +114,148 @@ winget: owner: microsoft name: winget-pkgs branch: master + +nfpms: + - package_name: gop + vendor: goplus + homepage: https://goplus.org/ + maintainer: Li Jie + license: Apache-2.0 + description: | + The Go+ programming language is designed for engineering, STEM education, and data science. + - For engineering: working in the simplest language that can be mastered by children. + - For STEM education: studying an engineering language that can be used for work in the future. + - For data science: communicating with engineers in the same language. + formats: + - "deb" + - "rpm" + overrides: + deb: + dependencies: + - "golang-go (>= 1.18.0)" + rpm: + dependencies: + - "golang-bin >= 1.18.0" + file_name_template: >- + {{ .ProjectName }}_v{{.Version}}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + bindir: /usr/lib/{{ .ProjectName }} + contents: + # source folder + - src: LICENSE + dst: /usr/lib/{{ .ProjectName }}/LICENSE + - src: "README.md" + dst: /usr/lib/{{ .ProjectName }}/README.md + - src: "go.mod" + dst: /usr/lib/{{ .ProjectName }}/go.mod + - src: "go.sum" + dst: /usr/lib/{{ .ProjectName }}/go.sum + - src: "*.go" + dst: /usr/lib/{{ .ProjectName }}/ + - src: ast + dst: /usr/lib/{{ .ProjectName }}/ast + - src: builtin + dst: /usr/lib/{{ .ProjectName }}/builtin + - src: cl + dst: /usr/lib/{{ .ProjectName }}/cl + - src: cmd + dst: /usr/lib/{{ .ProjectName }}/cmd + - src: doc + dst: /usr/lib/{{ .ProjectName }}/doc + - src: env + dst: /usr/lib/{{ .ProjectName }}/env + - src: format + dst: /usr/lib/{{ .ProjectName }}/format + - src: parser + dst: /usr/lib/{{ .ProjectName }}/parser + - src: printer + dst: /usr/lib/{{ .ProjectName }}/parser + - src: scanner + dst: /usr/lib/{{ .ProjectName }}/scanner + - src: token + dst: /usr/lib/{{ .ProjectName }}/token + - src: watcher + dst: /usr/lib/{{ .ProjectName }}/watcher + - src: "x" + dst: /usr/lib/{{ .ProjectName }}/x + # symlinks to binaries + - src: "/usr/lib/{{ .ProjectName }}/bin/gop" + dst: /usr/bin/gop + type: symlink + - src: "/usr/lib/{{ .ProjectName }}/bin/gopfmt" + dst: /usr/bin/gopfmt + type: symlink + +snapcrafts: + - id: gop + name: gop + title: The Go+ Programming Language + summary: The Go+ Programming Language + description: | + The Go+ programming language is designed for engineering, STEM education, and data science. + - For engineering: working in the simplest language that can be mastered by children. + - For STEM education: studying an engineering language that can be used for work in the future. + - For data science: communicating with engineers in the same language. + confinement: classic + license: Apache-2.0 + name_template: >- + {{ .ProjectName }}_v{{.Version}}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + extra_files: + # source folder + - source: LICENSE + destination: LICENSE + - source: "README.md" + destination: README.md + - source: "go.mod" + destination: go.mod + - source: "go.sum" + destination: go.sum + - source: ast + destination: ast + - source: builtin + destination: builtin + - source: cl + destination: cl + - source: cmd + destination: cmd + - source: doc + destination: doc + - source: env + destination: env + - source: format + destination: format + - source: parser + destination: parser + - source: printer + destination: parser + - source: scanner + destination: scanner + - source: token + destination: token + - source: watcher + destination: watcher + - source: "x" + destination: x + apps: + gop: + command: "gop" + aliases: ["gop"] + environment: + GOPROOT: "$SNAP" + gopfmt: + command: "gopfmt" + aliases: ["gopfmt"] + environment: + GOPROOT: "$SNAP" + +checksum: + name_template: "{{ .ProjectName }}_v{{ .Version }}_checksums.txt" diff --git a/README.md b/README.md index 397bc498b..f0f460e8a 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,41 @@ For more details, see [Quick Start](doc/docs.md). ## How to install +### on Windows + +```sh +winget install goplus +``` + +Or + +```sh +winget install goplus.gop +``` + +### on Debian/Ubuntu + +```sh +sudo bash -c ' echo "deb [trusted=yes] https://pkgs.goplus.org/apt/ /" > /etc/apt/sources.list.d/goplus.list' +sudo apt update +sudo apt install gop +``` + +### on RedHat/CentOS/Fedora + +```sh +sudo bash -c 'echo -e "[goplus]\nname=Go+ Repo\nbaseurl=https://pkgs.goplus.org/yum/\nenabled=1\ngpgcheck=0" > /etc/yum.repos.d/goplus.repo' +sudo yum install gop +``` + +### on macOS/Linux(Homebrew) + +Install via [brew](https://brew.sh/) + +```sh +$ brew install goplus +``` + ### from source code For now, we suggest you install Go+ from source code. @@ -63,14 +98,6 @@ cd gop all.bat ``` -### on macOS/Linux - -Install via [brew](https://brew.sh/) -```sh -$ brew install goplus -``` - - ## Go+ Applications ### 2D Games powered by Go+ diff --git a/all.bash b/all.bash index 8fe94a613..fad561e8a 100755 --- a/all.bash +++ b/all.bash @@ -18,4 +18,4 @@ set -ex -go run cmd/make.go --install --autoproxy +go run cmd/make.go --install --regtest --autoproxy diff --git a/all.bat b/all.bat index 5395a855c..c21e69d3d 100644 --- a/all.bat +++ b/all.bat @@ -1 +1 @@ -go run cmd/make.go --install --autoproxy +go run cmd/make.go --install --regtest --autoproxy diff --git a/ast/ast.go b/ast/ast.go index e174dbb19..423e60b99 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -176,13 +176,6 @@ type ( Elt Expr // ellipsis element type (parameter lists only); or nil } - // A BasicLit node represents a literal of basic type. - BasicLit struct { - ValuePos token.Pos // literal position - Kind token.Token // token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING or token.CSTRING - Value string // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 'a', '\x7f', "foo" or `\m\n\o` - } - // A FuncLit node represents a function literal. FuncLit struct { Type *FuncType // function type @@ -1060,6 +1053,7 @@ type ( Body *BlockStmt // function body; or nil for external (non-Go) function Operator bool // is operator or not Shadow bool // is a shadow entry + IsClass bool // recv set by class } ) @@ -1141,7 +1135,7 @@ type File struct { } // There is no entrypoint func to indicate the module entry point. -func (f *File) NoEntrypoint() bool { +func (f *File) HasShadowEntry() bool { return f.ShadowEntry != nil } diff --git a/ast/ast_gop.go b/ast/ast_gop.go index b0c398b63..239a73a80 100644 --- a/ast/ast_gop.go +++ b/ast/ast_gop.go @@ -22,6 +22,69 @@ import ( // ----------------------------------------------------------------------------- +// OverloadFuncDecl node represents an overload function declaration: +// +// `func name = (overloadFuncs)` +// `func (T).nameOrOp = (overloadFuncs)` +// +// here overloadFunc represents +// +// `func(params) {...}` +// `funcName` +// `(*T).methodName` +type OverloadFuncDecl struct { + Doc *CommentGroup // associated documentation; or nil + Func token.Pos // position of "func" keyword + Recv *FieldList // receiver (methods); or nil (functions) + Name *Ident // function/method name + Assign token.Pos // position of token "=" + Lparen token.Pos // position of "(" + Funcs []Expr // overload functions. here `Expr` can be *FuncLit, *Ident or *SelectorExpr + Rparen token.Pos // position of ")" + Operator bool // is operator or not + IsClass bool // recv set by class +} + +// Pos - position of first character belonging to the node. +func (p *OverloadFuncDecl) Pos() token.Pos { + return p.Func +} + +// End - position of first character immediately after the node. +func (p *OverloadFuncDecl) End() token.Pos { + return p.Rparen + 1 +} + +func (*OverloadFuncDecl) declNode() {} + +// ----------------------------------------------------------------------------- + +// A BasicLit node represents a literal of basic type. +type BasicLit struct { + ValuePos token.Pos // literal position + Kind token.Token // token.INT, token.FLOAT, token.IMAG, token.RAT, token.CHAR, token.STRING, token.CSTRING + Value string // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 3r, 'a', '\x7f', "foo" or `\m\n\o` + Extra *StringLitEx // optional (only available when Kind == token.STRING) +} + +type StringLitEx struct { + Parts []any // can be (val string) or (xval Expr) +} + +// NextPartPos - position of first character of next part. +// pos - position of this part (not including quote character). +func NextPartPos(pos token.Pos, part any) (nextPos token.Pos) { + switch v := part.(type) { + case string: // normal string literal or end with "$$" + return pos + token.Pos(len(v)) + case Expr: + return v.End() + } + panic("NextPartPos: unexpected parameters") +} + +// ----------------------------------------------------------------------------- + // A SliceLit node represents a slice literal. type SliceLit struct { Lbrack token.Pos // position of "[" @@ -30,12 +93,12 @@ type SliceLit struct { Incomplete bool // true if (source) expressions are missing in the Elts list } -// Pos - position of first character belonging to the node +// Pos - position of first character belonging to the node. func (p *SliceLit) Pos() token.Pos { return p.Lbrack } -// End - position of first character immediately after the node +// End - position of first character immediately after the node. func (p *SliceLit) End() token.Pos { return p.Rbrack + 1 } @@ -44,7 +107,7 @@ func (*SliceLit) exprNode() {} // ----------------------------------------------------------------------------- -// ErrWrapExpr represents `expr!`, `expr?` or `expr?: defaultValue` +// ErrWrapExpr represents `expr!`, `expr?` or `expr?: defaultValue`. type ErrWrapExpr struct { X Expr Tok token.Token // ! or ? @@ -52,12 +115,12 @@ type ErrWrapExpr struct { Default Expr // can be nil } -// Pos - position of first character belonging to the node +// Pos - position of first character belonging to the node. func (p *ErrWrapExpr) Pos() token.Pos { return p.X.Pos() } -// End - position of first character immediately after the node +// End - position of first character immediately after the node. func (p *ErrWrapExpr) End() token.Pos { if p.Default != nil { return p.Default.End() @@ -69,13 +132,16 @@ func (*ErrWrapExpr) exprNode() {} // ----------------------------------------------------------------------------- -// LambdaExpr represents -// `(x, y, ...) => exprOrExprTuple` -// `x => exprOrExprTuple` -// `=> exprOrExprTuple` +// LambdaExpr represents one of the following expressions: +// +// `(x, y, ...) => exprOrExprTuple` +// `x => exprOrExprTuple` +// `=> exprOrExprTuple` +// // here exprOrExprTuple represents -// `expr` -// `(expr1, expr2, ...)` +// +// `expr` +// `(expr1, expr2, ...)` type LambdaExpr struct { First token.Pos Lhs []*Ident @@ -86,10 +152,11 @@ type LambdaExpr struct { RhsHasParen bool } -// LambdaExpr2 represents -// `(x, y, ...) => { ... }` -// `x => { ... }` -// `=> { ... }` +// LambdaExpr2 represents one of the following expressions: +// +// `(x, y, ...) => { ... }` +// `x => { ... }` +// `=> { ... }` type LambdaExpr2 struct { First token.Pos Lhs []*Ident @@ -98,18 +165,22 @@ type LambdaExpr2 struct { LhsHasParen bool } +// Pos - position of first character belonging to the node. func (p *LambdaExpr) Pos() token.Pos { return p.First } +// End - position of first character immediately after the node. func (p *LambdaExpr) End() token.Pos { return p.Last } +// Pos - position of first character belonging to the node. func (p *LambdaExpr2) Pos() token.Pos { return p.First } +// End - position of first character immediately after the node. func (p *LambdaExpr2) End() token.Pos { return p.Body.End() } @@ -119,7 +190,7 @@ func (*LambdaExpr2) exprNode() {} // ----------------------------------------------------------------------------- -// ForPhrase represents `for k, v <- container, cond` +// ForPhrase represents `for k, v <- container, cond` phrase. type ForPhrase struct { For token.Pos // position of "for" keyword Key, Value *Ident // Key may be nil @@ -138,11 +209,12 @@ func (p *ForPhrase) End() token.Pos { return p.X.End() } func (p *ForPhrase) exprNode() {} -// ComprehensionExpr represents -// `[vexpr for k1, v1 <- container1, cond1 ...]` or -// `{vexpr for k1, v1 <- container1, cond1 ...}` or -// `{kexpr: vexpr for k1, v1 <- container1, cond1 ...}` or -// `{for k1, v1 <- container1, cond1 ...}` or +// ComprehensionExpr represents one of the following expressions: +// +// `[vexpr for k1, v1 <- container1, cond1 ...]` or +// `{vexpr for k1, v1 <- container1, cond1 ...}` or +// `{kexpr: vexpr for k1, v1 <- container1, cond1 ...}` or +// `{for k1, v1 <- container1, cond1 ...}` or type ComprehensionExpr struct { Lpos token.Pos // position of "[" or "{" Tok token.Token // token.LBRACK '[' or token.LBRACE '{' @@ -151,12 +223,12 @@ type ComprehensionExpr struct { Rpos token.Pos // position of "]" or "}" } -// Pos - position of first character belonging to the node +// Pos - position of first character belonging to the node. func (p *ComprehensionExpr) Pos() token.Pos { return p.Lpos } -// End - position of first character immediately after the node +// End - position of first character immediately after the node. func (p *ComprehensionExpr) End() token.Pos { return p.Rpos + 1 } @@ -171,12 +243,12 @@ type ForPhraseStmt struct { Body *BlockStmt } -// Pos - position of first character belonging to the node +// Pos - position of first character belonging to the node. func (p *ForPhraseStmt) Pos() token.Pos { return p.For } -// End - position of first character immediately after the node +// End - position of first character immediately after the node. func (p *ForPhraseStmt) End() token.Pos { return p.Body.End() } @@ -194,7 +266,7 @@ type RangeExpr struct { Expr3 Expr // step (or max) of composite elements; or nil } -// Pos - position of first character belonging to the node +// Pos - position of first character belonging to the node. func (p *RangeExpr) Pos() token.Pos { if p.First != nil { return p.First.Pos() @@ -202,7 +274,7 @@ func (p *RangeExpr) Pos() token.Pos { return p.To } -// End - position of first character immediately after the node +// End - position of first character immediately after the node. func (p *RangeExpr) End() token.Pos { if p.Expr3 != nil { return p.Expr3.End() diff --git a/ast/walk.go b/ast/walk.go index 3cdc64e96..088746110 100644 --- a/ast/walk.go +++ b/ast/walk.go @@ -98,9 +98,18 @@ func Walk(v Visitor, node Node) { } // Expressions - case *BadExpr, *Ident, *BasicLit: + case *BadExpr, *Ident: // nothing to do + case *BasicLit: + if n.Extra != nil { // Go+ extended + for _, part := range n.Extra.Parts { + if e, ok := part.(Expr); ok { + Walk(v, e) + } + } + } + case *Ellipsis: if n.Elt != nil { Walk(v, n.Elt) @@ -364,7 +373,9 @@ func Walk(v Visitor, node Node) { if n.Doc != nil { Walk(v, n.Doc) } - Walk(v, n.Name) + if !n.NoPkgDecl { + Walk(v, n.Name) + } walkDeclList(v, n.Decls) // don't walk n.Comments - they have been // visited already through the individual @@ -375,7 +386,7 @@ func Walk(v Visitor, node Node) { Walk(v, f) } - // Go+ expr and stmt + // Go+ extended expr and stmt case *SliceLit: walkExprList(v, n.Elts) @@ -431,6 +442,16 @@ func Walk(v Visitor, node Node) { Walk(v, n.Default) } + case *OverloadFuncDecl: + if n.Doc != nil { + Walk(v, n.Doc) + } + if n.Recv != nil { + Walk(v, n.Recv) + } + Walk(v, n.Name) + walkExprList(v, n.Funcs) + default: panic(fmt.Sprintf("ast.Walk: unexpected node type %T", n)) } diff --git a/build_install_run.go b/build_install_run.go index 9972ee651..0a665241f 100644 --- a/build_install_run.go +++ b/build_install_run.go @@ -26,6 +26,8 @@ import ( // ----------------------------------------------------------------------------- +// InstallDir installs a Go+ package directory. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err error) { _, _, err = GenGo(dir, conf, false) if err != nil { @@ -34,6 +36,8 @@ func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err err return gocmd.Install(dir, install) } +// InstallPkgPath installs a Go+ package. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func InstallPkgPath(workDir, pkgPath string, conf *Config, install *gocmd.InstallConfig) (err error) { localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, true) if err != nil { @@ -51,6 +55,8 @@ func cwdParam(recursively bool) string { return "." } +// InstallFiles installs specified Go+ files. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func InstallFiles(files []string, conf *Config, install *gocmd.InstallConfig) (err error) { files, err = GenGoFiles("", files, conf) if err != nil { @@ -73,6 +79,8 @@ func chdir(dir string) string { // ----------------------------------------------------------------------------- +// BuildDir builds a Go+ package directory. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) { _, _, err = GenGo(dir, conf, false) if err != nil { @@ -81,6 +89,8 @@ func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) { return gocmd.Build(dir, build) } +// BuildPkgPath builds a Go+ package. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig) (err error) { localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false) if err != nil { @@ -91,6 +101,8 @@ func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfi return gocmd.Build(cwdParam(recursively), build) } +// BuildFiles builds specified Go+ files. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func BuildFiles(files []string, conf *Config, build *gocmd.BuildConfig) (err error) { files, err = GenGoFiles("", files, conf) if err != nil { @@ -116,6 +128,8 @@ func restoreDirAndMod(old string, mod os.FileMode) { // ----------------------------------------------------------------------------- +// RunDir runs an application from a Go+ package directory. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err error) { _, _, err = GenGo(dir, conf, false) if err != nil { @@ -124,6 +138,8 @@ func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err return gocmd.RunDir(dir, args, run) } +// RunPkgPath runs an application from a Go+ package. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *gocmd.RunConfig) (err error) { localDir, recursively, err := GenGoPkgPath("", pkgPath, conf, true) if err != nil { @@ -140,6 +156,8 @@ func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *go return gocmd.RunDir(localDir, args, run) } +// RunFiles runs an application from specified Go+ files. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func RunFiles(autogen string, files []string, args []string, conf *Config, run *gocmd.RunConfig) (err error) { files, err = GenGoFiles(autogen, files, conf) if err != nil { @@ -150,6 +168,8 @@ func RunFiles(autogen string, files []string, args []string, conf *Config, run * // ----------------------------------------------------------------------------- +// TestDir tests a Go+ package directory. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) { _, _, err = GenGo(dir, conf, true) if err != nil { @@ -158,6 +178,8 @@ func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) { return gocmd.Test(dir, test) } +// TestPkgPath tests a Go+ package. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig) (err error) { localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false) if err != nil { @@ -168,6 +190,8 @@ func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig) return gocmd.Test(cwdParam(recursively), test) } +// TestFiles tests specified Go+ files. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func TestFiles(files []string, conf *Config, test *gocmd.TestConfig) (err error) { files, err = GenGoFiles("", files, conf) if err != nil { diff --git a/cl/builtin.go b/cl/builtin.go index 3058e246e..dda8c6d39 100644 --- a/cl/builtin.go +++ b/cl/builtin.go @@ -25,23 +25,22 @@ import ( // ----------------------------------------------------------------------------- -func initMathBig(pkg *gox.Package, conf *gox.Config, big *gox.PkgRef) { - big.EnsureImported() +func initMathBig(pkg *gox.Package, conf *gox.Config, big gox.PkgRef) { conf.UntypedBigInt = big.Ref("UntypedBigint").Type().(*types.Named) conf.UntypedBigRat = big.Ref("UntypedBigrat").Type().(*types.Named) conf.UntypedBigFloat = big.Ref("UntypedBigfloat").Type().(*types.Named) } -func initBuiltinFns(builtin *types.Package, scope *types.Scope, pkg *gox.PkgRef, fns []string) { +func initBuiltinFns(builtin *types.Package, scope *types.Scope, pkg gox.PkgRef, fns []string) { for _, fn := range fns { fnTitle := string(fn[0]-'a'+'A') + fn[1:] scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, fn, pkg.Ref(fnTitle))) } } -func initBuiltin(pkg *gox.Package, builtin *types.Package, os, fmt, ng, iox, buil *gox.PkgRef) { +func initBuiltin(pkg *gox.Package, builtin *types.Package, os, fmt, ng, iox, buil gox.PkgRef) { scope := builtin.Scope() - if ng != nil { + if ng.Types != nil { typs := []string{"bigint", "bigrat", "bigfloat"} for _, typ := range typs { name := string(typ[0]-('a'-'A')) + typ[1:] @@ -50,25 +49,26 @@ func initBuiltin(pkg *gox.Package, builtin *types.Package, os, fmt, ng, iox, bui scope.Insert(types.NewTypeName(token.NoPos, builtin, "uint128", ng.Ref("Uint128").Type())) scope.Insert(types.NewTypeName(token.NoPos, builtin, "int128", ng.Ref("Int128").Type())) } - if fmt != nil { + if fmt.Types != nil { + scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, "echo", fmt.Ref("Println"))) initBuiltinFns(builtin, scope, fmt, []string{ "print", "println", "printf", "errorf", "fprint", "fprintln", "fprintf", "sprint", "sprintln", "sprintf", }) } - if os != nil { + if os.Types != nil { initBuiltinFns(builtin, scope, os, []string{ "open", "create", }) } - if iox != nil { + if iox.Types != nil { initBuiltinFns(builtin, scope, iox, []string{ "lines", }) scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, "blines", iox.Ref("BLines"))) } - if buil != nil { + if buil.Types != nil { scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, "newRange", buil.Ref("NewRange__0"))) } scope.Insert(types.NewTypeName(token.NoPos, builtin, "any", gox.TyEmptyInterface)) @@ -83,7 +83,7 @@ func newBuiltinDefault(pkg *gox.Package, conf *gox.Config) *types.Package { iox := pkg.TryImport("github.com/goplus/gop/builtin/iox") pkg.TryImport("strconv") pkg.TryImport("strings") - if ng != nil { + if ng.Types != nil { initMathBig(pkg, conf, ng) } initBuiltin(pkg, builtin, os, fmt, ng, iox, buil) diff --git a/cl/builtin_test.go b/cl/builtin_test.go index 759150d72..3d03c7ad4 100644 --- a/cl/builtin_test.go +++ b/cl/builtin_test.go @@ -39,8 +39,154 @@ func getGoxConf() *gox.Config { return &gox.Config{Fset: fset, Importer: imp} } +func TestErrStringLit(t *testing.T) { + defer func() { + if e := recover(); e == nil { + t.Fatal("TestErrStringLit: no panic?") + } + }() + compileStringLitEx(nil, nil, &ast.BasicLit{ + Value: "Hello", + Extra: &ast.StringLitEx{ + Parts: []any{1}, + }, + }) +} + +func TestErrPreloadFile(t *testing.T) { + pkg := gox.NewPackage("", "foo", goxConf) + ctx := &blockCtx{pkgCtx: &pkgCtx{}} + t.Run("overloadName", func(t *testing.T) { + defer func() { + if e := recover(); e == nil || e != "TODO - can't overload operator ++\n" { + t.Fatal("TestErrPreloadFile:", e) + } + }() + overloadName(&ast.Ident{}, "++", true) + }) + t.Run("checkOverloadFunc", func(t *testing.T) { + defer func() { + if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: checkOverloadFunc\n" { + t.Fatal("TestErrPreloadFile:", e) + } + }() + checkOverloadFunc(&ast.OverloadFuncDecl{ + Recv: &ast.FieldList{}, + }) + }) + t.Run("checkOverloadMethod", func(t *testing.T) { + defer func() { + if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: checkOverloadMethod\n" { + t.Fatal("TestErrPreloadFile:", e) + } + }() + checkOverloadMethod(&ast.OverloadFuncDecl{}) + }) + t.Run("checkOverloadMethodRecvType1", func(t *testing.T) { + defer func() { + if e := recover(); e == nil || e != "TODO - checkOverloadMethodRecvType: bar\n" { + t.Fatal("TestErrPreloadFile:", e) + } + }() + checkOverloadMethodRecvType(&ast.Ident{Name: "foo"}, &ast.Ident{Name: "bar"}) + }) + t.Run("checkOverloadMethodRecvType2", func(t *testing.T) { + defer func() { + if e := recover(); e == nil || e != "TODO - checkOverloadMethodRecvType: &{0 INT 123 }\n" { + t.Fatal("TestErrPreloadFile:", e) + } + }() + expr := &ast.BasicLit{Kind: token.INT, Value: "123"} + checkOverloadMethodRecvType(&ast.Ident{Name: "foo"}, expr) + }) + t.Run("OverloadFuncDecl: invalid recv", func(t *testing.T) { + defer func() { + if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: invalid recv\n" { + t.Fatal("TestErrPreloadFile:", e) + } + }() + decls := []ast.Decl{ + &ast.OverloadFuncDecl{ + Name: &ast.Ident{Name: "add"}, + Funcs: []ast.Expr{ + &ast.FuncLit{}, + }, + Recv: &ast.FieldList{List: []*ast.Field{ + {Type: &ast.StarExpr{}}, + }}, + }, + } + preloadFile(pkg, ctx, "foo.gop", &ast.File{Decls: decls}, "", true) + }) + t.Run("OverloadFuncDecl: unknown func", func(t *testing.T) { + defer func() { + if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: unknown func - *ast.BasicLit\n" { + t.Fatal("TestErrPreloadFile:", e) + } + }() + decls := []ast.Decl{ + &ast.OverloadFuncDecl{ + Name: &ast.Ident{Name: "add"}, + Funcs: []ast.Expr{ + &ast.BasicLit{}, + }, + Operator: true, + }, + } + preloadFile(pkg, ctx, "foo.gop", &ast.File{Decls: decls}, "", true) + }) + t.Run("unknown decl", func(t *testing.T) { + defer func() { + if e := recover(); e == nil || e != "TODO - cl.preloadFile: unknown decl - *ast.BadDecl\n" { + t.Fatal("TestErrPreloadFile:", e) + } + }() + decls := []ast.Decl{ + &ast.BadDecl{}, + } + preloadFile(pkg, ctx, "foo.gop", &ast.File{Decls: decls}, "", true) + }) +} + +func TestErrParseTypeEmbedName(t *testing.T) { + defer func() { + if e := recover(); e == nil { + t.Fatal("TestErrParseTypeEmbedName: no panic?") + } + }() + parseTypeEmbedName(&ast.StructType{}) +} + +func TestGmxMainFunc(t *testing.T) { + gmxMainFunc(nil, &pkgCtx{ + projs: map[string]*gmxProject{ + ".a": {}, ".b": {}, + }, + }, false) +} + +func TestNodeInterp(t *testing.T) { + ni := &nodeInterp{} + if v := ni.Caller(&ast.Ident{}); v != "the function call" { + t.Fatal("TestNodeInterp:", v) + } +} + +func TestMarkAutogen(t *testing.T) { + old := noMarkAutogen + noMarkAutogen = false + + NewPackage("", &ast.Package{Files: map[string]*ast.File{ + "main.t2gmx": {IsProj: true}, + }}, &Config{ + LookupClass: lookupClassErr, + }) + + noMarkAutogen = old +} + func TestClassNameAndExt(t *testing.T) { - name, ext := classNameAndExt("/foo/bar.abc_yap.gox") + name, ext := ClassNameAndExt("/foo/bar.abc_yap.gox") if name != "bar" || ext != "_yap.gox" { t.Fatal("classNameAndExt:", name, ext) } @@ -172,7 +318,7 @@ func TestClRangeStmt(t *testing.T) { // ----------------------------------------------------------------------------- func TestGetStringConst(t *testing.T) { - spx := &gox.PkgRef{Types: types.NewPackage("", "foo")} + spx := gox.PkgRef{Types: types.NewPackage("", "foo")} if v := getStringConst(spx, "unknown"); v != "" { t.Fatal("getStringConst:", v) } @@ -184,7 +330,7 @@ func TestSpxRef(t *testing.T) { t.Fatal("TestSpxRef:", e) } }() - pkg := &gox.PkgRef{ + pkg := gox.PkgRef{ Types: types.NewPackage("foo", "foo"), } spxRef(pkg, "bar") @@ -202,20 +348,25 @@ func isError(e interface{}, msg string) bool { return false } -func TestGmxSettings(t *testing.T) { +func TestGmxProject(t *testing.T) { pkg := gox.NewPackage("", "foo", goxConf) - gmx := newGmx(nil, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{ + ctx := &pkgCtx{ + projs: make(map[string]*gmxProject), + classes: make(map[*ast.File]gmxClass), + } + gmx := loadClass(ctx, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{ LookupClass: lookupClass, }) scheds := gmx.getScheds(pkg.CB()) if len(scheds) != 2 || scheds[0] == nil || scheds[0] != scheds[1] { - t.Fatal("TestGmxSettings failed") + t.Fatal("TestGmxProject failed") } gmx.hasScheds = false if gmx.getScheds(nil) != nil { - t.Fatal("TestGmxSettings failed: hasScheds?") + t.Fatal("TestGmxProject failed: hasScheds?") } - _, err := NewPackage("", &ast.Package{Files: map[string]*ast.File{ + + /* _, err := NewPackage("", &ast.Package{Files: map[string]*ast.File{ "main.t2gmx": { IsProj: true, }, @@ -224,7 +375,28 @@ func TestGmxSettings(t *testing.T) { }) if e := err.Error(); e != `github.com/goplus/gop/cl/internal/libc.Game not found` { t.Fatal("newGmx:", e) - } + } */ + + func() { + defer func() { + if e := recover(); e != "TODO: class not found" { + t.Fatal("TestGmxProject failed:", e) + } + }() + loadClass(nil, pkg, "main.abcx", &ast.File{IsProj: true}, &Config{ + LookupClass: lookupClass, + }) + }() + func() { + defer func() { + if e := recover(); e != "multiple project files found: Game Game\n" { + t.Fatal("TestGmxProject failed:", e) + } + }() + loadClass(ctx, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{ + LookupClass: lookupClass, + }) + }() } func TestSpxLookup(t *testing.T) { @@ -259,10 +431,13 @@ func lookupClassErr(ext string) (c *modfile.Project, ok bool) { } func TestGetGoFile(t *testing.T) { - if f := genGoFile("a_test.gop", true); f != testingGoFile { + if f := genGoFile("a_test.gop", false); f != testingGoFile { + t.Fatal("TestGetGoFile:", f) + } + if f := genGoFile("a_test.gox", true); f != testingGoFile { t.Fatal("TestGetGoFile:", f) } - if f := genGoFile("a_test.gop", false); f != skippingGoFile { + if f := genGoFile("a.gop", false); f != defaultGoFile { t.Fatal("TestGetGoFile:", f) } } diff --git a/cl/c.go b/cl/c.go index b203bbb21..bf0d15aec 100644 --- a/cl/c.go +++ b/cl/c.go @@ -57,12 +57,12 @@ func c2goBase(base string) string { // ----------------------------------------------------------------------------- -func loadC2goPkg(ctx *blockCtx, realPath string, src *ast.BasicLit) *gox.PkgRef { +func loadC2goPkg(ctx *blockCtx, realPath string, src *ast.BasicLit) (ret gox.PkgRef) { cpkg, err := ctx.cpkgs.Import(realPath) if err != nil { ctx.handleErrorf(src.Pos(), "%v not found or not a valid C package (c2go.a.pub file not found).\n", realPath) - return nil + return } ctx.clookups = append(ctx.clookups, cpkg) return cpkg.Pkg() diff --git a/cl/classfile.go b/cl/classfile.go index e3490163b..d019783ef 100644 --- a/cl/classfile.go +++ b/cl/classfile.go @@ -20,6 +20,7 @@ import ( goast "go/ast" "go/constant" "go/types" + "log" "path/filepath" "strings" @@ -31,19 +32,27 @@ import ( // ----------------------------------------------------------------------------- -type gmxSettings struct { - gameClass string - game gox.Ref - sprite map[string]gox.Ref +type gmxClass struct { + tname string // class type + ext string + proj *gmxProject +} + +type gmxProject struct { + gameClass string // .gmx + game gox.Ref // Game + sprite map[string]gox.Ref // .spx => Sprite + sptypes []string // .spx scheds []string schedStmts []goast.Stmt // nil or len(scheds) == 2 (delayload) - pkgImps []*gox.PkgRef + pkgImps []gox.PkgRef pkgPaths []string hasScheds bool gameIsPtr bool + isTest bool } -func (p *gmxSettings) getScheds(cb *gox.CodeBuilder) []goast.Stmt { +func (p *gmxProject) getScheds(cb *gox.CodeBuilder) []goast.Stmt { if p == nil || !p.hasScheds { return nil } @@ -60,7 +69,7 @@ func (p *gmxSettings) getScheds(cb *gox.CodeBuilder) []goast.Stmt { return p.schedStmts } -func classNameAndExt(file string) (name, ext string) { +func ClassNameAndExt(file string) (name, ext string) { fname := filepath.Base(file) name, ext = modfile.SplitFname(fname) if idx := strings.Index(name, "."); idx > 0 { @@ -69,45 +78,59 @@ func classNameAndExt(file string) (name, ext string) { return } -func newGmx(ctx *pkgCtx, pkg *gox.Package, file string, f *ast.File, conf *Config) *gmxSettings { - fname := filepath.Base(file) - ext := modfile.ClassExt(fname) +func isGoxTestFile(ext string) bool { + return strings.HasSuffix(ext, "test.gox") +} + +func loadClass(ctx *pkgCtx, pkg *gox.Package, file string, f *ast.File, conf *Config) *gmxProject { + tname, ext := ClassNameAndExt(file) gt, ok := conf.LookupClass(ext) if !ok { panic("TODO: class not found") } - var name string - if f.IsProj { - _, name = filepath.Split(file) - if idx := strings.Index(name, "."); idx > 0 { - name = name[:idx] - if name == "main" { - name = gt.Class - } + p, ok := ctx.projs[gt.Ext] + if !ok { + pkgPaths := gt.PkgPaths + p = &gmxProject{pkgPaths: pkgPaths} + ctx.projs[gt.Ext] = p + + p.pkgImps = make([]gox.PkgRef, len(pkgPaths)) + for i, pkgPath := range pkgPaths { + p.pkgImps[i] = pkg.Import(pkgPath) + } + + spx := p.pkgImps[0] + if gt.Class != "" { + p.game, p.gameIsPtr = spxRef(spx, gt.Class) + } + p.sprite = make(map[string]types.Object) + for _, v := range gt.Works { + obj, _ := spxRef(spx, v.Class) + p.sprite[v.Ext] = obj + } + if x := getStringConst(spx, "Gop_sched"); x != "" { + p.scheds, p.hasScheds = strings.SplitN(x, ",", 2), true } } - pkgPaths := gt.PkgPaths - p := &gmxSettings{gameClass: name, pkgPaths: pkgPaths} - p.pkgImps = make([]*gox.PkgRef, len(pkgPaths)) - for i, pkgPath := range pkgPaths { - p.pkgImps[i] = pkg.Import(pkgPath) - } - spx := p.pkgImps[0] - if gt.Class != "" { - p.game, p.gameIsPtr = spxRef(spx, gt.Class) - } - p.sprite = make(map[string]types.Object) - for _, v := range gt.Works { - obj, _ := spxRef(spx, v.Class) - p.sprite[v.Ext] = obj + if f.IsProj { + if tname == "main" { + tname = gt.Class + } + if p.gameClass != "" { + log.Panicln("multiple project files found:", tname, p.gameClass) + } + p.gameClass = tname + } else { + p.sptypes = append(p.sptypes, tname) } - if x := getStringConst(spx, "Gop_sched"); x != "" { - p.scheds, p.hasScheds = strings.SplitN(x, ",", 2), true + ctx.classes[f] = gmxClass{tname, ext, p} + if debugLoad { + log.Println("==> InitClass", tname, "isProj:", f.IsProj) } return p } -func spxLookup(pkgImps []*gox.PkgRef, name string) gox.Ref { +func spxLookup(pkgImps []gox.PkgRef, name string) gox.Ref { for _, pkg := range pkgImps { if o := pkg.TryRef(name); o != nil { return o @@ -116,7 +139,7 @@ func spxLookup(pkgImps []*gox.PkgRef, name string) gox.Ref { panic("spxLookup: symbol not found - " + name) } -func spxTryRef(spx *gox.PkgRef, typ string) (obj types.Object, isPtr bool) { +func spxTryRef(spx gox.PkgRef, typ string) (obj types.Object, isPtr bool) { if strings.HasPrefix(typ, "*") { typ, isPtr = typ[1:], true } @@ -124,7 +147,7 @@ func spxTryRef(spx *gox.PkgRef, typ string) (obj types.Object, isPtr bool) { return } -func spxRef(spx *gox.PkgRef, typ string) (obj gox.Ref, isPtr bool) { +func spxRef(spx gox.PkgRef, typ string) (obj gox.Ref, isPtr bool) { obj, isPtr = spxTryRef(spx, typ) if obj == nil { panic(spx.Path() + "." + typ + " not found") @@ -132,7 +155,7 @@ func spxRef(spx *gox.PkgRef, typ string) (obj gox.Ref, isPtr bool) { return } -func getStringConst(spx *gox.PkgRef, name string) string { +func getStringConst(spx gox.PkgRef, name string) string { if o := spx.TryRef(name); o != nil { if c, ok := o.(*types.Const); ok { return constant.StringVal(c.Val()) @@ -155,8 +178,8 @@ func getFields(f *ast.File) []ast.Spec { } func setBodyHandler(ctx *blockCtx) { - if ctx.isClass { // in a Go+ class file - if scheds := ctx.getScheds(ctx.cb); scheds != nil { + if proj := ctx.proj; proj != nil { // in a Go+ class file + if scheds := proj.getScheds(ctx.cb); scheds != nil { ctx.cb.SetBodyHandler(func(body *goast.BlockStmt, kind int) { idx := 0 if len(body.List) == 0 { @@ -168,14 +191,105 @@ func setBodyHandler(ctx *blockCtx) { } } -func gmxMainFunc(p *gox.Package, ctx *pkgCtx) { - if o := p.Types.Scope().Lookup(ctx.gameClass); o != nil && hasMethod(o, "MainEntry") { - // new(Game).Main() - p.NewFunc(nil, "main", nil, nil, false).BodyStart(p). - Val(p.Builtin().Ref("new")).Val(o).Call(1). - MemberVal("Main").Call(0).EndStmt(). - End() +const ( + casePrefix = "case" +) + +func testNameSuffix(testType string) string { + if c := testType[0]; c >= 'A' && c <= 'Z' { + return testType + } + return "_" + testType +} + +func gmxTestFunc(pkg *gox.Package, testType string, isProj bool) { + if isProj { + genTestFunc(pkg, "TestMain", testType, "m", "M") + } else { + name := testNameSuffix(testType) + genTestFunc(pkg, "Test"+name, casePrefix+name, "t", "T") + } +} + +func genTestFunc(pkg *gox.Package, name, testType, param, paramType string) { + testing := pkg.Import("testing") + objT := testing.Ref(paramType) + paramT := types.NewParam(token.NoPos, pkg.Types, param, types.NewPointer(objT.Type())) + params := types.NewTuple(paramT) + + pkg.NewFunc(nil, name, params, nil, false).BodyStart(pkg). + Val(pkg.Builtin().Ref("new")).Val(pkg.Ref(testType)).Call(1). + MemberVal("TestMain").Val(paramT).Call(1).EndStmt(). + End() +} + +func gmxMainFunc(pkg *gox.Package, ctx *pkgCtx, noAutoGenMain bool) func() { + var proj *gmxProject + for _, v := range ctx.projs { + if v.isTest { + continue + } else if proj != nil { + return nil + } + proj = v + } + if proj != nil { // only one project file + scope := pkg.Types.Scope() + var o types.Object + if proj.gameClass != "" { + o = scope.Lookup(proj.gameClass) + if noAutoGenMain && o != nil && hasMethod(o, "MainEntry") { + noAutoGenMain = false + } + } else { + o = proj.game + } + if !noAutoGenMain && o != nil { + // new(Game).Main() + // new(Game).Main(workers...) + fn := pkg.NewFunc(nil, "main", nil, nil, false) + return func() { + new := pkg.Builtin().Ref("new") + cb := fn.BodyStart(pkg).Val(new).Val(o).Call(1).MemberVal("Main") + + sig := cb.Get(-1).Type.(*types.Signature) + narg := gmxMainNarg(sig) + if narg > 0 { + narg = len(proj.sptypes) + for _, spt := range proj.sptypes { + sp := scope.Lookup(spt) + cb.Val(new).Val(sp).Call(1) + } + } + + cb.Call(narg).EndStmt().End() + } + } + } + return nil +} + +func gmxMainNarg(sig *types.Signature) int { + if fex, ok := gox.CheckFuncEx(sig); ok { + if trm, ok := fex.(*gox.TyTemplateRecvMethod); ok { + sig = trm.Func.Type().(*types.Signature) + return sig.Params().Len() - 1 + } + } + return sig.Params().Len() +} + +func hasMethod(o types.Object, name string) bool { + if obj, ok := o.(*types.TypeName); ok { + if t, ok := obj.Type().(*types.Named); ok { + for i, n := 0, t.NumMethods(); i < n; i++ { + if t.Method(i).Name() == name { + return true + } + } + } } + return false } // ----------------------------------------------------------------------------- diff --git a/cl/compile.go b/cl/compile.go index d11931456..507612745 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -24,6 +24,7 @@ import ( "log" "reflect" "sort" + "strconv" "strings" _ "unsafe" @@ -41,6 +42,7 @@ type dbgFlags int const ( DbgFlagLoad dbgFlags = 1 << iota DbgFlagLookup + FlagNoMarkAutogen DbgFlagAll = DbgFlagLoad | DbgFlagLookup ) @@ -49,8 +51,9 @@ var ( ) var ( - debugLoad bool - debugLookup bool + debugLoad bool + debugLookup bool + noMarkAutogen bool // add const _ = true ) func SetDisableRecover(disableRecover bool) { @@ -60,6 +63,7 @@ func SetDisableRecover(disableRecover bool) { func SetDebug(flags dbgFlags) { debugLoad = (flags & DbgFlagLoad) != 0 debugLookup = (flags & DbgFlagLookup) != 0 + noMarkAutogen = (flags & FlagNoMarkAutogen) != 0 } // ----------------------------------------------------------------------------- @@ -172,6 +176,9 @@ type Config struct { // Fset provides source position information for syntax trees and types (required). Fset *token.FileSet + // Context represents all things between packages (optional). + Context *gox.Context + // RelativeBase is the root directory of relative path. RelativeBase string @@ -187,6 +194,9 @@ type Config struct { // See (*github.com/goplus/mod/gopmod.Module).LookupClass. LookupClass func(ext string) (c *Project, ok bool) + // IsPkgtStandard checks a pkgPath is a Go standard package or not. + IsPkgtStandard func(pkgPath string) bool + // An Importer resolves import paths to Packages (optional). Importer types.Importer @@ -330,13 +340,15 @@ func doInitMethods(ld *typeLoader) { type pkgCtx struct { *nodeInterp - *gmxSettings - fset *token.FileSet - cpkgs *cpackages.Importer - syms map[string]loader - inits []func() - tylds []*typeLoader - errs errors.List + projs map[string]*gmxProject // .gmx => project + classes map[*ast.File]gmxClass + fset *token.FileSet + cpkgs *cpackages.Importer + syms map[string]loader + lbinames []any // names that should load before initGopPkg (can be string/func or *ast.Ident/type) + inits []func() + tylds []*typeLoader + errs errors.List generics map[string]bool // generic type record idents []*ast.Ident // toType ident recored @@ -344,17 +356,18 @@ type pkgCtx struct { } type pkgImp struct { - *gox.PkgRef + gox.PkgRef pkgName *types.PkgName } type blockCtx struct { *pkgCtx + proj *gmxProject pkg *gox.Package cb *gox.CodeBuilder imports map[string]pkgImp - lookups []*gox.PkgRef - clookups []*cpackages.PkgRef + lookups []gox.PkgRef + clookups []cpackages.PkgRef tlookup *typeParamLookup c2goBase string // default is `github.com/goplus/` relBaseDir string @@ -464,12 +477,18 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package, fset: fset, files: files, relBaseDir: relBaseDir, } ctx := &pkgCtx{ - fset: fset, - syms: make(map[string]loader), nodeInterp: interp, generics: make(map[string]bool), + fset: fset, + nodeInterp: interp, + projs: make(map[string]*gmxProject), + classes: make(map[*ast.File]gmxClass), + syms: make(map[string]loader), + generics: make(map[string]bool), } confGox := &gox.Config{ Types: conf.Types, Fset: fset, + Context: conf.Context, + IsPkgtStandard: conf.IsPkgtStandard, Importer: conf.Importer, LoadNamed: ctx.loadNamed, HandleErr: ctx.handleErr, @@ -494,40 +513,40 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package, }() } p = gox.NewPackage(pkgPath, pkg.Name, confGox) + + if !noMarkAutogen { + p.CB().NewConstStart(nil, "_").Val(true).EndInit(1) + } + ctx.cpkgs = cpackages.NewImporter(&cpackages.Config{ Pkg: p, LookupPub: conf.LookupPub, }) + for file, gmx := range files { - if gmx.IsProj { - ctx.gmxSettings = newGmx(ctx, p, file, gmx, conf) - break - } - } - if ctx.gmxSettings == nil { - for file, gmx := range files { - if gmx.IsClass && !gmx.IsNormalGox { - ctx.gmxSettings = newGmx(ctx, p, file, gmx, conf) - break + if gmx.IsClass && !gmx.IsNormalGox { + if debugLoad { + log.Println("==> File", file, "normalGox:", gmx.IsNormalGox) } + loadClass(ctx, p, file, gmx, conf) } } - gofiles := make([]*ast.File, 0, len(pkg.GoFiles)) - for fpath, gof := range pkg.GoFiles { - f := fromgo.ASTFile(gof, 0) - gofiles = append(gofiles, f) - ctx := &blockCtx{ - pkg: p, pkgCtx: ctx, cb: p.CB(), relBaseDir: relBaseDir, - imports: make(map[string]pkgImp), - } - preloadFile(p, ctx, fpath, f, false, false) + // sort files + type File struct { + *ast.File + path string } - - initGopPkg(ctx, p) - + sfiles := make([]*File, 0, len(files)) for fpath, f := range files { + sfiles = append(sfiles, &File{f, fpath}) + } + sort.Slice(sfiles, func(i, j int) bool { + return sfiles[i].path < sfiles[j].path + }) + + for _, f := range sfiles { fileLine := !conf.NoFileLine - fileScope := types.NewScope(p.Types.Scope(), f.Pos(), f.End(), fpath) + fileScope := types.NewScope(p.Types.Scope(), f.Pos(), f.End(), f.path) ctx := &blockCtx{ pkg: p, pkgCtx: ctx, cb: p.CB(), relBaseDir: relBaseDir, fileScope: fileScope, fileLine: fileLine, isClass: f.IsClass, rec: rec, @@ -536,31 +555,46 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package, if rec := ctx.rec; rec != nil { rec.Scope(f, fileScope) } - preloadGopFile(p, ctx, fpath, f, conf) + preloadGopFile(p, ctx, f.path, f.File, conf) } - // sort files - type File struct { - *ast.File - path string + gopSyms := make(map[string]bool) // TODO: remove this map + for name := range ctx.syms { + gopSyms[name] = true } - sfiles := make([]*File, 0, len(files)) - for fpath, f := range files { - sfiles = append(sfiles, &File{f, fpath}) + + gofiles := make([]*ast.File, 0, len(pkg.GoFiles)) + for fpath, gof := range pkg.GoFiles { + f := fromgo.ASTFile(gof, 0) + gofiles = append(gofiles, f) + ctx := &blockCtx{ + pkg: p, pkgCtx: ctx, cb: p.CB(), relBaseDir: relBaseDir, + imports: make(map[string]pkgImp), + } + preloadFile(p, ctx, fpath, f, skippingGoFile, false) + } + + initGopPkg(ctx, p, gopSyms) + + // genMain = true if it is main package and no main func + var genMain bool + var gen func() + if pkg.Name == "main" { + _, hasMain := ctx.syms["main"] + genMain = !hasMain } - sort.Slice(sfiles, func(i, j int) bool { - return sfiles[i].path < sfiles[j].path - }) for _, f := range sfiles { if f.IsProj { loadFile(ctx, f.File) - gmxMainFunc(p, ctx) - break } } + if genMain { // make classfile main func if need + gen = gmxMainFunc(p, ctx, conf.NoAutoGenMain) + } + for _, f := range sfiles { - if !f.IsProj { // only one .gmx file + if !f.IsProj { loadFile(ctx, f.File) } } @@ -577,12 +611,12 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package, } err = ctx.complete() - if !conf.NoAutoGenMain && pkg.Name == "main" { - if obj := p.Types.Scope().Lookup("main"); obj == nil { - old, _ := p.SetCurFile(defaultGoFile, false) - p.NewFunc(nil, "main", nil, nil, false).BodyStart(p).End() - p.RestoreCurFile(old) - } + if gen != nil { // generate classfile main func + gen() + } else if genMain && !conf.NoAutoGenMain { // generate empty main func + old, _ := p.SetCurFile(defaultGoFile, false) + p.NewFunc(nil, "main", nil, nil, false).BodyStart(p).End() + p.RestoreCurFile(old) } return } @@ -599,33 +633,30 @@ func isOverloadFunc(name string) bool { //go:linkname initThisGopPkg github.com/goplus/gox.initThisGopPkg func initThisGopPkg(pkg *types.Package) -func initGopPkg(ctx *pkgCtx, pkg *gox.Package) { +func initGopPkg(ctx *pkgCtx, pkg *gox.Package, gopSyms map[string]bool) { for name, f := range ctx.syms { + if gopSyms[name] { + continue + } if _, ok := f.(*typeLoader); ok { ctx.loadType(name) } else if isOverloadFunc(name) { ctx.loadSymbol(name) } } + for _, lbi := range ctx.lbinames { + if name, ok := lbi.(string); ok { + ctx.loadSymbol(name) + } else { + ctx.loadType(lbi.(*ast.Ident).Name) + } + } if pkg.Types.Scope().Lookup(gopPackage) == nil { pkg.Types.Scope().Insert(types.NewConst(token.NoPos, pkg.Types, gopPackage, types.Typ[types.UntypedBool], constant.MakeBool(true))) } initThisGopPkg(pkg.Types) } -func hasMethod(o types.Object, name string) bool { - if obj, ok := o.(*types.TypeName); ok { - if t, ok := obj.Type().(*types.Named); ok { - for i, n := 0, t.NumMethods(); i < n; i++ { - if t.Method(i).Name() == name { - return true - } - } - } - } - return false -} - func getEntrypoint(f *ast.File) string { switch { case f.IsProj: @@ -642,17 +673,6 @@ func getEntrypoint(f *ast.File) string { func loadFile(ctx *pkgCtx, f *ast.File) { for _, decl := range f.Decls { switch d := decl.(type) { - case *ast.FuncDecl: - if d.Recv == nil { - name := d.Name.Name - if name != "init" { - ctx.loadSymbol(name) - } - } else { - if name, ok := getRecvTypeName(ctx, d.Recv, false); ok { - getTypeLoader(ctx, ctx.syms, token.NoPos, name).load() - } - } case *ast.GenDecl: switch d.Tok { case token.TYPE: @@ -666,51 +686,78 @@ func loadFile(ctx *pkgCtx, f *ast.File) { } } } + case *ast.FuncDecl: + if d.Recv == nil { + name := d.Name.Name + if name != "init" { + ctx.loadSymbol(name) + } + } else { + if name, ok := getRecvTypeName(ctx, d.Recv, false); ok { + getTypeLoader(ctx, ctx.syms, token.NoPos, name).load() + } + } } } } -func genGoFile(file string, gopFile bool) string { - if gopFile { - if strings.HasSuffix(file, "_test.gop") { - return testingGoFile - } - return defaultGoFile +// gen testingGoFile for: +// +// *_test.gop +// *test.gox +func genGoFile(file string, goxTestFile bool) string { + if goxTestFile || strings.HasSuffix(file, "_test.gop") { + return testingGoFile } - return skippingGoFile + return defaultGoFile } func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, conf *Config) { - var parent = ctx.pkgCtx + var proj *gmxProject var classType string + var testType string var baseTypeName string var baseType types.Type var spxClass bool - switch { - case f.IsProj: - classType = parent.gameClass - o := parent.game - baseTypeName, baseType = o.Name(), o.Type() - if parent.gameIsPtr { - baseType = types.NewPointer(baseType) - } - case f.IsClass: - var classExt string - classType, classExt = classNameAndExt(file) - if parent.gmxSettings != nil { - o, ok := parent.sprite[classExt] - if ok { + var goxTestFile bool + var parent = ctx.pkgCtx + if f.IsClass { + if f.IsNormalGox { + classType, _ = ClassNameAndExt(file) + if classType == "main" { + classType = "_main" + } + } else { + c := parent.classes[f] + proj, ctx.proj = c.proj, c.proj + classType = c.tname + if isGoxTestFile(c.ext) { // test classfile + testType = c.tname + goxTestFile, proj.isTest = true, true + if !f.IsProj { + classType = casePrefix + testNameSuffix(testType) + } + } + if f.IsProj { + o := proj.game + baseTypeName, baseType = o.Name(), o.Type() + if proj.gameIsPtr { + baseType = types.NewPointer(baseType) + } + } else { + o := proj.sprite[c.ext] baseTypeName, baseType, spxClass = o.Name(), o.Type(), true } } } + goFile := genGoFile(file, goxTestFile) if classType != "" { if debugLoad { log.Println("==> Preload type", classType) } - if parent.gmxSettings != nil { - ctx.lookups = make([]*gox.PkgRef, len(parent.pkgPaths)) - for i, pkgPath := range parent.pkgPaths { + if proj != nil { + ctx.lookups = make([]gox.PkgRef, len(proj.pkgPaths)) + for i, pkgPath := range proj.pkgPaths { ctx.lookups[i] = p.Import(pkgPath) } } @@ -722,22 +769,28 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con if debugLoad { log.Println("==> Load > NewType", classType) } - decl := p.NewType(classType) + old, _ := p.SetCurFile(goFile, true) + defer p.RestoreCurFile(old) + + decl := p.NewTypeDefs().NewType(classType) ld.typInit = func() { // decycle if debugLoad { log.Println("==> Load > InitType", classType) } + old, _ := p.SetCurFile(goFile, true) + defer p.RestoreCurFile(old) + pkg := p.Types var flds []*types.Var var tags []string chk := newCheckRedecl() - if len(baseTypeName) != 0 { + if baseTypeName != "" { flds = append(flds, types.NewField(pos, pkg, baseTypeName, baseType, true)) tags = append(tags, "") chk.chkRedecl(ctx, baseTypeName, pos) } - if spxClass && parent.gmxSettings != nil && parent.gameClass != "" { - typ := toType(ctx, &ast.StarExpr{X: &ast.Ident{Name: parent.gameClass}}) + if spxClass && proj.gameClass != "" { + typ := toType(ctx, &ast.StarExpr{X: &ast.Ident{Name: proj.gameClass}}) name := getTypeName(typ) if !chk.chkRedecl(ctx, name, pos) { fld := types.NewField(pos, pkg, name, typ, true) @@ -788,9 +841,10 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con }, }}} } - // check class project no MainEntry and auto added - if f.IsProj && !conf.NoAutoGenMain && !f.NoEntrypoint() && f.Name.Name == "main" { - entry := getEntrypoint(f) + if d := f.ShadowEntry; d != nil { + d.Name.Name = getEntrypoint(f) + } else if f.IsProj && !conf.NoAutoGenMain && f.Name.Name == "main" { + var entry = getEntrypoint(f) var hasEntry bool for _, decl := range f.Decls { switch d := decl.(type) { @@ -802,16 +856,25 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con } if !hasEntry { f.Decls = append(f.Decls, &ast.FuncDecl{ - Name: ast.NewIdent(entry), - Type: &ast.FuncType{Params: &ast.FieldList{}}, - Body: &ast.BlockStmt{}, + Name: &ast.Ident{ + Name: entry, + }, + Type: &ast.FuncType{ + Params: &ast.FieldList{}, + }, + Body: &ast.BlockStmt{}, + Shadow: true, }) } } - if d := f.ShadowEntry; d != nil { - d.Name.Name = getEntrypoint(f) + preloadFile(p, ctx, file, f, goFile, !conf.Outline) + if goxTestFile { + parent.inits = append(parent.inits, func() { + old, _ := p.SetCurFile(testingGoFile, true) + gmxTestFunc(p, testType, f.IsProj) + p.RestoreCurFile(old) + }) } - preloadFile(p, ctx, file, f, true, !conf.Outline) } func parseTypeEmbedName(typ ast.Expr) *ast.Ident { @@ -828,60 +891,84 @@ retry: panic("TODO: parseTypeEmbedName unexpected") } -func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFile, genFnBody bool) { +func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, goFile string, genFnBody bool) { parent := ctx.pkgCtx syms := parent.syms - goFile := genGoFile(file, gopFile) old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) var skipClassFields bool if f.IsClass { skipClassFields = true } - for _, decl := range f.Decls { - switch d := decl.(type) { - case *ast.FuncDecl: - if ctx.classRecv != nil { // in class file (.spx/.gmx) - if d.Recv == nil { - d.Recv = ctx.classRecv + + preloadFuncDecl := func(d *ast.FuncDecl) { + if ctx.classRecv != nil { // in class file (.spx/.gmx) + if d.Recv == nil { + d.Recv = ctx.classRecv + d.IsClass = true + } + } + if d.Recv == nil { + name := d.Name + fn := func() { + old, _ := p.SetCurFile(goFile, true) + defer p.RestoreCurFile(old) + loadFunc(ctx, nil, d, genFnBody) + } + if name.Name == "init" { + if genFnBody { + if debugLoad { + log.Println("==> Preload func init") + } + parent.inits = append(parent.inits, fn) + } + } else { + if debugLoad { + log.Println("==> Preload func", name.Name) } + initLoader(parent, syms, name.Pos(), name.Name, fn, genFnBody) } - if d.Recv == nil { - name := d.Name + } else { + if name, ok := getRecvTypeName(parent, d.Recv, true); ok { + if debugLoad { + log.Printf("==> Preload method %s.%s\n", name, d.Name.Name) + } + ld := getTypeLoader(parent, syms, token.NoPos, name) fn := func() { old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) - loadFunc(ctx, nil, d, genFnBody) - } - if name.Name == "init" { - if genFnBody { - if debugLoad { - log.Println("==> Preload func init") - } - parent.inits = append(parent.inits, fn) - } - } else { - if debugLoad { - log.Println("==> Preload func", name.Name) - } - initLoader(parent, syms, name.Pos(), name.Name, fn, genFnBody) + doInitType(ld) + recv := toRecv(ctx, d.Recv) + loadFunc(ctx, recv, d, genFnBody) } - } else { - if name, ok := getRecvTypeName(parent, d.Recv, true); ok { - if debugLoad { - log.Printf("==> Preload method %s.%s\n", name, d.Name.Name) - } - ld := getTypeLoader(parent, syms, token.NoPos, name) - fn := func() { - old, _ := p.SetCurFile(goFile, true) - defer p.RestoreCurFile(old) - doInitType(ld) - recv := toRecv(ctx, d.Recv) - loadFunc(ctx, recv, d, genFnBody) + ld.methods = append(ld.methods, fn) + } + } + } + + preloadConst := func(d *ast.GenDecl) { + pkg := ctx.pkg + cdecl := pkg.NewConstDefs(pkg.Types.Scope()) + for _, spec := range d.Specs { + vSpec := spec.(*ast.ValueSpec) + if debugLoad { + log.Println("==> Preload const", vSpec.Names) + } + setNamesLoader(parent, syms, vSpec.Names, func() { + if c := cdecl; c != nil { + cdecl = nil + loadConstSpecs(ctx, c, d.Specs) + for _, s := range d.Specs { + v := s.(*ast.ValueSpec) + removeNames(syms, v.Names) } - ld.methods = append(ld.methods, fn) } - } + }) + } + } + + for _, decl := range f.Decls { + switch d := decl.(type) { case *ast.GenDecl: switch d.Tok { case token.IMPORT: @@ -899,7 +986,7 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil pos := tName.Pos() ld := getTypeLoader(parent, syms, pos, name) defs := ctx.pkg.NewTypeDefs() - if gopFile { + if goFile != skippingGoFile { // is Go+ file ld.typ = func() { old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) @@ -955,24 +1042,7 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil } } case token.CONST: - pkg := ctx.pkg - cdecl := pkg.NewConstDefs(pkg.Types.Scope()) - for _, spec := range d.Specs { - vSpec := spec.(*ast.ValueSpec) - if debugLoad { - log.Println("==> Preload const", vSpec.Names) - } - setNamesLoader(parent, syms, vSpec.Names, func() { - if c := cdecl; c != nil { - cdecl = nil - loadConstSpecs(ctx, c, d.Specs) - for _, s := range d.Specs { - v := s.(*ast.ValueSpec) - removeNames(syms, v.Names) - } - } - }) - } + preloadConst(d) case token.VAR: if skipClassFields { skipClassFields = false @@ -996,10 +1066,120 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil default: log.Panicln("TODO - tok:", d.Tok, "spec:", reflect.TypeOf(d.Specs).Elem()) } + + case *ast.FuncDecl: + preloadFuncDecl(d) + + case *ast.OverloadFuncDecl: + var recv *ast.Ident + if d.Recv != nil { + otyp, ok := d.Recv.List[0].Type.(*ast.Ident) + if !ok { + log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: invalid recv") + } + recv = otyp + } + onames := make([]string, 0, 4) + exov := false + name := d.Name + for idx, fn := range d.Funcs { + switch expr := fn.(type) { + case *ast.Ident: + checkOverloadFunc(d) + onames = append(onames, expr.Name) + ctx.lbinames = append(ctx.lbinames, expr.Name) + exov = true + case *ast.SelectorExpr: + checkOverloadMethod(d) + checkOverloadMethodRecvType(recv, expr.X) + onames = append(onames, "."+expr.Sel.Name) + ctx.lbinames = append(ctx.lbinames, recv) + exov = true + case *ast.FuncLit: + checkOverloadFunc(d) + name1 := overloadFuncName(name.Name, idx) + ctx.lbinames = append(ctx.lbinames, name1) + preloadFuncDecl(&ast.FuncDecl{ + Doc: d.Doc, + Name: &ast.Ident{NamePos: name.NamePos, Name: name1}, + Type: expr.Type, + Body: expr.Body, + }) + default: + log.Panicf("TODO - cl.preloadFile OverloadFuncDecl: unknown func - %T\n", expr) + } + } + if exov { // need Gopo_xxx + oname := overloadName(recv, name.Name, d.Operator) + oval := strings.Join(onames, ",") + preloadConst(&ast.GenDecl{ + Doc: d.Doc, + Tok: token.CONST, + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: []*ast.Ident{{Name: oname}}, + Values: []ast.Expr{stringLit(oval)}, + }, + }, + }) + ctx.lbinames = append(ctx.lbinames, oname) + } + default: - log.Panicln("TODO - gopkg.Package.load: unknown decl -", reflect.TypeOf(decl)) + log.Panicf("TODO - cl.preloadFile: unknown decl - %T\n", decl) + } + } +} + +func checkOverloadFunc(d *ast.OverloadFuncDecl) { + if d.Recv != nil && !d.Operator { + log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: checkOverloadFunc") + } +} + +func checkOverloadMethod(d *ast.OverloadFuncDecl) { + if d.Recv == nil { + log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: checkOverloadMethod") + } +} + +func checkOverloadMethodRecvType(ot *ast.Ident, recv ast.Expr) { + rtyp, _ := getRecvType(recv) + rt, ok := rtyp.(*ast.Ident) + if !ok || ot.Name != rt.Name { + log.Panicln("TODO - checkOverloadMethodRecvType:", recv) + } +} + +const ( + indexTable = "0123456789abcdefghijklmnopqrstuvwxyz" +) + +func overloadFuncName(name string, idx int) string { + return name + "__" + indexTable[idx:idx+1] +} + +func overloadName(recv *ast.Ident, name string, isOp bool) string { + if isOp { + if oname, ok := binaryGopNames[name]; ok { + name = oname + } else { + log.Panicln("TODO - can't overload operator", name) } } + sep := "_" + if strings.ContainsRune(name, '_') || (recv != nil && strings.ContainsRune(recv.Name, '_')) { + sep = "__" + } + typ := "" + if recv != nil { + typ = recv.Name + sep + } + return "Gopo" + sep + typ + name +} + +func stringLit(val string) *ast.BasicLit { + return &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(val)} } func newType(pkg *types.Package, pos token.Pos, name string) *types.Named { @@ -1059,8 +1239,11 @@ func loadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool) { if genBody { if body := d.Body; body != nil { if recv != nil { + file := pkg.CurFile() ctx.inits = append(ctx.inits, func() { // interface issue: #795 + old := pkg.RestoreCurFile(file) loadFuncBody(ctx, fn, body, d) + pkg.RestoreCurFile(old) }) } else { loadFuncBody(ctx, fn, body, d) @@ -1140,13 +1323,13 @@ func loadImport(ctx *blockCtx, spec *ast.ImportSpec) { } }() } - var pkg *gox.PkgRef + var pkg gox.PkgRef var pkgPath = toString(spec.Path) if realPath, kind := checkC2go(pkgPath); kind != c2goInvalid { if kind == c2goStandard { realPath = ctx.c2goBase + realPath } - if pkg = loadC2goPkg(ctx, realPath, spec.Path); pkg == nil { + if pkg = loadC2goPkg(ctx, realPath, spec.Path); pkg.Types == nil { return } } else { @@ -1176,7 +1359,7 @@ func loadImport(ctx *blockCtx, spec *ast.ImportSpec) { return } if name == "_" { - pkg.MarkForceUsed() + pkg.MarkForceUsed(ctx.pkg) return } } diff --git a/cl/compile_gop_test.go b/cl/compile_gop_test.go new file mode 100644 index 000000000..e3ff38b4f --- /dev/null +++ b/cl/compile_gop_test.go @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cl_test + +import ( + "testing" +) + +func TestOverloadOp(t *testing.T) { + gopClTest(t, ` +type foo struct { +} + +func (a *foo) + (b *foo) *foo { + println("a + b") + return &foo{} +} + +func (a foo) - (b foo) foo { + println("a - b") + return foo{} +} + +func -(a foo) { + println("-a") +} + +func ++(a foo) { + println("a++") +} + +func (a foo) != (b foo) bool{ + println("a!=b") + return true +} + +var a, b foo +var c = a - b +var d = -a // TODO: -a have no return value! +var e = a!=b +`, `package main + +import "fmt" + +type foo struct { +} + +func (a *foo) Gop_Add(b *foo) *foo { + fmt.Println("a + b") + return &foo{} +} +func (a foo) Gop_Sub(b foo) foo { + fmt.Println("a - b") + return foo{} +} +func (a foo) Gop_NE(b foo) bool { + fmt.Println("a!=b") + return true +} +func (a foo) Gop_Neg() { + fmt.Println("-a") +} +func (a foo) Gop_Inc() { + fmt.Println("a++") +} + +var a, b foo +var c = (foo).Gop_Sub(a, b) +var d = a.Gop_Neg() +var e = (foo).Gop_NE(a, b) +`) +} + +func TestOverloadOp2(t *testing.T) { + gopClTest(t, ` +type foo struct { +} + +func (a foo) mulInt(b int) (ret foo) { + return +} + +func (a foo) mulFoo(b foo) (ret foo) { + return +} + +func intMulFoo(a int, b foo) (ret foo) { + return +} + +func (foo).* = ( + (foo).mulInt + (foo).mulFoo + intMulFoo +) + +var a, b foo + +println a * 10 +println a * b +println 10 * a +`, `package main + +import "fmt" + +type foo struct { +} + +const Gopo__foo__Gop_Mul = ".mulInt,.mulFoo,intMulFoo" + +func (a foo) mulInt(b int) (ret foo) { + return +} +func (a foo) mulFoo(b foo) (ret foo) { + return +} +func intMulFoo(a int, b foo) (ret foo) { + return +} + +var a, b foo + +func main() { + fmt.Println((foo).mulInt(a, 10)) + fmt.Println((foo).mulFoo(a, b)) + fmt.Println(intMulFoo(10, a)) +} +`) +} + +func TestOverloadMethod(t *testing.T) { + gopClTest(t, ` +type foo struct { +} + +func (a *foo) mulInt(b int) *foo { + println "mulInt" + return a +} + +func (a *foo) mulFoo(b *foo) *foo { + println "mulFoo" + return a +} + +func (foo).mul = ( + (foo).mulInt + (foo).mulFoo +) + +var a, b foo +var c = a.mul(100) +var d = a.mul(c) +`, `package main + +import "fmt" + +type foo struct { +} + +const Gopo_foo_mul = ".mulInt,.mulFoo" + +func (a *foo) mulInt(b int) *foo { + fmt.Println("mulInt") + return a +} +func (a *foo) mulFoo(b *foo) *foo { + fmt.Println("mulFoo") + return a +} + +var a, b foo +var c = a.mulInt(100) +var d = a.mulFoo(c) +`) +} + +func TestOverloadFunc(t *testing.T) { + gopClTest(t, ` +func add = ( + func(a, b int) int { + return a + b + } + func(a, b string) string { + return a + b + } +) + +println add(100, 7) +println add("Hello", "World") +`, `package main + +import "fmt" + +func add__0(a int, b int) int { + return a + b +} +func add__1(a string, b string) string { + return a + b +} +func main() { + fmt.Println(add__0(100, 7)) + fmt.Println(add__1("Hello", "World")) +} +`) +} + +func TestOverloadFunc2(t *testing.T) { + gopClTest(t, ` +func mulInt(a, b int) int { + return a * b +} + +func mulFloat(a, b float64) float64 { + return a * b +} + +func mul = ( + mulInt + mulFloat +) + +println mul(100, 7) +println mul(1.2, 3.14) +`, `package main + +import "fmt" + +const Gopo_mul = "mulInt,mulFloat" + +func mulInt(a int, b int) int { + return a * b +} +func mulFloat(a float64, b float64) float64 { + return a * b +} +func main() { + fmt.Println(mulInt(100, 7)) + fmt.Println(mulFloat(1.2, 3.14)) +} +`) +} + +func TestStringLitBasic(t *testing.T) { + gopClTest(t, `echo "$$"`, `package main + +import "fmt" + +func main() { + fmt.Println("$") +} +`) +} + +func TestStringLitVar(t *testing.T) { + gopClTest(t, ` +x := 1 +println "Hi, " + "a${x}b"`, `package main + +import ( + "fmt" + "strconv" +) + +func main() { + x := 1 + fmt.Println("Hi, " + ("a" + strconv.Itoa(x) + "b")) +} +`) +} + +func TestFileOpen(t *testing.T) { + gopClTest(t, ` +for line <- open("foo.txt")! { + println line +} +`, `package main + +import ( + "fmt" + "github.com/goplus/gop/builtin/iox" + "github.com/qiniu/x/errors" + "os" +) + +func main() { + for _gop_it := iox.EnumLines(func() (_gop_ret *os.File) { + var _gop_err error + _gop_ret, _gop_err = os.Open("foo.txt") + if _gop_err != nil { + _gop_err = errors.NewFrame(_gop_err, "open(\"foo.txt\")", "/foo/bar.gop", 2, "main.main") + panic(_gop_err) + } + return + }()); ; { + var _gop_ok bool + line, _gop_ok := _gop_it.Next() + if !_gop_ok { + break + } + fmt.Println(line) + } +} +`) +} + +func TestFileEnumLines(t *testing.T) { + gopClTest(t, ` +import "os" + +for line <- os.Stdin { + println line +} +`, `package main + +import ( + "fmt" + "github.com/goplus/gop/builtin/iox" + "os" +) + +func main() { + for _gop_it := iox.EnumLines(os.Stdin); ; { + var _gop_ok bool + line, _gop_ok := _gop_it.Next() + if !_gop_ok { + break + } + fmt.Println(line) + } +} +`) +} + +func TestIoxLines(t *testing.T) { + gopClTest(t, ` +import "io" + +var r io.Reader + +for line <- lines(r) { + println line +} +`, `package main + +import ( + "fmt" + "github.com/goplus/gop/builtin/iox" + "io" +) + +var r io.Reader + +func main() { + for _gop_it := iox.Lines(r).Gop_Enum(); ; { + var _gop_ok bool + line, _gop_ok := _gop_it.Next() + if !_gop_ok { + break + } + fmt.Println(line) + } +} +`) +} + +func TestMixedGo(t *testing.T) { + gopMixedClTest(t, "main", `package main + +import "strconv" + +const n = 10 + +func f(v int) string { + return strconv.Itoa(v) +} + +type foo struct { + v int +} + +func (a foo) _() { +} + +func (a foo) Str() string { + return f(a.v) +} + +func (a *foo) Bar() int { + return 0 +} + +type foo2 = foo +type foo3 foo2 +`, ` +var a [n]int +var b string = f(n) +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) + gopMixedClTest(t, "main", `package main +type Point struct { + X int + Y int +} +`, ` +type T struct{} +println(&T{},&Point{10,20}) +`, `package main + +import "fmt" + +type T struct { +} + +func main() { + fmt.Println(&T{}, &Point{10, 20}) +} +`, false) +} + +func Test_RangeExpressionIf_Issue1243(t *testing.T) { + gopClTest(t, ` +for i <- :10, i%3 == 0 { + println i +}`, `package main + +import "fmt" + +func main() { + for i := 0; i < 10; i += 1 { + if i%3 == 0 { + fmt.Println(i) + } + } +} +`) +} diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go index 314fdf2e9..03f289e47 100644 --- a/cl/compile_spx_test.go +++ b/cl/compile_spx_test.go @@ -19,7 +19,6 @@ package cl_test import ( "bytes" "os" - "path" "testing" "github.com/goplus/gop/cl" @@ -47,6 +46,16 @@ func lookupClass(ext string) (c *modfile.Project, ok bool) { Works: []*modfile.Class{{Ext: "_t3spx.gox", Class: "Sprite"}, {Ext: ".t3spx2", Class: "Sprite2"}}, PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true + case "_spx.gox": + return &modfile.Project{ + Ext: "_spx.gox", Class: "Game", + Works: []*modfile.Class{{Ext: "_spx.gox", Class: "Sprite"}}, + PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx3", "math"}}, true + case "_xtest.gox": + return &modfile.Project{ + Ext: "_xtest.gox", Class: "App", + Works: []*modfile.Class{{Ext: "_xtest.gox", Class: "Case"}}, + PkgPaths: []string{"github.com/goplus/gop/test", "testing"}}, true } return } @@ -54,10 +63,10 @@ func lookupClass(ext string) (c *modfile.Project, ok bool) { func spxParserConf() parser.Config { return parser.Config{ ClassKind: func(fname string) (isProj bool, ok bool) { - ext := path.Ext(fname) + ext := modfile.ClassExt(fname) c, ok := lookupClass(ext) if ok { - isProj = (c.Ext == ext) + isProj = c.IsProj(ext, fname) } return }, @@ -69,10 +78,14 @@ func gopSpxTest(t *testing.T, gmx, spxcode, expected string) { } func gopSpxTestEx(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile string) { - gopSpxTestExConf(t, "gopSpxTest", gblConf, gmx, spxcode, expected, gmxfile, spxfile) + gopSpxTestExConf(t, "gopSpxTest", gblConf, gmx, spxcode, expected, gmxfile, spxfile, "") } -func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode, expected, gmxfile, spxfile string) { +func gopSpxTestEx2(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) { + gopSpxTestExConf(t, "gopSpxTest", gblConf, gmx, spxcode, expected, gmxfile, spxfile, resultFile) +} + +func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) { t.Run(name, func(t *testing.T) { cl.SetDisableRecover(true) defer cl.SetDisableRecover(false) @@ -89,7 +102,7 @@ func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode, t.Fatal("NewPackage:", err) } var b bytes.Buffer - err = pkg.WriteTo(&b) + err = pkg.WriteTo(&b, resultFile) if err != nil { t.Fatal("gox.WriteTo failed:", err) } @@ -369,6 +382,43 @@ func (this *Kai) Main() { `, "index.tgmx", "Kai.tspx") } +func TestSpxRunWithWorkers(t *testing.T) { + gopSpxTestEx(t, ` +var ( + Kai Kai +) + +run +`, ` +echo "Hi" +`, `package main + +import ( + "fmt" + "github.com/goplus/gop/cl/internal/spx3" +) + +type Kai struct { + spx3.Sprite + *Game +} +type Game struct { + spx3.Game + Kai Kai +} + +func (this *Game) MainEntry() { + this.Run() +} +func main() { + spx3.Gopt_Game_Main(new(Game), new(Kai)) +} +func (this *Kai) Main() { + fmt.Println("Hi") +} +`, "main_spx.gox", "Kai_spx.gox") +} + func TestSpx2(t *testing.T) { gopSpxTestEx(t, ` println("Hi") @@ -401,7 +451,9 @@ type Kai struct { func (this *Kai) onMsg(msg string) { } `, "Game.t2gmx", "Kai.t2spx") +} +func TestSpx3(t *testing.T) { gopSpxTestEx(t, ` println("Hi, Sprite2") `, ` @@ -433,7 +485,9 @@ type Kai struct { func (this *Kai) onMsg(msg string) { } `, "Game.t2gmx", "Kai.t2spx2") +} +func TestSpx4(t *testing.T) { gopSpxTestEx(t, ` println("Hi, Sprite") `, ` @@ -483,7 +537,7 @@ func (this *Game) MainEntry() { func main() { new(Game).Main() } -`, "Game.t2gmx", "Kai.t2spx") +`, "Game.t2gmx", "Kai.t2spx", "") gopSpxTestExConf(t, "OnlyGmx", &conf, ` var ( Kai Kai @@ -507,7 +561,7 @@ func (this *Game) MainEntry() { func main() { new(Game).Main() } -`, "Game.t2gmx", "Kai.t2spx") +`, "Game.t2gmx", "Kai.t2spx", "") gopSpxTestExConf(t, "KaiAndGmx", &conf, ` var ( @@ -549,7 +603,7 @@ func (this *Kai) Main() { } func (this *Kai) onMsg(msg string) { } -`, "Game.t2gmx", "Kai.t2spx") +`, "Game.t2gmx", "Kai.t2spx", "") } func TestSpxGoxBasic(t *testing.T) { @@ -892,3 +946,72 @@ func (this *Kai) Main() { } `, "Game.tgmx", "Kai.tspx") } + +func TestTestClassFile(t *testing.T) { + gopSpxTestEx2(t, ` +println "Hi" +`, ` +t.log "Hi" +t.run "a test", t => { + t.fatal "failed" +} +`, `package main + +import ( + "fmt" + "github.com/goplus/gop/test" + "testing" +) + +type App struct { + test.App +} + +func (this *App) MainEntry() { + fmt.Println("Hi") +} + +type caseFoo struct { + test.Case + *App +} + +func (this *caseFoo) Main() { + this.T().Log("Hi") + this.T().Run("a test", func(t *testing.T) { + t.Fatal("failed") + }) +} +func TestFoo(t *testing.T) { + test.Gopt_Case_TestMain(new(caseFoo), t) +} +func TestMain(m *testing.M) { + test.Gopt_App_TestMain(new(App), m) +} +`, "main_xtest.gox", "Foo_xtest.gox", "_test") +} + +func TestTestClassFile2(t *testing.T) { + gopSpxTestEx2(t, ` +println "Hi" +`, ` +t.log "Hi" +`, `package main + +import ( + "github.com/goplus/gop/test" + "testing" +) + +type case_foo struct { + test.Case +} + +func (this *case_foo) Main() { + this.T().Log("Hi") +} +func Test_foo(t *testing.T) { + test.Gopt_Case_TestMain(new(case_foo), t) +} +`, "main.gox", "foo_xtest.gox", "_test") +} diff --git a/cl/compile_test.go b/cl/compile_test.go index 8220c1264..8ddf572a2 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -45,7 +45,7 @@ var ( func init() { gox.SetDebug(gox.DbgFlagAll) - cl.SetDebug(cl.DbgFlagAll) + cl.SetDebug(cl.DbgFlagAll | cl.FlagNoMarkAutogen) gblFset = token.NewFileSet() imp := gop.NewImporter(nil, &env.Gop{Root: gopRootDir, Version: "1.0"}, gblFset) gblConf = &cl.Config{ @@ -156,184 +156,6 @@ ls `) } -func TestFileOpen(t *testing.T) { - gopClTest(t, ` -for line <- open("foo.txt")! { - println line -} -`, `package main - -import ( - "fmt" - "os" - "github.com/goplus/gop/builtin/iox" - "github.com/qiniu/x/errors" -) - -func main() { - for _gop_it := iox.EnumLines(func() (_gop_ret *os.File) { - var _gop_err error - _gop_ret, _gop_err = os.Open("foo.txt") - if _gop_err != nil { - _gop_err = errors.NewFrame(_gop_err, "open(\"foo.txt\")", "/foo/bar.gop", 2, "main.main") - panic(_gop_err) - } - return - }()); ; { - var _gop_ok bool - line, _gop_ok := _gop_it.Next() - if !_gop_ok { - break - } - fmt.Println(line) - } -} -`) -} - -func TestFileEnumLines(t *testing.T) { - gopClTest(t, ` -import "os" - -for line <- os.Stdin { - println line -} -`, `package main - -import ( - "fmt" - "os" - "github.com/goplus/gop/builtin/iox" -) - -func main() { - for _gop_it := iox.EnumLines(os.Stdin); ; { - var _gop_ok bool - line, _gop_ok := _gop_it.Next() - if !_gop_ok { - break - } - fmt.Println(line) - } -} -`) -} - -func TestIoxLines(t *testing.T) { - gopClTest(t, ` -import "io" - -var r io.Reader - -for line <- lines(r) { - println line -} -`, `package main - -import ( - "fmt" - "github.com/goplus/gop/builtin/iox" - "io" -) - -var r io.Reader - -func main() { - for _gop_it := iox.Lines(r).Gop_Enum(); ; { - var _gop_ok bool - line, _gop_ok := _gop_it.Next() - if !_gop_ok { - break - } - fmt.Println(line) - } -} -`) -} - -func TestMixedGo(t *testing.T) { - gopMixedClTest(t, "main", `package main - -import "strconv" - -const n = 10 - -func f(v int) string { - return strconv.Itoa(v) -} - -type foo struct { - v int -} - -func (a foo) _() { -} - -func (a foo) Str() string { - return f(a.v) -} - -func (a *foo) Bar() int { - return 0 -} - -type foo2 = foo -type foo3 foo2 -`, ` -var a [n]int -var b string = f(n) -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) - gopMixedClTest(t, "main", `package main -type Point struct { - X int - Y int -} -`, ` -type T struct{} -println(&T{},&Point{10,20}) -`, `package main - -import "fmt" - -type T struct { -} - -func main() { - fmt.Println(&T{}, &Point{10, 20}) -} -`, false) -} - -func Test_RangeExpressionIf_Issue1243(t *testing.T) { - gopClTest(t, ` -for i <- :10, i%3 == 0 { - println i -}`, `package main - -import "fmt" - -func main() { - for i := 0; i < 10; i += 1 { - if i%3 == 0 { - fmt.Println(i) - } - } -} -`) -} - func Test_CastSlice_Issue1240(t *testing.T) { gopClTest(t, ` type fvec []float64 @@ -1474,9 +1296,9 @@ func foo(script string) { import ( "fmt" + "github.com/goplus/gop/ast/gopq" "github.com/goplus/gop/ast/goptest" "github.com/qiniu/x/errors" - "github.com/goplus/gop/ast/gopq" ) func foo(script string) { @@ -1508,9 +1330,9 @@ func foo(script string) { import ( "fmt" + "github.com/goplus/gop/ast/gopq" "github.com/goplus/gop/ast/goptest" "github.com/qiniu/x/errors" - "github.com/goplus/gop/ast/gopq" ) func foo(script string) { @@ -1539,8 +1361,8 @@ func add(x, y string) (int, error) { `, `package main import ( - "strconv" "github.com/qiniu/x/errors" + "strconv" ) func add(x string, y string) (int, error) { @@ -1713,7 +1535,7 @@ var z uint128 = x + y import "github.com/goplus/gop/builtin/ng" var x, y ng.Uint128 -var z ng.Uint128 = x.Gop_Add__1(y) +var z ng.Uint128 = (ng.Uint128).Gop_Add__1(x, y) `) } @@ -1726,7 +1548,7 @@ var z int128 = x + y import "github.com/goplus/gop/builtin/ng" var x, y ng.Int128 -var z ng.Int128 = x.Gop_Add__1(y) +var z ng.Int128 = (ng.Int128).Gop_Add__1(x, y) `) } @@ -1739,7 +1561,7 @@ var z bigint = x + y import "github.com/goplus/gop/builtin/ng" var x, y ng.Bigint -var z ng.Bigint = x.Gop_Add(y) +var z ng.Bigint = (ng.Bigint).Gop_Add(x, y) `) } @@ -1821,8 +1643,8 @@ import ( ) var x = ng.Bigrat_Init__2(big.NewRat(7, 2)) -var y = x.Gop_Add(ng.Bigrat_Init__0(100)) -var z = ng.Bigrat_Init__0(100) + y +var y = (ng.Bigrat).Gop_Add(x, ng.Bigrat_Init__0(100)) +var z = (ng.Bigrat).Gop_Add(ng.Bigrat_Init__0(100), y) `) } @@ -2936,71 +2758,6 @@ func (M) Bar() { `) } -func TestOverloadOp(t *testing.T) { - gopClTest(t, ` -type foo struct { -} - -func (a *foo) + (b *foo) *foo { - println("a + b") - return &foo{} -} - -func (a foo) - (b foo) foo { - println("a - b") - return foo{} -} - -func -(a foo) { - println("-a") -} - -func ++(a foo) { - println("a++") -} - -func (a foo) != (b foo) bool{ - println("a!=b") - return true -} - -var a, b foo -var c = a - b -var d = -a // TODO: -a have no return value! -var e = a!=b -`, `package main - -import "fmt" - -type foo struct { -} - -func (a *foo) Gop_Add(b *foo) *foo { - fmt.Println("a + b") - return &foo{} -} -func (a foo) Gop_Sub(b foo) foo { - fmt.Println("a - b") - return foo{} -} -func (a foo) Gop_NE(b foo) bool { - fmt.Println("a!=b") - return true -} -func (a foo) Gop_Neg() { - fmt.Println("-a") -} -func (a foo) Gop_Inc() { - fmt.Println("a++") -} - -var a, b foo -var c = a.Gop_Sub(b) -var d = a.Gop_Neg() -var e = a.Gop_NE(b) -`) -} - func TestCmdlineNoEOL(t *testing.T) { gopClTest(t, `println "Hi"`, `package main @@ -4939,9 +4696,9 @@ var e = a!=b `, `package main var a, b foo -var c = a.Gop_Sub(b) +var c = (foo).Gop_Sub(a, b) var d = a.Gop_Neg() -var e = a.Gop_NE(b) +var e = (foo).Gop_NE(a, b) `) } @@ -5000,11 +4757,11 @@ var b int var c float64 func main() { - _ = a.Gop_Add__0(b) - _ = a.Gop_Add__0(100) - _ = a.Gop_Add__1(c) - _ = Vector3_Init__0(100) + a - _ = Vector3_Cast__0(b).Gop_Add__2(a) + _ = (Vector3).Gop_Add__0(a, b) + _ = (Vector3).Gop_Add__0(a, 100) + _ = (Vector3).Gop_Add__1(a, c) + _ = (Vector3).Gop_Add__2(Vector3_Init__0(100), a) + _ = (Vector3).Gop_Add__2(Vector3_Cast__0(b), a) _ = b + a.Gop_Rcast__0() a.Gop_AddAssign(Vector3_Init__0(b)) a.Gop_AddAssign(Vector3_Init__1(c)) @@ -5112,3 +4869,70 @@ func main() { } `) } + +func TestOverloadNamed(t *testing.T) { + gopClTest(t, ` +import "github.com/goplus/gop/cl/internal/overload/bar" + +var a bar.Var[int] +var b bar.Var[bar.M] +c := bar.Var(string) +d := bar.Var(bar.M) +`, `package main + +import "github.com/goplus/gop/cl/internal/overload/bar" + +var a bar.Var__0[int] +var b bar.Var__1[map[string]any] + +func main() { + c := bar.Gopx_Var_Cast__0[string]() + d := bar.Gopx_Var_Cast__1[map[string]any]() +} +`) +} + +func TestMixedOverloadNamed(t *testing.T) { + gopMixedClTest(t, "main", `package main + +const GopPackage = true + +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]) +} +`, ` +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 { + }]() +} +`) +} diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go index fe2bf1df5..4a4e1a589 100644 --- a/cl/error_msg_test.go +++ b/cl/error_msg_test.go @@ -23,6 +23,7 @@ import ( "runtime" "testing" + "github.com/goplus/gop/ast" "github.com/goplus/gop/cl" "github.com/goplus/gop/parser" "github.com/goplus/gop/parser/fsx/memfs" @@ -53,6 +54,24 @@ func codeErrorTestEx(t *testing.T, pkgname, filename, msg, src string) { } } +func codeErrorTestAst(t *testing.T, pkgname, filename, msg, src string) { + f, _ := parser.ParseFile(gblFset, filename, src, parser.AllErrors) + pkg := &ast.Package{ + Name: pkgname, + Files: map[string]*ast.File{filename: f}, + } + conf := *gblConf + conf.NoFileLine = false + conf.RelativeBase = "/foo" + _, err := cl.NewPackage("", pkg, &conf) + if err == nil { + t.Fatal("no error?") + } + if ret := err.Error(); ret != msg { + t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg) + } +} + func TestErrLambdaExpr(t *testing.T) { codeErrorTest(t, "bar.gop:7:6: too few arguments in lambda expression\n\thave ()\n\twant (int, int)", ` @@ -969,3 +988,10 @@ func TestErrCompileFunc(t *testing.T) { printf("%+v\n", int32) `) } + +func TestToTypeError(t *testing.T) { + codeErrorTestAst(t, "main", "bar.gop", `bar.gop:3:3: toType unexpected: *ast.BadExpr`, ` +type +a := 1 +`) +} diff --git a/cl/expr.go b/cl/expr.go index 15356c2ef..fb6cc143b 100644 --- a/cl/expr.go +++ b/cl/expr.go @@ -74,7 +74,7 @@ const ( const errorPkgPath = "github.com/qiniu/x/errors" -func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg *gox.PkgRef, kind int) { +func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg gox.PkgRef, kind int) { fvalue := (flags&clIdentSelectorExpr) != 0 || (flags&clIdentLHS) == 0 name := ident.Name if name == "_" { @@ -122,7 +122,8 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg *gox.PkgRef, // pkgRef object if (flags & clIdentSelectorExpr) != 0 { if name == "C" && len(ctx.clookups) > 0 { - return nil, objCPkgRef + kind = objCPkgRef + return } if pi, ok := ctx.findImport(name); ok { if rec := ctx.recorder(); rec != nil { @@ -133,7 +134,7 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg *gox.PkgRef, } // object from import . "xxx" - if compilePkgRef(ctx, nil, ident, flags, objPkgRef) { + if compilePkgRef(ctx, gox.PkgRef{}, ident, flags, objPkgRef) { return } @@ -389,7 +390,7 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, flags int) { } } -func pkgRef(at *gox.PkgRef, name string) (o types.Object, alias bool) { +func pkgRef(at gox.PkgRef, name string) (o types.Object, alias bool) { if c := name[0]; c >= 'a' && c <= 'z' { name = string(rune(c)+('A'-'a')) + name[1:] if v := at.TryRef(name); v != nil && gox.IsFunc(v.Type()) { @@ -400,8 +401,9 @@ func pkgRef(at *gox.PkgRef, name string) (o types.Object, alias bool) { return at.TryRef(name), false } -func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident, pkgKind int) (o types.Object, alias bool) { - if pkg != nil { +// allow pkg.Types to be nil +func lookupPkgRef(ctx *blockCtx, pkg gox.PkgRef, x *ast.Ident, pkgKind int) (o types.Object, alias bool) { + if pkg.Types != nil { return pkgRef(pkg, x.Name) } if pkgKind == objPkgRef { @@ -416,7 +418,7 @@ func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident, pkgKind int) (o } } } else { - var cpkg *cpackages.PkgRef + var cpkg cpackages.PkgRef for _, at := range ctx.clookups { if o2 := at.Lookup(x.Name); o2 != nil { if o != nil { @@ -431,7 +433,8 @@ func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident, pkgKind int) (o return } -func compilePkgRef(ctx *blockCtx, at *gox.PkgRef, x *ast.Ident, flags, pkgKind int) bool { +// allow at.Types to be nil +func compilePkgRef(ctx *blockCtx, at gox.PkgRef, x *ast.Ident, flags, pkgKind int) bool { if v, alias := lookupPkgRef(ctx, at, x, pkgKind); v != nil { cb := ctx.cb if (flags & clIdentLHS) != 0 { @@ -778,7 +781,46 @@ func compileBasicLit(ctx *blockCtx, v *ast.BasicLit) { } cb.Val(rune(0)).ArrayLit(typ, n+1).UnaryOp(gotoken.AND).Call(1).Call(1) default: - cb.Val(&goast.BasicLit{Kind: gotoken.Token(v.Kind), Value: v.Value}, v) + if v.Extra == nil { + basicLit(cb, v) + return + } + compileStringLitEx(ctx, cb, v) + } +} + +func basicLit(cb *gox.CodeBuilder, v *ast.BasicLit) { + cb.Val(&goast.BasicLit{Kind: gotoken.Token(v.Kind), Value: v.Value}, v) +} + +func compileStringLitEx(ctx *blockCtx, cb *gox.CodeBuilder, lit *ast.BasicLit) { + pos := lit.ValuePos + 1 + quote := lit.Value[:1] + notFirst := false + for _, part := range lit.Extra.Parts { + switch v := part.(type) { + case string: // normal string literal or end with "$$" + next := pos + token.Pos(len(v)) + if strings.HasSuffix(v, "$$") { + v = v[:len(v)-1] + } + basicLit(cb, &ast.BasicLit{ValuePos: pos - 1, Value: quote + v + quote, Kind: token.STRING}) + pos = next + case ast.Expr: + compileExpr(ctx, v) + t := cb.Get(-1).Type + if t.Underlying() != types.Typ[types.String] { + cb.Member("string", gox.MemberFlagAutoProperty) + } + pos = v.End() + default: + panic("compileStringLitEx TODO: unexpected part") + } + if notFirst { + cb.BinaryOp(gotoken.ADD) + } else { + notFirst = true + } } } @@ -1133,7 +1175,7 @@ func compileErrWrapExpr(ctx *blockCtx, v *ast.ErrWrapExpr, inFlags int) { retName = "_gop_ret" + strconv.Itoa(i+1) } } - sig := types.NewSignature(nil, nil, types.NewTuple(ret...), false) + sig := types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(ret...), false) if useClosure { cb.NewClosureWith(sig).BodyStart(pkg) } else { diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go index ca8a37f1a..03fa6fdb8 100644 --- a/cl/func_type_and_var.go +++ b/cl/func_type_and_var.go @@ -21,7 +21,6 @@ import ( "go/types" "log" "math/big" - "reflect" "strconv" "github.com/goplus/gop/ast" @@ -179,9 +178,10 @@ func toType(ctx *blockCtx, typ ast.Expr) (t types.Type) { return toIndexType(ctx, v) case *ast.IndexListExpr: return toIndexListType(ctx, v) + default: + ctx.handleErrorf(v.Pos(), "toType unexpected: %T", v) + return types.Typ[types.Invalid] } - log.Panicln("toType: unknown -", reflect.TypeOf(typ)) - return nil } var ( @@ -251,7 +251,7 @@ func toIdentType(ctx *blockCtx, ident *ast.Ident) (ret types.Type) { obj = t return t.Type() } - if v, _ := lookupPkgRef(ctx, nil, ident, objPkgRef); v != nil { + if v, _ := lookupPkgRef(ctx, gox.PkgRef{}, ident, objPkgRef); v != nil { if t, ok := v.(*types.TypeName); ok { obj = t return t.Type() @@ -438,28 +438,30 @@ func toInterfaceType(ctx *blockCtx, v *ast.InterfaceType) types.Type { return intf } -func toIndexType(ctx *blockCtx, v *ast.IndexExpr) types.Type { +func instantiate(ctx *blockCtx, exprX ast.Expr, indices ...ast.Expr) types.Type { ctx.inInst++ defer func() { ctx.inInst-- }() - ctx.cb.Typ(toType(ctx, v.X), v.X) - ctx.cb.Typ(toType(ctx, v.Index), v.Index) - ctx.cb.Index(1, false, v) - return ctx.cb.InternalStack().Pop().Type.(*gox.TypeType).Type() + + x := toType(ctx, exprX) + idx := make([]types.Type, len(indices)) + for i, index := range indices { + idx[i] = toType(ctx, index) + } + typ := ctx.pkg.Instantiate(x, idx, exprX) + if rec := ctx.recorder(); rec != nil { + rec.instantiate(exprX, x, typ) + } + return typ +} + +func toIndexType(ctx *blockCtx, v *ast.IndexExpr) types.Type { + return instantiate(ctx, v.X, v.Index) } func toIndexListType(ctx *blockCtx, v *ast.IndexListExpr) types.Type { - ctx.inInst++ - defer func() { - ctx.inInst-- - }() - ctx.cb.Typ(toType(ctx, v.X), v.X) - for _, index := range v.Indices { - ctx.cb.Typ(toType(ctx, index), index) - } - ctx.cb.Index(len(v.Indices), false, v) - return ctx.cb.InternalStack().Pop().Type.(*gox.TypeType).Type() + return instantiate(ctx, v.X, v.Indices...) } // ----------------------------------------------------------------------------- diff --git a/cl/internal/overload/bar/bar.go b/cl/internal/overload/bar/bar.go new file mode 100644 index 000000000..b675663b4 --- /dev/null +++ b/cl/internal/overload/bar/bar.go @@ -0,0 +1,33 @@ +package bar + +const GopPackage = true + +type M = map[string]any + +type basetype interface { + string | int | bool | float64 +} + +type Var__0[T basetype] struct { + val T +} + +func (p *Var__0[T]) Value() T { + return p.val +} + +type Var__1[T map[string]any] struct { + val T +} + +func (p *Var__1[T]) Value() T { + return p.val +} + +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]) +} diff --git a/cl/internal/spx3/spx3.go b/cl/internal/spx3/spx3.go new file mode 100644 index 000000000..6fe294747 --- /dev/null +++ b/cl/internal/spx3/spx3.go @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package spx3 + +const ( + GopPackage = true +) + +type Game struct { +} + +func (p *Game) initGame() {} + +func (p *Game) Run() {} + +type Sprite struct { +} + +func (p *Sprite) initSprite() {} + +func Gopt_Game_Main(game interface{ initGame() }, workers ...interface{ initSprite() }) { +} diff --git a/cl/outline/outline.go b/cl/outline/outline.go index 7adab5ac1..9e97dadd9 100644 --- a/cl/outline/outline.go +++ b/cl/outline/outline.go @@ -47,6 +47,9 @@ type Config struct { // LookupClass lookups a class by specified file extension. LookupClass func(ext string) (c *Project, ok bool) + // IsPkgtStandard checks a pkgPath is a Go standard package or not. + IsPkgtStandard func(pkgPath string) bool + // An Importer resolves import paths to Packages. Importer types.Importer } @@ -63,6 +66,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (_ Package, err C2goBase: conf.C2goBase, LookupPub: conf.LookupPub, LookupClass: conf.LookupClass, + IsPkgtStandard: conf.IsPkgtStandard, Importer: conf.Importer, NoFileLine: true, NoAutoGenMain: true, diff --git a/cl/recorder.go b/cl/recorder.go index 5ce8e9cbe..3ffb0e026 100644 --- a/cl/recorder.go +++ b/cl/recorder.go @@ -76,6 +76,26 @@ func (rec *typesRecorder) Type(expr ast.Expr, tv types.TypeAndValue) { rec.Recorder.Type(expr, tv) } +func (rec *typesRecorder) instantiate(expr ast.Expr, org, typ types.Type) { + // check gox TyOverloadNamed + if tv, ok := rec.types[expr]; ok { + tv.Type = typ + rec.Recorder.Type(expr, tv) + } + var ident *ast.Ident + switch id := expr.(type) { + case *ast.Ident: + ident = id + case *ast.SelectorExpr: + ident = id.Sel + } + if ident != nil { + if named, ok := typ.(*types.Named); ok { + rec.Use(ident, named.Obj()) + } + } +} + func newTypeRecord(rec Recorder) *typesRecorder { return &typesRecorder{rec, make(map[ast.Expr]types.TypeAndValue)} } diff --git a/cl/typeparams_test.go b/cl/typeparams_test.go index 014d36b33..c4b53b2d9 100644 --- a/cl/typeparams_test.go +++ b/cl/typeparams_test.go @@ -1,6 +1,3 @@ -//go:build go1.18 -// +build go1.18 - package cl_test import ( diff --git a/cmd/internal/mod/tidy.go b/cmd/internal/mod/tidy.go index 23879dd3d..2a79d340e 100644 --- a/cmd/internal/mod/tidy.go +++ b/cmd/internal/mod/tidy.go @@ -39,7 +39,7 @@ func runTidy(cmd *base.Command, args []string) { err := gop.Tidy(".", gopenv.Get()) if err != nil { if gop.NotFound(err) { - fmt.Fprintln(os.Stderr, "gop.mod not found") + fmt.Fprintln(os.Stderr, "go.mod not found") } else { fmt.Fprintln(os.Stderr, err) } diff --git a/cmd/make.go b/cmd/make.go index d04c52c46..6ecf458a6 100644 --- a/cmd/make.go +++ b/cmd/make.go @@ -548,10 +548,28 @@ func releaseNewVersion(tag string) { println("Released new version:", version) } +func runRegtests() { + println("\nStart running regtests.") + + cmd := exec.Command(filepath.Join(gopRoot, "bin/"+gopBinFiles[0]), "go", "./...") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = filepath.Join(gopRoot, "testdata") + err := cmd.Run() + if err != nil { + code := cmd.ProcessState.ExitCode() + if code == 0 { + code = 1 + } + os.Exit(code) + } +} + func main() { isInstall := flag.Bool("install", false, "Install Go+") isBuild := flag.Bool("build", false, "Build Go+ tools") isTest := flag.Bool("test", false, "Run testcases") + isRegtest := flag.Bool("regtest", false, "Run regtests") isUninstall := flag.Bool("uninstall", false, "Uninstall Go+") isGoProxy := flag.Bool("proxy", false, "Set GOPROXY for people in China") isAutoProxy := flag.Bool("autoproxy", false, "Check to set GOPROXY automatically") @@ -572,10 +590,11 @@ func main() { }, isUninstall: uninstall, isTest: runTestcases, + isRegtest: runRegtests, } // Sort flags, for example: install flag should be checked earlier than test flag. - flags := []*bool{isBuild, isInstall, isTest, isUninstall} + flags := []*bool{isBuild, isInstall, isTest, isRegtest, isUninstall} hasActionDone := false if *tag != "" { diff --git a/cmd/make_test.go b/cmd/make_test.go index e1ae186d1..d9dcc5e01 100644 --- a/cmd/make_test.go +++ b/cmd/make_test.go @@ -307,7 +307,7 @@ func TestInstallInNonGitRepo(t *testing.T) { }) t.Run("install with VERSION file", func(t *testing.T) { - version := "v1.1.98" + version := "v1.2.98" // Create VERSION file if err := os.WriteFile(versionFile, []byte(version), 0644); err != nil { t.Fatal(err) diff --git a/doc/docs.md b/doc/docs.md index f2434cf9c..d10ae9fca 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -266,7 +266,7 @@ s += "world" println s // Hello world ``` -All operators in Go+ must have values of the same type on both sides. You cannot concatenate an +Most Go+ operators must have values of the same type on both sides. You cannot concatenate an integer to a string: ```go failcompile @@ -281,6 +281,24 @@ age := 10 println "age = " + age.string ``` +However, you can replace `age.string` to `"${age}"`: + +```go +age := 10 +println "age = ${age}" +``` + +Here is a more complex example of `${expr}`: + +```go +host := "example.com" +page := 0 +limit := 20 +println "https://${host}/items?page=${page+1}&limit=${limit}" // https://example.com/items?page=1&limit=20 +println "$$" // $ +``` + +
⬆ back to toc
diff --git a/doc/dsl-vs-sdf.md b/doc/dsl-vs-sdf.md index f539a4fd9..b3df6dfc6 100644 --- a/doc/dsl-vs-sdf.md +++ b/doc/dsl-vs-sdf.md @@ -142,7 +142,6 @@ total 72 -rw-r--r-- 1 xushiwei staff 365 Jun 19 00:25 example.gsh -rw-r--r-- 1 xushiwei staff 126 Jun 19 09:33 go.mod -rw-r--r-- 1 xushiwei staff 165 Jun 19 09:33 go.sum --rw-r--r-- 1 xushiwei staff 110 Jun 19 09:33 gop.mod -rw-r--r-- 1 xushiwei staff 1938 Jun 19 10:00 gop_autogen.go ``` diff --git a/gengo.go b/gengo.go index 4e0103f89..79100c0e2 100644 --- a/gengo.go +++ b/gengo.go @@ -48,10 +48,14 @@ const ( // ----------------------------------------------------------------------------- +// GenGo generates gop_autogen.go for a Go+ package directory. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func GenGo(dir string, conf *Config, genTestPkg bool) (string, bool, error) { return GenGoEx(dir, conf, genTestPkg, 0) } +// GenGoEx generates gop_autogen.go for a Go+ package directory. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func GenGoEx(dir string, conf *Config, genTestPkg bool, flags GenFlags) (string, bool, error) { recursively := strings.HasSuffix(dir, "/...") if recursively { @@ -61,6 +65,9 @@ func GenGoEx(dir string, conf *Config, genTestPkg bool, flags GenFlags) (string, } func genGoDir(dir string, conf *Config, genTestPkg, recursively bool, flags GenFlags) (err error) { + if conf == nil { + conf = new(Config) + } if recursively { var ( list errors.List @@ -201,6 +208,8 @@ const ( modReadonly = 0555 ) +// GenGoPkgPath generates gop_autogen.go for a Go+ package. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func GenGoPkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (localDir string, recursively bool, err error) { return GenGoPkgPathEx(workDir, pkgPath, conf, allowExtern, 0) } @@ -218,6 +227,8 @@ func remotePkgPath(pkgPath string, conf *Config, recursively bool, flags GenFlag return } +// GenGoPkgPathEx generates gop_autogen.go for a Go+ package. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func GenGoPkgPathEx(workDir, pkgPath string, conf *Config, allowExtern bool, flags GenFlags) (localDir string, recursively bool, err error) { recursively = strings.HasSuffix(pkgPath, "/...") if recursively { @@ -259,7 +270,12 @@ func remotePkgPathDo(pkgPath string, doSth func(pkgDir, modDir string), onErr fu // ----------------------------------------------------------------------------- +// GenGoFiles generates gop_autogen.go for specified Go+ files. +// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`. func GenGoFiles(autogen string, files []string, conf *Config) (result []string, err error) { + if conf == nil { + conf = new(Config) + } if autogen == "" { autogen = "gop_autogen.go" if len(files) == 1 { diff --git a/go.mod b/go.mod index 6734b2b57..bb02a687b 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.18 require ( github.com/fsnotify/fsnotify v1.7.0 - github.com/goplus/c2go v0.7.19 - github.com/goplus/gox v1.13.2 - github.com/goplus/mod v0.12.2-0.20240107203906-5044606d0c51 + github.com/goplus/c2go v0.7.20 + github.com/goplus/gox v1.14.1 + github.com/goplus/mod v0.12.3 github.com/qiniu/x v1.13.2 - golang.org/x/tools v0.16.1 + golang.org/x/tools v0.17.0 ) require ( @@ -16,7 +16,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.16.0 // indirect ) retract v1.1.12 diff --git a/go.sum b/go.sum index 185d4182a..f4de21021 100644 --- a/go.sum +++ b/go.sum @@ -4,13 +4,14 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/goplus/c2go v0.7.19 h1:pPMarGN1RiNJFAYlL8oZlM7VNXeg054B4czDgBlck0o= -github.com/goplus/c2go v0.7.19/go.mod h1:EJgnt9CEHnXuWQc4y78w3DhKErL1aBzgE41NZaEa/3c= -github.com/goplus/gox v1.13.2 h1:XZZtLcZasRVKOYNzTnJ9E2Cu350TWu9K+CYMJTItkis= -github.com/goplus/gox v1.13.2/go.mod h1:iIchh0wp8Ye0DOPcFgHc5d0qlMOx8/OJ+DBQfe7hcTs= -github.com/goplus/mod v0.12.0/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs= -github.com/goplus/mod v0.12.2-0.20240107203906-5044606d0c51 h1:SXuaj7lYEL2fAQTrKzbCAUuRre2s2nMfGEyBqbUWooc= -github.com/goplus/mod v0.12.2-0.20240107203906-5044606d0c51/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs= +github.com/goplus/c2go v0.7.20 h1:90HiO7tuX8hHfEnl7HZRmLsAPH9q2wsYIvHiJMpwtU8= +github.com/goplus/c2go v0.7.20/go.mod h1:hToieb5V4BMY3c431DQ7DEg13JQ+q9qGmls4+GgymrA= +github.com/goplus/gox v1.14.0/go.mod h1:G7Hz+cAOUyJyN9pPHrpqhfQPDUtiJNmoRVTwoaQ9nw0= +github.com/goplus/gox v1.14.1 h1:ppqY4xzwc6UaeProYf4pn5fMa4xSDe80PsSsBfCYZ3U= +github.com/goplus/gox v1.14.1/go.mod h1:G7Hz+cAOUyJyN9pPHrpqhfQPDUtiJNmoRVTwoaQ9nw0= +github.com/goplus/mod v0.12.2/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs= +github.com/goplus/mod v0.12.3 h1:qLU5/F27CzUTQhCQRN1WruiCepUn5GdLKQTB41OsYfk= +github.com/goplus/mod v0.12.3/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= @@ -28,7 +29,7 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -40,13 +41,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -55,14 +56,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -75,6 +76,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/imp.go b/imp.go index 0ea6b318b..7de0b4b30 100644 --- a/imp.go +++ b/imp.go @@ -86,16 +86,9 @@ func (p *Importer) Import(pkgPath string) (pkg *types.Package, err error) { modDir := ret.ModDir goModfile := filepath.Join(modDir, "go.mod") if _, e := os.Lstat(goModfile); e != nil { // no go.mod - gopModfile := filepath.Join(modDir, "gop.mod") - if _, e := os.Lstat(gopModfile); e == nil { // has gop.mod - if err = p.genGoExtern(ret.Dir, isExtern); err != nil { - return - } - } else { // maybe a old Go package without go.mod - os.Chmod(modDir, modWritable) - defer os.Chmod(modDir, modReadonly) - os.WriteFile(goModfile, defaultGoMod(ret.ModPath), 0644) - } + os.Chmod(modDir, modWritable) + defer os.Chmod(modDir, modReadonly) + os.WriteFile(goModfile, defaultGoMod(ret.ModPath), 0644) } return p.impFrom.ImportFrom(pkgPath, ret.ModDir, 0) case gopmod.PkgtModule, gopmod.PkgtLocal: diff --git a/load.go b/load.go index 17990ce4f..79890fb45 100644 --- a/load.go +++ b/load.go @@ -121,6 +121,9 @@ type Config struct { Filter func(fs.FileInfo) bool Importer types.Importer + // Context represents all things between packages (optional). + Context *gox.Context + IgnoreNotatedError bool } @@ -183,11 +186,13 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou var pkgTest *ast.Package var clConf = &cl.Config{ - Fset: fset, - RelativeBase: relativeBaseOf(mod), - Importer: imp, - LookupClass: mod.LookupClass, - LookupPub: c2go.LookupPub(mod), + Fset: fset, + Context: conf.Context, + RelativeBase: relativeBaseOf(mod), + Importer: imp, + IsPkgtStandard: mod.IsPkgtStandard, + LookupClass: mod.LookupClass, + LookupPub: c2go.LookupPub(mod), } for name, pkg := range pkgs { @@ -267,11 +272,13 @@ func LoadFiles(dir string, files []string, conf *Config) (out *gox.Package, err imp = NewImporter(mod, gop, fset) } clConf := &cl.Config{ - Fset: fset, - RelativeBase: relativeBaseOf(mod), - Importer: imp, - LookupClass: mod.LookupClass, - LookupPub: c2go.LookupPub(mod), + Fset: fset, + Context: conf.Context, + RelativeBase: relativeBaseOf(mod), + Importer: imp, + LookupClass: mod.LookupClass, + IsPkgtStandard: mod.IsPkgtStandard, + LookupPub: c2go.LookupPub(mod), } out, err = cl.NewPackage("", pkg, clConf) if err != nil { diff --git a/parser/_testdata/goxtest/bar.gox b/parser/_testdata/goxtest1/bar.gox similarity index 100% rename from parser/_testdata/goxtest/bar.gox rename to parser/_testdata/goxtest1/bar.gox diff --git a/parser/_testdata/goxtest/parser.expect b/parser/_testdata/goxtest1/parser.expect similarity index 100% rename from parser/_testdata/goxtest/parser.expect rename to parser/_testdata/goxtest1/parser.expect diff --git a/parser/_testdata/goxtest2/bar.gox b/parser/_testdata/goxtest2/bar.gox new file mode 100644 index 000000000..954f1881f --- /dev/null +++ b/parser/_testdata/goxtest2/bar.gox @@ -0,0 +1,3 @@ +var ( + x.App +) diff --git a/parser/_testdata/goxtest2/parser.expect b/parser/_testdata/goxtest2/parser.expect new file mode 100644 index 000000000..43479424a --- /dev/null +++ b/parser/_testdata/goxtest2/parser.expect @@ -0,0 +1,15 @@ +package main + +file bar.gox +ast.GenDecl: + Tok: var + Specs: + ast.ValueSpec: + Type: + ast.SelectorExpr: + X: + ast.Ident: + Name: x + Sel: + ast.Ident: + Name: App diff --git a/parser/_testdata/overload1/overload.gop b/parser/_testdata/overload1/overload.gop new file mode 100644 index 000000000..d5b458bfe --- /dev/null +++ b/parser/_testdata/overload1/overload.gop @@ -0,0 +1,13 @@ +func foo = ( + func(a, b float64) float64 { + return a + b + } + func(a, b string) string { + return a + b + } +) + +func bar = ( + addComplex + (T).add +) diff --git a/parser/_testdata/overload1/parser.expect b/parser/_testdata/overload1/parser.expect new file mode 100644 index 000000000..aa686f6cc --- /dev/null +++ b/parser/_testdata/overload1/parser.expect @@ -0,0 +1,94 @@ +package main + +file overload.gop +ast.OverloadFuncDecl: + Name: + ast.Ident: + Name: foo + Funcs: + ast.FuncLit: + Type: + ast.FuncType: + Params: + ast.FieldList: + List: + ast.Field: + Names: + ast.Ident: + Name: a + ast.Ident: + Name: b + Type: + ast.Ident: + Name: float64 + Results: + ast.FieldList: + List: + ast.Field: + Type: + ast.Ident: + Name: float64 + Body: + ast.BlockStmt: + List: + ast.ReturnStmt: + Results: + ast.BinaryExpr: + X: + ast.Ident: + Name: a + Op: + + Y: + ast.Ident: + Name: b + ast.FuncLit: + Type: + ast.FuncType: + Params: + ast.FieldList: + List: + ast.Field: + Names: + ast.Ident: + Name: a + ast.Ident: + Name: b + Type: + ast.Ident: + Name: string + Results: + ast.FieldList: + List: + ast.Field: + Type: + ast.Ident: + Name: string + Body: + ast.BlockStmt: + List: + ast.ReturnStmt: + Results: + ast.BinaryExpr: + X: + ast.Ident: + Name: a + Op: + + Y: + ast.Ident: + Name: b +ast.OverloadFuncDecl: + Name: + ast.Ident: + Name: bar + Funcs: + ast.Ident: + Name: addComplex + ast.SelectorExpr: + X: + ast.ParenExpr: + X: + ast.Ident: + Name: T + Sel: + ast.Ident: + Name: add diff --git a/parser/_testdata/overload2/overload2.gop b/parser/_testdata/overload2/overload2.gop new file mode 100644 index 000000000..6d0eeeea3 --- /dev/null +++ b/parser/_testdata/overload2/overload2.gop @@ -0,0 +1,11 @@ +func (T).* = ( + mul1 + mul2 +) + +func (T).add = ( + add1 + func(a, b T) T { + return a + b + } +) diff --git a/parser/_testdata/overload2/parser.expect b/parser/_testdata/overload2/parser.expect new file mode 100644 index 000000000..0a6f83f90 --- /dev/null +++ b/parser/_testdata/overload2/parser.expect @@ -0,0 +1,68 @@ +package main + +file overload2.gop +ast.OverloadFuncDecl: + Recv: + ast.FieldList: + List: + ast.Field: + Type: + ast.Ident: + Name: T + Name: + ast.Ident: + Name: * + Funcs: + ast.Ident: + Name: mul1 + ast.Ident: + Name: mul2 +ast.OverloadFuncDecl: + Recv: + ast.FieldList: + List: + ast.Field: + Type: + ast.Ident: + Name: T + Name: + ast.Ident: + Name: add + Funcs: + ast.Ident: + Name: add1 + ast.FuncLit: + Type: + ast.FuncType: + Params: + ast.FieldList: + List: + ast.Field: + Names: + ast.Ident: + Name: a + ast.Ident: + Name: b + Type: + ast.Ident: + Name: T + Results: + ast.FieldList: + List: + ast.Field: + Type: + ast.Ident: + Name: T + Body: + ast.BlockStmt: + List: + ast.ReturnStmt: + Results: + ast.BinaryExpr: + X: + ast.Ident: + Name: a + Op: + + Y: + ast.Ident: + Name: b diff --git a/parser/_testdata/printvariadic/parser.expect b/parser/_testdata/printvariadic/parser.expect new file mode 100644 index 000000000..61658c498 --- /dev/null +++ b/parser/_testdata/printvariadic/parser.expect @@ -0,0 +1,24 @@ +package main + +file printv.gop +noEntrypoint +ast.FuncDecl: + Name: + ast.Ident: + Name: main + Type: + ast.FuncType: + Params: + ast.FieldList: + Body: + ast.BlockStmt: + List: + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: println + Args: + ast.Ident: + Name: x diff --git a/parser/_testdata/printvariadic/printv.gop b/parser/_testdata/printvariadic/printv.gop new file mode 100644 index 000000000..72d734b3c --- /dev/null +++ b/parser/_testdata/printvariadic/printv.gop @@ -0,0 +1 @@ +println x... diff --git a/parser/_testdata/stringex1/parser.expect b/parser/_testdata/stringex1/parser.expect new file mode 100644 index 000000000..ef2ca6842 --- /dev/null +++ b/parser/_testdata/stringex1/parser.expect @@ -0,0 +1,73 @@ +package main + +file string_lit.gop +noEntrypoint +ast.FuncDecl: + Name: + ast.Ident: + Name: main + Type: + ast.FuncType: + Params: + ast.FieldList: + Body: + ast.BlockStmt: + List: + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: println + Args: + ast.BasicLit: + Kind: STRING + Value: "$" + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: println + Args: + ast.BasicLit: + Kind: STRING + Value: "$$" + Extra: + $$ + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: println + Args: + ast.BasicLit: + Kind: STRING + Value: "a$$b$" + Extra: + a$$ + b$ + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: println + Args: + ast.BasicLit: + Kind: STRING + Value: "a$$b$$" + Extra: + a$$ + b$$ + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: println + Args: + ast.BasicLit: + Kind: STRING + Value: "a$b$%" diff --git a/parser/_testdata/stringex1/string_lit.gop b/parser/_testdata/stringex1/string_lit.gop new file mode 100644 index 000000000..b7d7154f3 --- /dev/null +++ b/parser/_testdata/stringex1/string_lit.gop @@ -0,0 +1,5 @@ +println "$" +println "$$" +println "a$$b$" +println "a$$b$$" +println "a$b$%" diff --git a/parser/_testdata/stringex2/parser.expect b/parser/_testdata/stringex2/parser.expect new file mode 100644 index 000000000..f8758a24f --- /dev/null +++ b/parser/_testdata/stringex2/parser.expect @@ -0,0 +1,67 @@ +package main + +file string_lit.gop +noEntrypoint +ast.FuncDecl: + Name: + ast.Ident: + Name: main + Type: + ast.FuncType: + Params: + ast.FieldList: + Body: + ast.BlockStmt: + List: + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: println + Args: + ast.BasicLit: + Kind: STRING + Value: "${" + Extra: + ${ + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: println + Args: + ast.BasicLit: + Kind: STRING + Value: "${ b }" + Extra: + ast.Ident: + Name: b + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: println + Args: + ast.BasicLit: + Kind: STRING + Value: "a${" + Extra: + a${ + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: println + Args: + ast.BasicLit: + Kind: STRING + Value: "a${b}c" + Extra: + a + ast.Ident: + Name: b + c diff --git a/parser/_testdata/stringex2/string_lit.gop b/parser/_testdata/stringex2/string_lit.gop new file mode 100644 index 000000000..478814746 --- /dev/null +++ b/parser/_testdata/stringex2/string_lit.gop @@ -0,0 +1,4 @@ +println "${" +println "${ b }" +println "a${" +println "a${b}c" diff --git a/parser/_testdata/stringex3/parser.expect b/parser/_testdata/stringex3/parser.expect new file mode 100644 index 000000000..320578eb7 --- /dev/null +++ b/parser/_testdata/stringex3/parser.expect @@ -0,0 +1,38 @@ +package main + +file string_lit.gop +noEntrypoint +ast.FuncDecl: + Name: + ast.Ident: + Name: main + Type: + ast.FuncType: + Params: + ast.FieldList: + Body: + ast.BlockStmt: + List: + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: println + Args: + ast.BasicLit: + Kind: STRING + Value: "file:${args[0]}?${query}" + Extra: + file: + ast.IndexExpr: + X: + ast.Ident: + Name: args + Index: + ast.BasicLit: + Kind: INT + Value: 0 + ? + ast.Ident: + Name: query diff --git a/parser/_testdata/stringex3/string_lit.gop b/parser/_testdata/stringex3/string_lit.gop new file mode 100644 index 000000000..fdb219953 --- /dev/null +++ b/parser/_testdata/stringex3/string_lit.gop @@ -0,0 +1 @@ +println "file:${args[0]}?${query}" diff --git a/parser/interface.go b/parser/interface.go index d9cff8e0a..7fd616af6 100644 --- a/parser/interface.go +++ b/parser/interface.go @@ -20,6 +20,7 @@ package parser import ( goparser "go/parser" + "go/scanner" "github.com/goplus/gop/ast" "github.com/goplus/gop/token" @@ -45,12 +46,15 @@ const ( AllErrors = Mode(goparser.AllErrors) // SkipObjectResolution - don't resolve identifiers to objects - see ParseFile SkipObjectResolution = Mode(goparser.SkipObjectResolution) + // ParseGoAsGoPlus - parse Go files by gop/parser ParseGoAsGoPlus Mode = 1 << 16 // ParserGoPlusClass - parse Go+ classfile by gop/parser ParseGoPlusClass Mode = 1 << 17 // SaveAbsFile - parse and save absolute path to pkg.Files SaveAbsFile Mode = 1 << 18 + + goReservedFlags Mode = ((1 << 16) - 1) ) // ----------------------------------------------------------------------------- @@ -119,7 +123,7 @@ func parseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) // ParseExprFrom is a convenience function for parsing an expression. // The arguments have the same meaning as for ParseFile, but the source must -// be a valid Go (type or value) expression. Specifically, fset must not +// be a valid Go/Go+ (type or value) expression. Specifically, fset must not // be nil. // // If the source couldn't be read, the returned AST is nil and the error @@ -160,6 +164,42 @@ func ParseExprFrom(fset *token.FileSet, filename string, src any, mode Mode) (ex return } +// parseExprEx is a convenience function for parsing an expression. +// The arguments have the same meaning as for ParseFile, but the source must +// be a valid Go/Go+ (type or value) expression. Specifically, fset must not +// be nil. +// +// If the source couldn't be read, the returned AST is nil and the error +// indicates the specific failure. If the source was read but syntax +// errors were found, the result is a partial AST (with ast.Bad* nodes +// representing the fragments of erroneous source code). Multiple errors +// are returned via a scanner.ErrorList which is sorted by source position. +func parseExprEx(file *token.File, src []byte, offset int, mode Mode) (expr ast.Expr, err scanner.ErrorList) { + var p parser + defer func() { + if e := recover(); e != nil { + // resume same panic if it's not a bailout + if _, ok := e.(bailout); !ok { + panic(e) + } + } + err = p.errors + }() + + // parse expr + p.initSub(file, src, offset, mode) + expr = p.parseRHS() + + // If a semicolon was inserted, consume it; + // report an error if there's more tokens. + if p.tok == token.SEMICOLON && p.lit == "\n" { + p.next() + } + p.expect(token.EOF) + + return +} + // ParseExpr is a convenience function for obtaining the AST of an expression x. // The position information recorded in the AST is undefined. The filename used // in error messages is the empty string. diff --git a/parser/parser.go b/parser/parser.go index ef1d8a646..97423f237 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -109,6 +109,16 @@ func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mod p.next() } +func (p *parser) initSub(file *token.File, src []byte, offset int, mode Mode) { + p.file = file + eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) } + p.scanner.InitEx(p.file, src, offset, eh, 0) + + p.mode = mode + p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently) + p.next() +} + // ---------------------------------------------------------------------------- // Scoping support @@ -456,8 +466,8 @@ func (p *parser) expectClosing(tok token.Token, context string) token.Pos { } func (p *parser) expectSemi() { - // semicolon is optional before a closing ')' or '}' - if p.tok != token.RPAREN && p.tok != token.RBRACE { + // semicolon is optional before a closing ')' or '}' or EOF + if p.tok != token.RPAREN && p.tok != token.RBRACE && p.tok != token.EOF { switch p.tok { case token.COMMA: // permit a ',' instead of a ';' but complain @@ -1590,6 +1600,90 @@ func (p *parser) parseFuncTypeOrLit() ast.Expr { return &ast.FuncLit{Type: typ, Body: body} } +func (p *parser) stringLit(pos token.Pos, val string) *ast.StringLitEx { + parts := p.stringLitEx(nil, pos+1, val[1:len(val)-1]) + if parts != nil { + return &ast.StringLitEx{Parts: parts} + } + return nil +} + +func (p *parser) stringLitEx(parts []any, pos token.Pos, text string) []any { + extra := false +loop: + at := strings.IndexByte(text, '$') + if at < 0 || at+1 == len(text) { // no '$' or end with '$' + if extra { + goto normal + } + return nil + } + switch text[at+1] { + case '{': // ${ + from := at + 2 + left := text[from:] + if left == "" { // "...${" (string end with "${") + goto normal + } + end := strings.IndexByte(left, '}') + if end < 0 { + p.error(pos+token.Pos(at+1), "invalid $ expression: ${ doesn't end with }") + goto normal + } + if at != 0 { + parts = append(parts, text[:at]) + } + to := pos + token.Pos(from+end) + parts = p.stringLitExpr(parts, pos+token.Pos(from), to) + pos = to + 1 + text = left[end+1:] + case '$': // $$ + parts = append(parts, text[:at+2]) + pos += token.Pos(at + 2) + text = text[at+2:] + default: + if extra || hasExtra(text[at+1:]) { + p.error(pos+token.Pos(at), "invalid $ expression: neither `${ ... }` nor `$$`") + } + return nil + } + if text != "" { + extra = true + goto loop + } + return parts +normal: + parts = append(parts, text) + return parts +} + +func hasExtra(text string) bool { + for { + at := strings.IndexByte(text, '$') + if at < 0 || at+1 == len(text) { // no '$' or end with '$' + return false + } + ch := text[at+1] + if ch == '{' || ch == '$' { + return true + } + text = text[at+2:] + } +} + +func (p *parser) stringLitExpr(parts []any, off, end token.Pos) []any { + file := p.file + base := file.Base() + src := p.scanner.CodeTo(int(end) - base) + expr, err := parseExprEx(p.file, src, int(off)-base, 0) + if err != nil { + p.errors = append(p.errors, err...) + expr = &ast.BadExpr{From: off, To: end} + } + parts = append(parts, expr) + return parts +} + // parseOperand may return an expression or a raw type (incl. array // types of the form [...]T. Callers must verify the result. // If lhs is set and the result is an identifier, it is not resolved. @@ -1607,7 +1701,11 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTup return case token.STRING, token.CSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT: - x = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} + bl := &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} + if p.tok == token.STRING { + bl.Extra = p.stringLit(p.pos, p.lit) + } + x = bl if debugParseOutput { log.Printf("ast.BasicLit{Kind: %v, Value: %v}\n", p.tok, p.lit) } @@ -3486,9 +3584,65 @@ func isOverloadOps(tok token.Token) bool { return int(tok) < len(overloadOps) && overloadOps[tok] != 0 } -func (p *parser) parseFuncDeclOrCall() (*ast.FuncDecl, *ast.CallExpr) { +// `funcName` +// `(*T).methodName` +// `func(params) results {...}` +func (p *parser) parseOverloadFunc() (ast.Expr, bool) { + switch p.tok { + case token.IDENT: + return p.parseIdent(), true + case token.FUNC: + return p.parseFuncTypeOrLit(), true + case token.LPAREN: + x, _ := p.parsePrimaryExpr(false, false, false) + return x, true + } + return nil, false +} + +// `= (overloadFuncs)` +// +// here overloadFunc represents +// +// `funcName` +// `(*T).methodName` +// `func(params) results {...}` +func (p *parser) parseOverloadDecl(decl *ast.OverloadFuncDecl) *ast.OverloadFuncDecl { + decl.Assign = p.expect(token.ASSIGN) + decl.Lparen = p.expect(token.LPAREN) + funcs := make([]ast.Expr, 0, 4) + for { + f, ok := p.parseOverloadFunc() + if !ok { + break + } + funcs = append(funcs, f) + if p.tok == token.SEMICOLON { + p.next() + } + } + decl.Funcs = funcs + decl.Rparen = p.expect(token.RPAREN) + p.expectSemi() + if debugParseOutput { + var recvt ast.Expr + if recv := decl.Recv; recv != nil { + recvt = recv.List[0].Type + } + log.Printf("ast.OverloadFuncDecl{Recv: %v, Name: %v, ...}\n", recvt, decl.Name) + } + return decl +} + +// `func identOrOp(params) results {...}` +// `func identOrOp = (overloadFuncs)` +// +// `func (recv) identOrOp(params) results { ... }` +// `func (T).identOrOp = (overloadFuncs)` +// `func (params) results { ... }()` +func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) { if p.trace { - defer un(trace(p, "FunctionDecl")) + defer un(trace(p, "FunctionDeclOrCall")) } doc := p.leadComment @@ -3499,16 +3653,39 @@ func (p *parser) parseFuncDeclOrCall() (*ast.FuncDecl, *ast.CallExpr) { var ident *ast.Ident var isOp, isFunLit, ok bool - if p.tok != token.LPAREN { // func identOrOp(...) + if p.tok != token.LPAREN { + // func: `func identOrOp(...) results` + // overload: `func identOrOp = (overloadFuncs)` ident, isOp = p.parseIdentOrOp() + if p.tok == token.ASSIGN { + // func identOrOp = (overloadFuncs) + return p.parseOverloadDecl(&ast.OverloadFuncDecl{ + Doc: doc, + Func: pos, + Name: ident, + Operator: isOp, + }), nil + } params, results = p.parseSignature(scope) } else { - // method: func (recv) XXX(params) results { ... } - // funlit: func (params) results { ... }() + // method: `func (recv) identOrOp(params) results { ... }` + // overload: `func (T).identOrOp = (overloadFuncs)` + // funlit: `func (params) results { ... }()` params = p.parseParameters(scope, true) if p.tok == token.LPAREN { // func (params) (results) { ... }() isFunLit, results = true, p.parseParameters(scope, false) + } else if p.tok == token.PERIOD { + p.next() + // func (T).identOrOp = (overloadFuncs) + ident, isOp = p.parseIdentOrOp() + return p.parseOverloadDecl(&ast.OverloadFuncDecl{ + Doc: doc, + Func: pos, + Recv: params, + Name: ident, + Operator: isOp, + }), nil } else if isOp = isOverloadOps(p.tok); isOp { oldtok, oldpos := p.tok, p.pos p.next() diff --git a/parser/parser_gop.go b/parser/parser_gop.go index 77cf548c8..34f549f23 100644 --- a/parser/parser_gop.go +++ b/parser/parser_gop.go @@ -161,7 +161,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, dir string, conf Config) (pk filename := fs.Join(dir, fname) if useGoParser { if filedata, err := fs.ReadFile(filename); err == nil { - if src, err := goparser.ParseFile(fset, filename, filedata, goparser.Mode(conf.Mode)); err == nil { + if src, err := goparser.ParseFile(fset, filename, filedata, goparser.Mode(conf.Mode&goReservedFlags)); err == nil { pkg := reqPkg(pkgs, src.Name.Name) if pkg.GoFiles == nil { pkg.GoFiles = make(map[string]*goast.File) @@ -173,13 +173,19 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, dir string, conf Config) (pk } else { first = err } - } else if f, err := ParseFSFile(fset, fs, filename, nil, mode); err == nil { - f.IsProj, f.IsClass = isProj, isClass - f.IsNormalGox = isNormalGox - pkg := reqPkg(pkgs, f.Name.Name) - pkg.Files[filename] = f - } else if first == nil { - first = err + } else { + f, err := ParseFSFile(fset, fs, filename, nil, mode) + if f != nil { + f.IsProj, f.IsClass = isProj, isClass + f.IsNormalGox = isNormalGox + if f.Name != nil { + pkg := reqPkg(pkgs, f.Name.Name) + pkg.Files[filename] = f + } + } + if err != nil && first == nil { + first = err + } } } } @@ -190,32 +196,23 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, dir string, conf Config) (pk // Compared to ParseFSFile, ParseFSEntry detects fileKind by its filename. func ParseFSEntry(fset *token.FileSet, fs FileSystem, filename string, src interface{}, conf Config) (f *ast.File, err error) { fname := fs.Base(filename) - fnameRmGox := fname ext := path.Ext(fname) - var isProj, isClass, isNormalGox, rmGox bool + var isProj, isClass, isNormalGox bool switch ext { case ".gop", ".go": case ".gox": - isClass = true - t := fname[:len(fname)-4] - if c := path.Ext(t); c != "" { - fnameRmGox, rmGox = t, true - } else { - isNormalGox = true - } + isNormalGox = true fallthrough default: - if !isNormalGox { - if conf.ClassKind == nil { - conf.ClassKind = defaultClassKind - } - if isProj, isClass = conf.ClassKind(fnameRmGox); !isClass { - if !rmGox { - return nil, ErrUnknownFileKind - } - // not found Go+ class by ext, but is a .gox file - isClass, isNormalGox = true, true - } + if conf.ClassKind == nil { + conf.ClassKind = defaultClassKind + } + if isProj, isClass = conf.ClassKind(fname); isClass { + isNormalGox = false + } else if isNormalGox { // not found Go+ class by ext, but is a .gox file + isClass = true + } else { + return nil, ErrUnknownFileKind } } mode := conf.Mode @@ -223,7 +220,7 @@ func ParseFSEntry(fset *token.FileSet, fs FileSystem, filename string, src inter mode |= ParseGoPlusClass } f, err = ParseFSFile(fset, fs, filename, src, mode) - if err == nil { + if f != nil { f.IsProj, f.IsClass = isProj, isClass f.IsNormalGox = isNormalGox } diff --git a/parser/parser_test.go b/parser/parser_test.go index 9a90a7de9..b0dd4b7ed 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -63,6 +63,7 @@ func testErrCode(t *testing.T, code string, errExp, panicExp string) { } } }() + t.Helper() fset := token.NewFileSet() _, err := Parse(fset, "/foo/bar.gop", code, 0) if err == nil || err.Error() != errExp { @@ -78,6 +79,7 @@ func testErrCodeParseExpr(t *testing.T, code string, errExp, panicExp string) { } } }() + t.Helper() _, err := ParseExpr(code) if err == nil || err.Error() != errExp { t.Fatal("testErrCodeParseExpr error:", err) @@ -92,6 +94,7 @@ func testClassErrCode(t *testing.T, code string, errExp, panicExp string) { } } }() + t.Helper() fset := token.NewFileSet() _, err := Parse(fset, "/foo/bar.gox", code, ParseGoPlusClass) if err == nil || err.Error() != errExp { @@ -504,4 +507,23 @@ func TestErrCompositeLiteral(t *testing.T) { `, `/foo/bar.gop:1:10: cannot parenthesize type in composite literal`, ``) } +func TestErrSelectorExpr(t *testing.T) { + testErrCode(t, ` +x. +*p +`, `/foo/bar.gop:3:1: expected selector or type assertion, found '*'`, ``) +} + +func TestErrStringLitEx(t *testing.T) { + testErrCode(t, ` +println "${ ... }" +`, "/foo/bar.gop:2:13: expected operand, found '...'", ``) + testErrCode(t, ` +println "${b" +`, "/foo/bar.gop:2:11: invalid $ expression: ${ doesn't end with }", ``) + testErrCode(t, ` +println "$a${b}" +`, "/foo/bar.gop:2:10: invalid $ expression: neither `${ ... }` nor `$$`", ``) +} + // ----------------------------------------------------------------------------- diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go index ff9257017..d43bfcaee 100644 --- a/parser/parserdir_test.go +++ b/parser/parserdir_test.go @@ -100,6 +100,7 @@ func testFrom(t *testing.T, pkgDir, sel string, exclude Mode) { if sel != "" && !strings.Contains(pkgDir, sel) { return } + t.Helper() log.Println("Parsing", pkgDir) fset := token.NewFileSet() pkgs, err := ParseDir(fset, pkgDir, nil, (Trace|ParseComments|ParseGoAsGoPlus)&^exclude) @@ -194,6 +195,30 @@ func TestParseEntry(t *testing.T) { }) } +func TestParseEntry2(t *testing.T) { + fset := token.NewFileSet() + src, err := os.ReadFile("./_testdata/functype/functype.go") + if err != nil { + t.Fatal("os.ReadFile:", err) + } + conf := Config{} + conf.ClassKind = func(fname string) (isProj bool, ok bool) { + if strings.HasSuffix(fname, "_yap.gox") { + return true, true + } + return defaultClassKind(fname) + } + t.Run("_yap.gox file", func(t *testing.T) { + f, err := ParseEntry(fset, "./functype_yap.gox", src, conf) + if err != nil { + t.Fatal("ParseEntry failed:", err) + } + if !f.IsClass || !f.IsProj || f.IsNormalGox { + t.Fatal("ParseEntry functype_yap.gox:", f.IsClass, f.IsProj, f.IsNormalGox) + } + }) +} + func TestSaveAbsFile(t *testing.T) { fset := token.NewFileSet() src, err := os.ReadFile("./_testdata/functype/functype.go") diff --git a/parser/parsertest/parsertest.go b/parser/parsertest/parsertest.go index 47f2821ea..081d233ab 100644 --- a/parser/parsertest/parsertest.go +++ b/parser/parsertest/parsertest.go @@ -84,6 +84,16 @@ func FprintNode(w io.Writer, lead string, v interface{}, prefix, indent string) FprintNode(w, fmt.Sprintf("%s%v:\n", prefix, sf.Name), sfv, prefix+indent, indent) } } + } else if lit, ok := v.(*ast.StringLitEx); ok { + fmt.Fprintf(w, "%sExtra:\n", prefix) + prefix += indent + for _, part := range lit.Parts { + if val, ok := part.(string); ok { + fmt.Fprintf(w, "%s%v\n", prefix, val) + } else { + FprintNode(w, "", part, prefix, indent) + } + } } else { log.Panicln("FprintNode unexpected type:", t) } @@ -101,7 +111,7 @@ func Fprint(w io.Writer, pkg *ast.Package) { for _, fpath := range paths { fmt.Fprintf(w, "\nfile %s\n", filepath.Base(fpath)) file := pkg.Files[fpath] - if file.NoEntrypoint() { + if file.HasShadowEntry() { fmt.Fprintf(w, "noEntrypoint\n") } FprintNode(w, "", file.Decls, "", " ") diff --git a/printer/gop_test.go b/printer/gop_test.go index 30bd7abab..8aadd2977 100644 --- a/printer/gop_test.go +++ b/printer/gop_test.go @@ -19,7 +19,6 @@ package printer_test import ( "bytes" "io/fs" - "io/ioutil" "os" "path/filepath" "strings" @@ -114,7 +113,7 @@ func testFrom(t *testing.T, fpath, sel string, mode int) { if sel != "" && !strings.Contains(fpath, sel) { return } - src, err := ioutil.ReadFile(fpath) + src, err := os.ReadFile(fpath) if err != nil { t.Fatal(err) } diff --git a/printer/nodes.go b/printer/nodes.go index 2a9e2fbb1..4bbf900a8 100644 --- a/printer/nodes.go +++ b/printer/nodes.go @@ -1705,7 +1705,9 @@ func (p *printer) spec(spec ast.Spec, n int, doIndent bool) { p.setComment(s.Doc) p.identList(s.Names, doIndent) // always present if s.Type != nil { - p.print(blank) + if len(s.Names) > 0 { + p.print(blank) + } p.expr(s.Type) } if s.Values != nil { @@ -1980,7 +1982,7 @@ func (p *printer) funcDecl(d *ast.FuncDecl) { // different line (all whitespace preceding the FUNC is emitted only when the // FUNC is emitted). startCol := p.out.Column - len("func ") - if d.Recv != nil { + if d.Recv != nil && !d.IsClass { p.parameters(d.Recv) // method: print receiver p.print(blank) } @@ -1992,6 +1994,28 @@ func (p *printer) funcDecl(d *ast.FuncDecl) { p.funcBody(p.distanceFrom(d.Pos(), startCol), vtab, d.Body) } +func (p *printer) overloadFuncDecl(d *ast.OverloadFuncDecl) { + if debugFormat { + log.Println("==> Format OverloadFunc", d.Name.Name) + } + p.setComment(d.Doc) + + pos := d.Pos() + p.print(pos, token.FUNC, blank) + if d.Recv != nil && !d.IsClass { + p.parameters(d.Recv) // method: print receiver + p.print(token.PERIOD) + } + p.expr(d.Name) + p.print(blank, token.ASSIGN, blank, token.LPAREN, newline) + for _, fn := range d.Funcs { + p.print(indent) + p.expr1(fn, token.LowestPrec, 1) + p.print(unindent, newline) + } + p.print(token.RPAREN) +} + func (p *printer) decl(decl ast.Decl) { switch d := decl.(type) { case *ast.BadDecl: @@ -2000,6 +2024,8 @@ func (p *printer) decl(decl ast.Decl) { p.genDecl(d) case *ast.FuncDecl: p.funcDecl(d) + case *ast.OverloadFuncDecl: + p.overloadFuncDecl(d) default: panic("unreachable") } diff --git a/scanner/scanner.go b/scanner/scanner.go index 5143470bc..08189102b 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -17,7 +17,6 @@ // Package scanner implements a scanner for Go+ source text. // It takes a []byte as source which can then be tokenized // through repeated calls to the Scan method. -// package scanner import ( @@ -36,13 +35,11 @@ import ( // encountered and a handler was installed, the handler is called with a // position and an error message. The position points to the beginning of // the offending token. -// type ErrorHandler = scanner.ErrorHandler // A Scanner holds the scanner's internal state while processing // a given text. It can be allocated as part of another data // structure but must be initialized via Init before use. -// type Scanner struct { // immutable state file *token.File // source file handle @@ -66,7 +63,6 @@ const bom = 0xFEFF // byte order mark, only permitted as very first character // Read the next Unicode char into s.ch. // s.ch < 0 means end-of-file. -// func (s *Scanner) next() { if s.rdOffset < len(s.src) { s.offset = s.rdOffset @@ -110,7 +106,6 @@ func (s *Scanner) peek() byte { // A Mode value is a set of flags (or 0). // They control scanner behavior. -// type Mode uint const ( @@ -133,12 +128,16 @@ const ( // // Note that Init may call err if there is an error in the first character // of the file. -// func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) { // Explicitly initialize all fields since a scanner may be reused. if file.Size() != len(src) { panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src))) } + s.InitEx(file, src, 0, err, mode) +} + +// InitEx init the scanner with an offset (this means src[offset:] is all the code to scan). +func (s *Scanner) InitEx(file *token.File, src []byte, offset int, err ErrorHandler, mode Mode) { s.file = file s.dir, _ = filepath.Split(file.Name()) s.src = src @@ -147,7 +146,7 @@ func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode s.ch = ' ' s.offset = 0 - s.rdOffset = 0 + s.rdOffset = offset s.lineOffset = 0 s.insertSemi = false s.ErrorCount = 0 @@ -158,6 +157,10 @@ func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode } } +func (s *Scanner) CodeTo(end int) []byte { + return s.src[:end] +} + func (s *Scanner) error(offs int, msg string) { if s.err != nil { s.err(s.file.Position(s.file.Pos(offs)), msg) @@ -810,7 +813,6 @@ func (s *Scanner) switch4(tok0, tok1 token.Token, ch2 rune, tok2, tok3 token.Tok // Scan adds line information to the file added to the file // set with Init. Token positions are relative to that file // and thus relative to the file set. -// func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) { scanAgain: s.skipWhitespace() diff --git a/test/classfile.go b/test/classfile.go new file mode 100644 index 000000000..8ca2593a1 --- /dev/null +++ b/test/classfile.go @@ -0,0 +1,61 @@ +package test + +import ( + "os" + "testing" +) + +const ( + GopPackage = true +) + +// ----------------------------------------------------------------------------- + +type testingT = testing.T + +// Case represents a Go+ testcase. +type Case struct { + t *testingT +} + +func (p *Case) initCase(t *testing.T) { + p.t = t +} + +// T returns a *testing.T object. +func (p Case) T() *testing.T { return p.t } + +// Run runs f as a subtest of t called name. It runs f in a separate goroutine +// and blocks until f returns or calls t.Parallel to become a parallel test. +// Run reports whether f succeeded (or at least did not fail before calling t.Parallel). +func (p Case) Run(name string, f func(t *testing.T)) bool { + return p.t.Run(name, f) +} + +// Gopt_Case_TestMain is required by Go+ compiler as the test case entry. +func Gopt_Case_TestMain(c interface{ initCase(t *testing.T) }, t *testing.T) { + c.initCase(t) + c.(interface{ Main() }).Main() +} + +// ----------------------------------------------------------------------------- + +// App represents a Go+ testing main application. +type App struct { + m *testing.M +} + +func (p *App) initApp(m *testing.M) { + p.m = m +} + +// Gopt_App_TestMain is required by Go+ compiler as the entry of a Go+ testing project. +func Gopt_App_TestMain(app interface{ initApp(m *testing.M) }, m *testing.M) { + app.initApp(m) + if me, ok := app.(interface{ MainEntry() }); ok { + me.MainEntry() + } + os.Exit(m.Run()) +} + +// ----------------------------------------------------------------------------- diff --git a/testdata/helloc2go/README.md b/testdata/_helloc2go/README.md similarity index 100% rename from testdata/helloc2go/README.md rename to testdata/_helloc2go/README.md diff --git a/testdata/_helloc2go/go.mod b/testdata/_helloc2go/go.mod new file mode 100644 index 000000000..050eb82f1 --- /dev/null +++ b/testdata/_helloc2go/go.mod @@ -0,0 +1,31 @@ +module hello2go + +go 1.21.1 + +require github.com/goplus/libc v0.3.15 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/gofuzz v1.0.0 // indirect + github.com/goplus/c2go v0.7.15 // indirect + github.com/goplus/gox v1.12.0 // indirect + github.com/goplus/mod v0.11.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/qiniu/x v1.13.0 // indirect + github.com/stretchr/objx v0.1.0 // indirect + github.com/stretchr/testify v1.3.0 // indirect + github.com/yuin/goldmark v1.4.13 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/term v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.13.0 // indirect + golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 // indirect +) diff --git a/testdata/_helloc2go/go.sum b/testdata/_helloc2go/go.sum new file mode 100644 index 000000000..63c018c34 --- /dev/null +++ b/testdata/_helloc2go/go.sum @@ -0,0 +1,60 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/goplus/c2go v0.7.15/go.mod h1:SKSY0OvbO9JBkHRjyiJz3vz0rHpwPETjNj6AQ/rQZOg= +github.com/goplus/gox v1.12.0/go.mod h1:Ek4bXv4xzfBiFuHC6yfkzHXrhUHXfrM9QyXtMusdkGo= +github.com/goplus/libc v0.3.15 h1:xfo4Lc66Sf7Dyel+gAT+5fCG0XOB7PxRE4892MH5zgg= +github.com/goplus/libc v0.3.15/go.mod h1:ExS/KlOepeNJkY10ykY1Qgg6GPql+AItHNNsXuIZwgw= +github.com/goplus/mod v0.11.5/go.mod h1:NDC5E+XOT8vcJCMjqKhLDJHTHX7lyVN4Vbfi2U7dBhs= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a h1:VXRRto5GMJPNfB7MNbUVoFhtxwoYjBEsIt/NpWg42U0= +github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/qiniu/x v1.13.0/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/testdata/helloc2go/hello.gop b/testdata/_helloc2go/hello.gop similarity index 100% rename from testdata/helloc2go/hello.gop rename to testdata/_helloc2go/hello.gop diff --git a/testdata/mixgo-complex/bar.go b/testdata/mixgo-complex/bar.go new file mode 100644 index 000000000..1711b2141 --- /dev/null +++ b/testdata/mixgo-complex/bar.go @@ -0,0 +1,24 @@ +package main + +import ( + "io" + "log" + "testing" +) + +type ift interface { + io.Closer + f(int) string + g() +} + +type impl struct { + a T +} + +func Bar(t *testing.T) int { + log.Println("Hello") + t.Log("Hello", + "world") + return 0 +} diff --git a/testdata/mixgo-complex/foo.gop b/testdata/mixgo-complex/foo.gop new file mode 100644 index 000000000..f607a8d10 --- /dev/null +++ b/testdata/mixgo-complex/foo.gop @@ -0,0 +1,43 @@ +// Pkg doc + +import ( + "io" + "log" +) + +type ift2 interface { + io.Closer + f(int) string + g() +} + +// T doc +type T struct { + io.Closer +} + +func (T) Close() (err error) { + log.Println("Hi!") + return +} + +func (t T) g() {} + +func (t T) f(a int) (b string) { + _ = t.Closer + return +} + +func Foo(i *impl) string { + i.a.f(0) + i.a.g() + return "" +} + +// foo golang/go#61561: interface instances aren't concurrency-safe +// as they are not completed by the type checker. +func foo(a, b int) string { + return "" +} + +println "Hello, world", T{}.f(0) diff --git a/testdata/overloadfunc1/add.gop b/testdata/overloadfunc1/add.gop new file mode 100644 index 000000000..c8fe0b8a0 --- /dev/null +++ b/testdata/overloadfunc1/add.gop @@ -0,0 +1,11 @@ +func add = ( + func(a, b int) int { + return a + b + } + func(a, b string) string { + return a + b + } +) + +println add(100, 7) +println add("Hello", "World") diff --git a/testdata/overloadfunc2/mul.gop b/testdata/overloadfunc2/mul.gop new file mode 100644 index 000000000..6b18ac0d7 --- /dev/null +++ b/testdata/overloadfunc2/mul.gop @@ -0,0 +1,15 @@ +func mulInt(a, b int) int { + return a * b +} + +func mulFloat(a, b float64) float64 { + return a * b +} + +func mul = ( + mulInt + mulFloat +) + +println mul(100, 7) +println mul(1.2, 3.14) diff --git a/testdata/overloadmethod/method.gop b/testdata/overloadmethod/method.gop new file mode 100644 index 000000000..a55ee9bb9 --- /dev/null +++ b/testdata/overloadmethod/method.gop @@ -0,0 +1,21 @@ +type foo struct { +} + +func (a *foo) mulInt(b int) *foo { + println "mulInt" + return a +} + +func (a *foo) mulFoo(b *foo) *foo { + println "mulFoo" + return a +} + +func (foo).mul = ( + (foo).mulInt + (foo).mulFoo +) + +var a, b *foo +var c = a.mul(100) +var d = a.mul(c) diff --git a/testdata/overloadop1/overloadop.gop b/testdata/overloadop1/overloadop.gop new file mode 100644 index 000000000..d7b489b6c --- /dev/null +++ b/testdata/overloadop1/overloadop.gop @@ -0,0 +1,34 @@ +type foo struct { +} + +func (a foo) + (b foo) (ret foo) { + println "a + b" + return +} + +func (a foo) - (b foo) (ret foo) { + println "a - b" + return +} + +func -(a foo) (ret foo) { + println "-a" + return +} + +func ++(a foo) { + println "a++" +} + +func (a foo) != (b foo) bool { + println "a != b" + return true +} + +var a, b foo +var c = a + b +var d = a - b +var e = -a // TODO: -a have no return value! +var f = a != b +println f +a++ diff --git a/testdata/overloadop2/overloadop.gop b/testdata/overloadop2/overloadop.gop new file mode 100644 index 000000000..516947f2c --- /dev/null +++ b/testdata/overloadop2/overloadop.gop @@ -0,0 +1,28 @@ +type foo struct { +} + +func (a foo) mulInt(b int) (ret foo) { + println "a * int" + return +} + +func (a foo) mulFoo(b foo) (ret foo) { + println "a * b" + return +} + +func intMulFoo(a int, b foo) (ret foo) { + println "int * b" + return +} + +func (foo).* = ( + (foo).mulInt + (foo).mulFoo + intMulFoo +) + +var a, b foo +var c = a * 10 +var d = a * b +var e = 10 * a diff --git a/testdata/typeparamscast/foo.go b/testdata/typeparamscast/foo.go new file mode 100644 index 000000000..b2d304f16 --- /dev/null +++ b/testdata/typeparamscast/foo.go @@ -0,0 +1,23 @@ +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]) +} diff --git a/testdata/typeparamscast/typecast.gop b/testdata/typeparamscast/typecast.gop new file mode 100644 index 000000000..c3c95e53e --- /dev/null +++ b/testdata/typeparamscast/typecast.gop @@ -0,0 +1 @@ +println Var(int) diff --git a/testdata/unit-test/foo.gop b/testdata/unit-test/foo.gop new file mode 100644 index 000000000..bfdedc1bf --- /dev/null +++ b/testdata/unit-test/foo.gop @@ -0,0 +1,3 @@ +func foo(v int) int { + return v * 2 +} diff --git a/testdata/unit-test/foo_test.gox b/testdata/unit-test/foo_test.gox new file mode 100644 index 000000000..4e238df38 --- /dev/null +++ b/testdata/unit-test/foo_test.gox @@ -0,0 +1,17 @@ +if v := foo(50); v == 100 { + t.log "test foo(50) ok" +} else { + t.error "foo() ret: ${v}" +} + +t.run "foo -10", t => { + if foo(-10) != -20 { + t.fatal "foo(-10) != -20" + } +} + +t.run "foo 0", t => { + if foo(0) != 0 { + t.fatal "foo(0) != 0" + } +} diff --git a/x/build/build.go b/x/build/build.go index 0b99626ed..10fae033c 100644 --- a/x/build/build.go +++ b/x/build/build.go @@ -21,7 +21,6 @@ import ( "fmt" goast "go/ast" "go/types" - "path" "path/filepath" "github.com/goplus/gop/ast" @@ -31,6 +30,7 @@ import ( "github.com/goplus/gop/token" "github.com/goplus/gox" "github.com/goplus/gox/packages" + "github.com/goplus/mod/modfile" ) type Class = cl.Class @@ -76,7 +76,7 @@ func (p *Package) ToAst() *goast.File { } func ClassKind(fname string) (isProj, ok bool) { - ext := path.Ext(fname) + ext := modfile.ClassExt(fname) switch ext { case ".gmx": return true, true diff --git a/x/build/build_test.go b/x/build/build_test.go index cb867bd22..b43a127ff 100644 --- a/x/build/build_test.go +++ b/x/build/build_test.go @@ -21,7 +21,6 @@ import ( "fmt" "go/printer" "go/types" - "io/ioutil" "os" "path" "path/filepath" @@ -38,12 +37,14 @@ var ( ) func init() { + cl.SetDebug(cl.FlagNoMarkAutogen) ctx.LoadConfig = func(cfg *cl.Config) { cfg.NoFileLine = true } build.RegisterClassFileType(".tspx", "MyGame", []*build.Class{ {Ext: ".tspx", Class: "Sprite"}, }, "github.com/goplus/gop/cl/internal/spx") + build.RegisterClassFileType("_yap.gox", "App", nil, "github.com/goplus/yap") } func gopClTest(t *testing.T, gopcode interface{}, expected string) { @@ -76,6 +77,7 @@ func TestKind(t *testing.T) { testKind(t, "main.gmx", true, true) testKind(t, "Cat.tspx", false, true) testKind(t, "main.tspx", true, true) + testKind(t, "blog_yap.gox", true, true) } func TestGop(t *testing.T) { @@ -183,8 +185,8 @@ println "Go+" `, `package main import ( - "fmt" "bytes" + "fmt" ) type Rect struct { @@ -208,8 +210,8 @@ println "Go+" `, `package main import ( - "fmt" "bytes" + "fmt" ) type Rect struct { @@ -239,7 +241,7 @@ import ( func main() { a := ng.Bigrat_Init__2(big.NewRat(1, 2)) - fmt.Println(a.Gop_Add(ng.Bigrat_Init__2(big.NewRat(1, 2)))) + fmt.Println((ng.Bigrat).Gop_Add(a, ng.Bigrat_Init__2(big.NewRat(1, 2)))) } `) } @@ -300,8 +302,8 @@ println addSafe("10", "abc") import ( "fmt" - "strconv" "github.com/qiniu/x/errors" + "strconv" ) func add(x string, y string) (int, error) { @@ -389,6 +391,10 @@ import ( "github.com/goplus/gop/cl/internal/spx" ) +func main() { + spx.Gopt_MyGame_Main(new(spx.MyGame)) +} + type Cat struct { spx.Sprite } @@ -396,8 +402,6 @@ type Cat struct { func (this *Cat) Main() { fmt.Println("hi") } -func main() { -} `) } @@ -407,7 +411,7 @@ func testFromDir(t *testing.T, relDir string) { t.Fatal("Getwd failed:", err) } dir = path.Join(dir, relDir) - fis, err := ioutil.ReadDir(dir) + fis, err := os.ReadDir(dir) if err != nil { t.Fatal("ReadDir failed:", err) } diff --git a/x/c2go/c2go.go b/x/c2go/c2go.go index d2240f117..33116cc8c 100644 --- a/x/c2go/c2go.go +++ b/x/c2go/c2go.go @@ -18,7 +18,6 @@ package c2go import ( "path/filepath" - "syscall" "github.com/goplus/mod/gopmod" ) @@ -28,9 +27,6 @@ import ( // LookupPub returns a anonymous function required by cl.NewPackage. func LookupPub(mod *gopmod.Module) func(pkgPath string) (pubfile string, err error) { return func(pkgPath string) (pubfile string, err error) { - if mod.File == nil { // no go.mod/gop.mod file - return "", syscall.ENOENT - } pkg, err := mod.Lookup(pkgPath) if err == nil { pubfile = filepath.Join(pkg.Dir, "c2go.a.pub") diff --git a/x/typesutil/check.go b/x/typesutil/check.go index 88673790d..f6803f470 100644 --- a/x/typesutil/check.go +++ b/x/typesutil/check.go @@ -69,6 +69,9 @@ type Config struct { // Fset provides source position information for syntax trees and types (required). Fset *token.FileSet + // Context represents all things between packages (optional). + Context *gox.Context + // WorkingDir is the directory in which to run gop compiler (optional). // If WorkingDir is not set, os.Getwd() is used. WorkingDir string @@ -77,7 +80,7 @@ type Config struct { // Default is github.com/goplus/. C2goBase string - // Mod represents a gop.mod object (optional). + // Mod represents a Go+ module (optional). Mod *gopmod.Module } @@ -111,6 +114,9 @@ func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error) gopfs := make(map[string]*ast.File) for _, goFile := range goFiles { f := fset.File(goFile.Pos()) + if f == nil { + continue + } file := f.Name() fname := filepath.Base(file) if strings.HasPrefix(fname, "gop_autogen") { @@ -121,6 +127,9 @@ func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error) } for _, gopFile := range gopFiles { f := fset.File(gopFile.Pos()) + if f == nil { + continue + } gopfs[f.Name()] = gopFile } if debugVerbose { @@ -138,9 +147,11 @@ func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error) _, err = cl.NewPackage(pkgTypes.Path(), pkg, &cl.Config{ Types: pkgTypes, Fset: fset, + Context: opts.Context, C2goBase: opts.C2goBase, LookupPub: c2go.LookupPub(mod), LookupClass: mod.LookupClass, + IsPkgtStandard: mod.IsPkgtStandard, Importer: newImporter(conf.Importer, mod, nil, fset), Recorder: gopRecorder{p.gopInfo}, NoFileLine: true, diff --git a/x/typesutil/check_test.go b/x/typesutil/check_test.go index fbbbe0acb..703118c1f 100644 --- a/x/typesutil/check_test.go +++ b/x/typesutil/check_test.go @@ -185,3 +185,13 @@ var i int = "hello" t.Fatal("no error") } } + +func TestBadFile(t *testing.T) { + conf := &types.Config{} + opt := &typesutil.Config{} + opt.Fset = token.NewFileSet() + opt.Types = types.NewPackage("", "main") + checker := typesutil.NewChecker(conf, opt, nil, nil) + _ = checker.Files([]*goast.File{&goast.File{Name: goast.NewIdent("main")}}, + []*ast.File{&ast.File{Name: ast.NewIdent("main")}}) +} diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go index 4632f22fc..addddcaa2 100644 --- a/x/typesutil/info_test.go +++ b/x/typesutil/info_test.go @@ -1385,3 +1385,173 @@ func (p *N) Test__1(n int) { 005: 6: 1 | n | var n main.N 006: 6: 3 | test | func (*main.N).Test__1(n int)`) } + +func TestOverloadNamed(t *testing.T) { + testGopInfo(t, ` +import "github.com/goplus/gop/cl/internal/overload/bar" + +var a bar.Var[int] +var b bar.Var[bar.M] +c := bar.Var(string) +d := bar.Var(bar.M) +`, ``, `== types == +000: 4: 7 | bar.Var *ast.SelectorExpr | type : github.com/goplus/gop/cl/internal/overload/bar.Var__0[int] | type +001: 4: 7 | bar.Var[int] *ast.IndexExpr | type : github.com/goplus/gop/cl/internal/overload/bar.Var__0[int] | type +002: 4:15 | int *ast.Ident | type : int | type +003: 5: 7 | bar.Var *ast.SelectorExpr | type : github.com/goplus/gop/cl/internal/overload/bar.Var__1[map[string]any] | type +004: 5: 7 | bar.Var[bar.M] *ast.IndexExpr | type : github.com/goplus/gop/cl/internal/overload/bar.Var__1[map[string]any] | type +005: 5:15 | bar.M *ast.SelectorExpr | type : map[string]any | type +006: 6: 6 | bar.Var *ast.SelectorExpr | value : func[T github.com/goplus/gop/cl/internal/overload/bar.basetype]() *github.com/goplus/gop/cl/internal/overload/bar.Var__0[T] | value +007: 6: 6 | bar.Var(string) *ast.CallExpr | value : *github.com/goplus/gop/cl/internal/overload/bar.Var__0[string] | value +008: 6:14 | string *ast.Ident | type : string | type +009: 7: 6 | bar.Var *ast.SelectorExpr | value : func[T map[string]any]() *github.com/goplus/gop/cl/internal/overload/bar.Var__1[T] | value +010: 7: 6 | bar.Var(bar.M) *ast.CallExpr | value : *github.com/goplus/gop/cl/internal/overload/bar.Var__1[map[string]any] | value +011: 7:14 | bar.M *ast.SelectorExpr | var : map[string]any | variable +== defs == +000: 4: 5 | a | var main.a github.com/goplus/gop/cl/internal/overload/bar.Var__0[int] +001: 5: 5 | b | var main.b github.com/goplus/gop/cl/internal/overload/bar.Var__1[map[string]any] +002: 6: 1 | c | var c *github.com/goplus/gop/cl/internal/overload/bar.Var__0[string] +003: 6: 1 | main | func main.main() +004: 7: 1 | d | var d *github.com/goplus/gop/cl/internal/overload/bar.Var__1[map[string]any] +== uses == +000: 4: 7 | bar | package bar ("github.com/goplus/gop/cl/internal/overload/bar") +001: 4:11 | Var | type github.com/goplus/gop/cl/internal/overload/bar.Var__0[T github.com/goplus/gop/cl/internal/overload/bar.basetype] struct{val T} +002: 4:15 | int | type int +003: 5: 7 | bar | package bar ("github.com/goplus/gop/cl/internal/overload/bar") +004: 5:11 | Var | type github.com/goplus/gop/cl/internal/overload/bar.Var__1[T map[string]any] struct{val T} +005: 5:15 | bar | package bar ("github.com/goplus/gop/cl/internal/overload/bar") +006: 5:19 | M | type github.com/goplus/gop/cl/internal/overload/bar.M = map[string]any +007: 6: 6 | bar | package bar ("github.com/goplus/gop/cl/internal/overload/bar") +008: 6:10 | Var | func github.com/goplus/gop/cl/internal/overload/bar.Gopx_Var_Cast__0[T github.com/goplus/gop/cl/internal/overload/bar.basetype]() *github.com/goplus/gop/cl/internal/overload/bar.Var__0[T] +009: 6:14 | string | type string +010: 7: 6 | bar | package bar ("github.com/goplus/gop/cl/internal/overload/bar") +011: 7:10 | Var | func github.com/goplus/gop/cl/internal/overload/bar.Gopx_Var_Cast__1[T map[string]any]() *github.com/goplus/gop/cl/internal/overload/bar.Var__1[T] +012: 7:14 | bar | package bar ("github.com/goplus/gop/cl/internal/overload/bar") +013: 7:18 | M | type github.com/goplus/gop/cl/internal/overload/bar.M = map[string]any`) +} + +func TestMixedOverloadNamed(t *testing.T) { + //gox.SetDebug(gox.DbgFlagAll) + testGopInfo(t, ` +var a Var[int] +var b Var[M] +c := Var(string) +d := Var(M) +`, ` +package main + +const GopPackage = true + +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 == +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[map[string]interface{}] | type +004: 3: 7 | Var[M] *ast.IndexExpr | type : main.Var__1[map[string]interface{}] | type +005: 3:11 | M *ast.Ident | type : map[string]interface{} | 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[map[string]interface{}] | value +011: 5:10 | M *ast.Ident | type : map[string]interface{} | type +== defs == +000: 2: 5 | a | var main.a main.Var__0[int] +001: 3: 5 | b | var main.b main.Var__1[map[string]interface{}] +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[map[string]interface{}] +== 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`) +} + +func TestMixedRawNamed(t *testing.T) { + //gox.SetDebug(gox.DbgFlagAll) + 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 + +const GopPackage = true + +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 == +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[map[string]interface{}] | type +004: 3: 7 | Var__1[M] *ast.IndexExpr | type : main.Var__1[map[string]interface{}] | type +005: 3:14 | M *ast.Ident | type : map[string]interface{} | 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[map[string]interface{}] | variable +011: 5:23 | M *ast.Ident | type : map[string]interface{} | type +== defs == +000: 2: 5 | a | var main.a main.Var__0[int] +001: 3: 5 | b | var main.b main.Var__1[map[string]interface{}] +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[map[string]interface{}] +== 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`) +}