diff --git a/errors_test.go b/errors_test.go index 1ee9a0e18..9c478efbb 100644 --- a/errors_test.go +++ b/errors_test.go @@ -17,6 +17,7 @@ import ( func TestErrors(t *testing.T) { for _, name := range []string{ "cgo", + "multi", "loader-importcycle", "loader-invaliddep", "loader-invalidpackage", diff --git a/loader/loader.go b/loader/loader.go index fe75e6c9b..314a513d1 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -318,18 +318,34 @@ func (p *Program) MainPkg() *Package { func (p *Program) Parse() error { // Parse all packages. // TODO: do this in parallel. + var errors []error for _, pkg := range p.sorted { err := pkg.Parse() if err != nil { - return err + errors = append(errors, err) } } // Typecheck all packages. for _, pkg := range p.sorted { + if !pkg.isParsed() || !pkg.allImportsChecked() { + if len(errors) == 0 { + // Sanity check. + // If there are no errors, all packages should have been parsed. + panic("unreachable") + } + continue + } err := pkg.Check() if err != nil { - return err + errors = append(errors, err) + } + } + + if len(errors) != 0 { + // TODO: use errors.Join in Go 1.20. + return Errors{ + Errs: errors, } } @@ -354,12 +370,12 @@ func (p *Package) parseFile(path string, mode parser.Mode) (*ast.File, error) { return parser.ParseFile(p.program.fset, originalPath, data, mode) } -// Parse parses and typechecks this package. +// Parse parses this package. // // Idempotent. func (p *Package) Parse() error { - if len(p.Files) != 0 { - return nil // nothing to do (?) + if p.isParsed() { + return nil // nothing to do } // Load the AST. @@ -379,6 +395,24 @@ func (p *Package) Parse() error { return nil } +// isParsed returns whether this package has been parsed. +func (p *Package) isParsed() bool { + // Special case: the unsafe package doesn't have files to parse but does + // have p.Pkg set once it is parsed. + return len(p.Files) != 0 || p.Pkg != nil +} + +// allImportsChecked returns whether all imports of this package have been +// type-checked. +func (p *Package) allImportsChecked() bool { + for _, dep := range p.Imports { + if p.program.Packages[dep].Pkg == nil { + return false + } + } + return true +} + // Check runs the package through the typechecker. The package must already be // loaded and all dependencies must have been checked already. // diff --git a/testdata/errors/multi-1/types.go b/testdata/errors/multi-1/types.go new file mode 100644 index 000000000..8fe34d3c5 --- /dev/null +++ b/testdata/errors/multi-1/types.go @@ -0,0 +1,3 @@ +package typesmulti3 + +var _ int = 3.14 diff --git a/testdata/errors/multi-2/syntax.go b/testdata/errors/multi-2/syntax.go new file mode 100644 index 000000000..42caf8cab --- /dev/null +++ b/testdata/errors/multi-2/syntax.go @@ -0,0 +1,4 @@ +package syntaxmulti2 + +func Foo2(type) { +} diff --git a/testdata/errors/multi-3/syntax.go b/testdata/errors/multi-3/syntax.go new file mode 100644 index 000000000..0ba5921b1 --- /dev/null +++ b/testdata/errors/multi-3/syntax.go @@ -0,0 +1,4 @@ +package syntaxmulti1 + +func Foo1(import) { +} diff --git a/testdata/errors/multi.go b/testdata/errors/multi.go new file mode 100644 index 000000000..628326154 --- /dev/null +++ b/testdata/errors/multi.go @@ -0,0 +1,17 @@ +package main + +import ( + _ "github.com/tinygo-org/tinygo/testdata/errors/multi-1" + _ "github.com/tinygo-org/tinygo/testdata/errors/multi-2" + _ "github.com/tinygo-org/tinygo/testdata/errors/multi-3" +) + +func main() { +} + +// ERROR: # github.com/tinygo-org/tinygo/testdata/errors/multi-2 +// ERROR: multi-2/syntax.go:3:11: expected ')', found 'type' +// ERROR: # github.com/tinygo-org/tinygo/testdata/errors/multi-3 +// ERROR: multi-3/syntax.go:3:11: expected ')', found 'import' +// ERROR: # github.com/tinygo-org/tinygo/testdata/errors/multi-1 +// ERROR: multi-1/types.go:3:13: cannot use 3.14 (untyped float constant) as int value in variable declaration (truncated)