diff --git a/ast/import.go b/ast/import.go index ea877fe7d..a6023c769 100644 --- a/ast/import.go +++ b/ast/import.go @@ -31,6 +31,7 @@ import ( type ImportDeclaration struct { Location common.Location Identifiers []Identifier + Aliases map[string]string Range LocationPos Position } @@ -41,6 +42,7 @@ var _ Declaration = &ImportDeclaration{} func NewImportDeclaration( gauge common.MemoryGauge, identifiers []Identifier, + aliases map[string]string, location common.Location, declRange Range, locationPos Position, @@ -49,6 +51,7 @@ func NewImportDeclaration( return &ImportDeclaration{ Identifiers: identifiers, + Aliases: aliases, Location: location, Range: declRange, LocationPos: locationPos, diff --git a/ast/import_test.go b/ast/import_test.go index 2b6490f8e..7a221970d 100644 --- a/ast/import_test.go +++ b/ast/import_test.go @@ -40,6 +40,9 @@ func TestImportDeclaration_MarshalJSON(t *testing.T) { Pos: Position{Offset: 1, Line: 2, Column: 3}, }, }, + Aliases: map[string]string{ + "foo": "bar", + }, Location: common.StringLocation("test"), LocationPos: Position{Offset: 4, Line: 5, Column: 6}, Range: Range{ @@ -63,6 +66,9 @@ func TestImportDeclaration_MarshalJSON(t *testing.T) { "EndPos": {"Offset": 3, "Line": 2, "Column": 5} } ], + "Aliases": { + "foo": "bar" + }, "Location": { "Type": "StringLocation", "String": "test" diff --git a/interpreter/import_test.go b/interpreter/import_test.go index b1599e2a0..59b53df97 100644 --- a/interpreter/import_test.go +++ b/interpreter/import_test.go @@ -401,3 +401,236 @@ func TestInterpretResourceConstructionThroughIndirectImport(t *testing.T) { resourceConstructionError.CompositeType, ) } + +// TestInterpretImportWithAlias shows importing two funs of the same name from different addresses +func TestInterpretImportWithAlias(t *testing.T) { + + t.Parallel() + + address := common.MustBytesToAddress([]byte{0x1}) + address2 := common.MustBytesToAddress([]byte{0x2}) + + importedCheckerA, err := ParseAndCheckWithOptions(t, + ` + access(all) fun a(): Int { + return 1 + } + `, + ParseAndCheckOptions{ + Location: common.AddressLocation{ + Address: address, + Name: "", + }, + }, + ) + require.NoError(t, err) + + importedCheckerB, err := ParseAndCheckWithOptions(t, + ` + access(all) fun a(): Int { + return 2 + } + `, + ParseAndCheckOptions{ + Location: common.AddressLocation{ + Address: address2, + Name: "", + }, + }, + ) + require.NoError(t, err) + + importingChecker, err := ParseAndCheckWithOptions(t, + ` + import a as a1 from 0x1 + import a as a2 from 0x2 + + access(all) fun test(): Int { + return a1() + a2() + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + LocationHandler: func(identifiers []ast.Identifier, location common.Location) (result []sema.ResolvedLocation, err error) { + + for _, identifier := range identifiers { + result = append(result, sema.ResolvedLocation{ + Location: common.AddressLocation{ + Address: location.(common.AddressLocation).Address, + Name: identifier.Identifier, + }, + Identifiers: []ast.Identifier{ + identifier, + }, + }) + } + return + }, + ImportHandler: func(checker *sema.Checker, importedLocation common.Location, _ ast.Range) (sema.Import, error) { + require.IsType(t, common.AddressLocation{}, importedLocation) + addressLocation := importedLocation.(common.AddressLocation) + + var importedChecker *sema.Checker + + switch addressLocation.Address { + case address: + importedChecker = importedCheckerA + case address2: + importedChecker = importedCheckerB + default: + t.Errorf( + "invalid address location location name: %s", + addressLocation.Name, + ) + } + + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(t, err) + + storage := newUnmeteredInMemoryStorage() + + inter, err := interpreter.NewInterpreter( + interpreter.ProgramFromChecker(importingChecker), + importingChecker.Location, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + require.IsType(t, common.AddressLocation{}, location) + addressLocation := location.(common.AddressLocation) + + var importedChecker *sema.Checker + + switch addressLocation.Address { + case address: + importedChecker = importedCheckerA + case address2: + importedChecker = importedCheckerB + default: + return nil + } + + program := interpreter.ProgramFromChecker(importedChecker) + subInterpreter, err := inter.NewSubInterpreter(program, location) + if err != nil { + panic(err) + } + + return interpreter.InterpreterImport{ + Interpreter: subInterpreter, + } + }, + }, + ) + require.NoError(t, err) + + err = inter.Interpret() + require.NoError(t, err) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredIntValueFromInt64(3), + value, + ) +} + +func TestInterpretImportAliasGetType(t *testing.T) { + + t.Parallel() + + address := common.MustBytesToAddress([]byte{0x1}) + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + access(all) struct Foo { + } + `, + ParseAndCheckOptions{ + Location: common.AddressLocation{ + Address: address, + Name: "", + }, + }, + ) + require.NoError(t, err) + + importingChecker, err := ParseAndCheckWithOptions(t, + ` + import Foo as Bar from 0x1 + + access(all) fun test(): String { + var bar: Bar = Bar() + return bar.getType().identifier + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + LocationHandler: func(identifiers []ast.Identifier, location common.Location) (result []sema.ResolvedLocation, err error) { + + for _, identifier := range identifiers { + result = append(result, sema.ResolvedLocation{ + Location: common.AddressLocation{ + Address: location.(common.AddressLocation).Address, + Name: identifier.Identifier, + }, + Identifiers: []ast.Identifier{ + identifier, + }, + }) + } + return + }, + ImportHandler: func(checker *sema.Checker, importedLocation common.Location, _ ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + require.NoError(t, err) + + storage := newUnmeteredInMemoryStorage() + + inter, err := interpreter.NewInterpreter( + interpreter.ProgramFromChecker(importingChecker), + importingChecker.Location, + &interpreter.Config{ + Storage: storage, + ImportLocationHandler: func(inter *interpreter.Interpreter, location common.Location) interpreter.Import { + program := interpreter.ProgramFromChecker(importedChecker) + subInterpreter, err := inter.NewSubInterpreter(program, location) + if err != nil { + panic(err) + } + + return interpreter.InterpreterImport{ + Interpreter: subInterpreter, + } + }, + }, + ) + require.NoError(t, err) + + err = inter.Interpret() + require.NoError(t, err) + + value, err := inter.Invoke("test") + require.NoError(t, err) + + AssertValuesEqual( + t, + inter, + interpreter.NewUnmeteredStringValue("A.0000000000000001.Foo"), + value, + ) +} diff --git a/interpreter/interpreter_import.go b/interpreter/interpreter_import.go index 4d9296241..ff3d50b29 100644 --- a/interpreter/interpreter_import.go +++ b/interpreter/interpreter_import.go @@ -32,13 +32,13 @@ func (interpreter *Interpreter) VisitImportDeclaration(declaration *ast.ImportDe resolvedLocations := interpreter.Program.Elaboration.ImportDeclarationsResolvedLocations(declaration) for _, resolvedLocation := range resolvedLocations { - interpreter.importResolvedLocation(resolvedLocation) + interpreter.importResolvedLocation(resolvedLocation, declaration.Aliases) } return nil } -func (interpreter *Interpreter) importResolvedLocation(resolvedLocation sema.ResolvedLocation) { +func (interpreter *Interpreter) importResolvedLocation(resolvedLocation sema.ResolvedLocation, aliases map[string]string) { config := interpreter.SharedState.Config // tracing @@ -62,7 +62,14 @@ func (interpreter *Interpreter) importResolvedLocation(resolvedLocation sema.Res if identifierLength > 0 { variables = make(map[string]Variable, identifierLength) for _, identifier := range resolvedLocation.Identifiers { - variables[identifier.Identifier] = + name := identifier.Identifier + alias, ok := aliases[name] + if ok { + name = alias + } + + // map alias to original + variables[name] = subInterpreter.Globals.Get(identifier.Identifier) } } else { diff --git a/old_parser/declaration.go b/old_parser/declaration.go index ac34e9879..101d60497 100644 --- a/old_parser/declaration.go +++ b/old_parser/declaration.go @@ -691,6 +691,7 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { return ast.NewImportDeclaration( p.memoryGauge, identifiers, + nil, location, ast.NewRange( p.memoryGauge, diff --git a/parser/declaration.go b/parser/declaration.go index 511bb9ecd..afde6f6f6 100644 --- a/parser/declaration.go +++ b/parser/declaration.go @@ -645,13 +645,14 @@ func parsePragmaDeclaration(p *parser) (*ast.PragmaDeclaration, error) { // // importDeclaration : // 'import' -// ( identifier (',' identifier)* 'from' )? +// ( identifier ('as' identifier)? (',' identifier ('as' identifier)?)* 'from' )? // ( string | hexadecimalLiteral | identifier ) func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { startPosition := p.current.StartPos var identifiers []ast.Identifier + var aliases map[string]string var location common.Location var locationPos ast.Position @@ -684,6 +685,36 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { endPos = identifier.EndPosition(p.memoryGauge) } + // pass in the identifier which would be aliased + parseOptionalImportAlias := func(identifier ast.Identifier) error { + // stop early if the current token is not as + if p.current.Type != lexer.TokenIdentifier || string(p.currentTokenSource()) != KeywordAs { + return nil + } + // Skip the `as` keyword + p.nextSemanticToken() + + // lazy initialize alias map + if aliases == nil { + aliases = make(map[string]string) + } + + switch p.current.Type { + case lexer.TokenIdentifier: + identifierAlias := p.tokenToIdentifier(p.current) + aliases[identifier.Identifier] = identifierAlias.Identifier + + // Skip the alias + p.nextSemanticToken() + default: + return p.syntaxError( + "expected identifier in import alias: got %s", + p.current.Type, + ) + } + return nil + } + parseLocation := func() error { switch p.current.Type { case lexer.TokenString, lexer.TokenHexadecimalIntegerLiteral: @@ -705,12 +736,10 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { } parseMoreIdentifiers := func() error { - expectCommaOrFrom := false + expectCommaOrFrom := true atEnd := false for !atEnd { - p.nextSemanticToken() - switch p.current.Type { case lexer.TokenComma: if !expectCommaOrFrom { @@ -722,6 +751,7 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { ) } expectCommaOrFrom = false + p.nextSemanticToken() case lexer.TokenIdentifier: @@ -755,6 +785,13 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { identifier := p.tokenToIdentifier(p.current) identifiers = append(identifiers, identifier) + p.nextSemanticToken() + + // Parse optional alias + err := parseOptionalImportAlias(identifier) + if err != nil { + return err + } expectCommaOrFrom = true @@ -813,6 +850,11 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { identifier := p.tokenToIdentifier(p.current) // Skip the identifier p.nextSemanticToken() + // Parse optional alias + err := parseOptionalImportAlias(identifier) + if err != nil { + return nil, err + } switch p.current.Type { case lexer.TokenComma: @@ -854,6 +896,7 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { return ast.NewImportDeclaration( p.memoryGauge, identifiers, + aliases, location, ast.NewRange( p.memoryGauge, diff --git a/parser/declaration_test.go b/parser/declaration_test.go index 681d5cb59..216b149e5 100644 --- a/parser/declaration_test.go +++ b/parser/declaration_test.go @@ -2431,6 +2431,200 @@ func TestParseImportDeclaration(t *testing.T) { result, ) }) + + t.Run("single import alias", func(t *testing.T) { + + t.Parallel() + + result, errs := testParseDeclarations(` + import foo as bar, lorem from 0x42 + `) + require.Empty(t, errs) + + AssertEqualWithDiff(t, + []ast.Declaration{ + &ast.ImportDeclaration{ + Identifiers: []ast.Identifier{ + { + Identifier: "foo", + Pos: ast.Position{Line: 2, Column: 10, Offset: 11}, + }, + { + Identifier: "lorem", + Pos: ast.Position{Line: 2, Column: 22, Offset: 23}, + }, + }, + Aliases: map[string]string{ + "foo": "bar", + }, + Location: common.AddressLocation{ + Address: common.MustBytesToAddress([]byte{0x42}), + }, + LocationPos: ast.Position{Line: 2, Column: 33, Offset: 34}, + Range: ast.Range{ + StartPos: ast.Position{Line: 2, Column: 3, Offset: 4}, + EndPos: ast.Position{Line: 2, Column: 36, Offset: 37}, + }, + }, + }, + result, + ) + }) + + t.Run("multiple import alias", func(t *testing.T) { + + t.Parallel() + + result, errs := testParseDeclarations(` + import foo as bar, lorem as ipsum from 0x42 + `) + require.Empty(t, errs) + + AssertEqualWithDiff(t, + []ast.Declaration{ + &ast.ImportDeclaration{ + Identifiers: []ast.Identifier{ + { + Identifier: "foo", + Pos: ast.Position{Line: 2, Column: 10, Offset: 11}, + }, + { + Identifier: "lorem", + Pos: ast.Position{Line: 2, Column: 22, Offset: 23}, + }, + }, + Aliases: map[string]string{ + "foo": "bar", + "lorem": "ipsum", + }, + Location: common.AddressLocation{ + Address: common.MustBytesToAddress([]byte{0x42}), + }, + LocationPos: ast.Position{Line: 2, Column: 42, Offset: 43}, + Range: ast.Range{ + StartPos: ast.Position{Line: 2, Column: 3, Offset: 4}, + EndPos: ast.Position{Line: 2, Column: 45, Offset: 46}, + }, + }, + }, + result, + ) + }) + + t.Run("combination import aliases", func(t *testing.T) { + + t.Parallel() + + result, errs := testParseDeclarations(` + import foo as bar, test as from, from from 0x42 + `) + require.Empty(t, errs) + + AssertEqualWithDiff(t, + []ast.Declaration{ + &ast.ImportDeclaration{ + Identifiers: []ast.Identifier{ + { + Identifier: "foo", + Pos: ast.Position{Line: 2, Column: 10, Offset: 11}, + }, + { + Identifier: "test", + Pos: ast.Position{Line: 2, Column: 22, Offset: 23}, + }, + { + Identifier: "from", + Pos: ast.Position{Line: 2, Column: 36, Offset: 37}, + }, + }, + Aliases: map[string]string{ + "foo": "bar", + "test": "from", + }, + Location: common.AddressLocation{ + Address: common.MustBytesToAddress([]byte{0x42}), + }, + LocationPos: ast.Position{Line: 2, Column: 46, Offset: 47}, + Range: ast.Range{ + StartPos: ast.Position{Line: 2, Column: 3, Offset: 4}, + EndPos: ast.Position{Line: 2, Column: 49, Offset: 50}, + }, + }, + }, + result, + ) + }) + + t.Run("alias same imported function", func(t *testing.T) { + + t.Parallel() + + result, errs := testParseDeclarations(` + import foo as bar from 0x42 + import foo as cab from 0x42 + `) + require.Empty(t, errs) + + AssertEqualWithDiff(t, + []ast.Declaration{ + &ast.ImportDeclaration{ + Identifiers: []ast.Identifier{ + { + Identifier: "foo", + Pos: ast.Position{Line: 2, Column: 10, Offset: 11}, + }, + }, + Aliases: map[string]string{ + "foo": "bar", + }, + Location: common.AddressLocation{ + Address: common.MustBytesToAddress([]byte{0x42}), + }, + LocationPos: ast.Position{Line: 2, Column: 26, Offset: 27}, + Range: ast.Range{ + StartPos: ast.Position{Line: 2, Column: 3, Offset: 4}, + EndPos: ast.Position{Line: 2, Column: 29, Offset: 30}, + }, + }, + &ast.ImportDeclaration{ + Identifiers: []ast.Identifier{ + { + Identifier: "foo", + Pos: ast.Position{Line: 3, Column: 10, Offset: 42}, + }, + }, + Aliases: map[string]string{ + "foo": "cab", + }, + Location: common.AddressLocation{ + Address: common.MustBytesToAddress([]byte{0x42}), + }, + LocationPos: ast.Position{Line: 3, Column: 26, Offset: 58}, + Range: ast.Range{ + StartPos: ast.Position{Line: 3, Column: 3, Offset: 35}, + EndPos: ast.Position{Line: 3, Column: 29, Offset: 61}, + }, + }, + }, + result, + ) + }) + + t.Run("invalid, non identifier alias", func(t *testing.T) { + + t.Parallel() + + _, errs := testParseDeclarations(` + import foo as 1 from 0x42 + `) + + AssertEqualWithDiff(t, []error{ + &SyntaxError{ + Pos: ast.Position{Line: 2, Column: 17, Offset: 18}, + Message: `expected identifier in import alias: got decimal integer`, + }, + }, errs) + }) } func TestParseEvent(t *testing.T) { diff --git a/sema/check_import_declaration.go b/sema/check_import_declaration.go index 0dc96d8d4..fabf48563 100644 --- a/sema/check_import_declaration.go +++ b/sema/check_import_declaration.go @@ -89,7 +89,7 @@ func (checker *Checker) declareImportDeclaration(declaration *ast.ImportDeclarat checker.Elaboration.SetImportDeclarationsResolvedLocations(declaration, resolvedLocations) for _, resolvedLocation := range resolvedLocations { - checker.importResolvedLocation(resolvedLocation, locationRange) + checker.importResolvedLocation(resolvedLocation, locationRange, declaration.Aliases) } } @@ -113,7 +113,7 @@ func (checker *Checker) resolveLocation(identifiers []ast.Identifier, location c return locationHandler(identifiers, location) } -func (checker *Checker) importResolvedLocation(resolvedLocation ResolvedLocation, locationRange ast.Range) { +func (checker *Checker) importResolvedLocation(resolvedLocation ResolvedLocation, locationRange ast.Range, aliases map[string]string) { // First, get the Import for the resolved location @@ -178,6 +178,7 @@ func (checker *Checker) importResolvedLocation(resolvedLocation ResolvedLocation checker.valueActivations, resolvedLocation.Identifiers, allValueElements, + aliases, true, ) @@ -188,6 +189,7 @@ func (checker *Checker) importResolvedLocation(resolvedLocation ResolvedLocation checker.typeActivations, resolvedLocation.Identifiers, allTypeElements, + aliases, false, ) @@ -302,6 +304,7 @@ func (checker *Checker) importElements( valueActivations *VariableActivations, requestedIdentifiers []ast.Identifier, availableElements *StringImportElementOrderedMap, + aliases map[string]string, importValues bool, ) ( found map[ast.Identifier]bool, @@ -326,6 +329,10 @@ func (checker *Checker) importElements( if !ok { continue } + alias, ok := aliases[name] + if ok { + name = alias + } elements.Set(name, element) found[identifier] = true explicitlyImported[name] = identifier diff --git a/sema/import_test.go b/sema/import_test.go index c5bf92d36..4e7ea5d8f 100644 --- a/sema/import_test.go +++ b/sema/import_test.go @@ -824,3 +824,236 @@ func TestCheckImportContract(t *testing.T) { }) } + +func TestCheckImportAlias(t *testing.T) { + + t.Parallel() + + t.Run("valid contract import", func(t *testing.T) { + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + access(all) contract Foo { + access(all) let x: [Int] + + access(all) fun answer(): Int { + return 42 + } + + access(all) struct Bar {} + + init() { + self.x = [] + } + } + `, + ParseAndCheckOptions{ + Location: ImportedLocation, + }, + ) + + require.NoError(t, err) + + _, err = ParseAndCheckWithOptions(t, + ` + import Foo as Bar from "imported" + + access(all) fun main() { + var foo: &Bar = Bar + var x: &[Int] = Bar.x + var bar: Bar.Bar = Bar.Bar() + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, _ common.Location, _ ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + + require.NoError(t, err) + }) + + t.Run("valid multiple alias of same contract", func(t *testing.T) { + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + access(all) contract Foo { + access(all) let x: [Int] + + access(all) fun answer(): Int { + return 42 + } + + access(all) struct Bar {} + + init() { + self.x = [] + } + } + `, + ParseAndCheckOptions{ + Location: ImportedLocation, + }, + ) + + require.NoError(t, err) + + _, err = ParseAndCheckWithOptions(t, + ` + import Foo as Bar from "imported" + import Foo as Cab from "imported" + + access(all) fun main() { + var foo: &Cab = Cab + var x: &[Int] = Bar.x + var bar: Cab.Bar = Cab.Bar() + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, _ common.Location, _ ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + + require.NoError(t, err) + }) + + t.Run("invalid duplicate aliases", func(t *testing.T) { + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + access(all) fun a(): Int { + return 42 + } + + access(all) fun b(): Int { + return 50 + } + `, + ParseAndCheckOptions{ + Location: ImportedLocation, + }, + ) + + require.NoError(t, err) + + _, err = ParseAndCheckWithOptions(t, + ` + import a as c from "imported" + import b as c from "imported" + + access(all) fun main() { + c() + c() + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, _ common.Location, _ ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + + errs := RequireCheckerErrors(t, err, 1) + + redeclarationError := &sema.RedeclarationError{} + assert.ErrorAs(t, errs[0], &redeclarationError) + + }) + + t.Run("invalid missing aliased import", func(t *testing.T) { + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + access(all) fun a(): Int { + return 42 + } + `, + ParseAndCheckOptions{ + Location: ImportedLocation, + }, + ) + + require.NoError(t, err) + + _, err = ParseAndCheckWithOptions(t, + ` + import c as a from "imported" + + access(all) fun main() { + c() + c() + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, _ common.Location, _ ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + + errs := RequireCheckerErrors(t, err, 1) + + notExportedError := &sema.NotExportedError{} + assert.ErrorAs(t, errs[0], ¬ExportedError) + + }) + + t.Run("invalid use orig instead of alias", func(t *testing.T) { + + importedChecker, err := ParseAndCheckWithOptions(t, + ` + access(all) fun a(): Int { + return 42 + } + `, + ParseAndCheckOptions{ + Location: ImportedLocation, + }, + ) + + require.NoError(t, err) + + _, err = ParseAndCheckWithOptions(t, + ` + import a as b from "imported" + + access(all) fun main() { + a() + } + `, + ParseAndCheckOptions{ + Config: &sema.Config{ + ImportHandler: func(_ *sema.Checker, _ common.Location, _ ast.Range) (sema.Import, error) { + return sema.ElaborationImport{ + Elaboration: importedChecker.Elaboration, + }, nil + }, + }, + }, + ) + + errs := RequireCheckerErrors(t, err, 1) + + notDeclaredError := &sema.NotDeclaredError{} + assert.ErrorAs(t, errs[0], ¬DeclaredError) + + }) + +}