From 9d503abd5666a108149e8b0631f1053dfb67b6b2 Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Fri, 13 Dec 2024 12:37:56 -0500 Subject: [PATCH 1/8] Add test for import aliasing. --- interpreter/import_test.go | 141 +++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/interpreter/import_test.go b/interpreter/import_test.go index b1599e2a0..6c8206b1a 100644 --- a/interpreter/import_test.go +++ b/interpreter/import_test.go @@ -401,3 +401,144 @@ 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 a() + a() + } + `, + 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, + ) +} From 027432ca8f5cb2fc67cb0721ba5c18757fe78f73 Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Fri, 13 Dec 2024 15:42:02 -0500 Subject: [PATCH 2/8] Add initial parsing changes to support import aliases. --- parser/declaration.go | 31 ++++++++++++++++++++++++++++++- parser/declaration_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/parser/declaration.go b/parser/declaration.go index 511bb9ecd..06e751eac 100644 --- a/parser/declaration.go +++ b/parser/declaration.go @@ -645,7 +645,7 @@ 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) { @@ -684,6 +684,30 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { endPos = identifier.EndPosition(p.memoryGauge) } + parseOptionalImportAlias := func() error { + // stop early if the current token is not as + if string(p.currentTokenSource()) != KeywordAs { + return nil + } + // Skip the `as` keyword + p.nextSemanticToken() + + switch p.current.Type { + case lexer.TokenIdentifier: + identifierAlias := p.tokenToIdentifier(p.current) + identifiers = append(identifiers, identifierAlias) + + // Skip the alias + p.nextSemanticToken() + default: + return p.syntaxError( + "expected identifer in import alias: got %s", + p.current.Type, + ) + } + return nil + } + parseLocation := func() error { switch p.current.Type { case lexer.TokenString, lexer.TokenHexadecimalIntegerLiteral: @@ -711,6 +735,9 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { for !atEnd { p.nextSemanticToken() + // Parse optional alias + parseOptionalImportAlias() + switch p.current.Type { case lexer.TokenComma: if !expectCommaOrFrom { @@ -813,6 +840,8 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { identifier := p.tokenToIdentifier(p.current) // Skip the identifier p.nextSemanticToken() + // Parse optional alias + parseOptionalImportAlias() switch p.current.Type { case lexer.TokenComma: diff --git a/parser/declaration_test.go b/parser/declaration_test.go index 681d5cb59..d8b1e8b8b 100644 --- a/parser/declaration_test.go +++ b/parser/declaration_test.go @@ -2431,6 +2431,42 @@ func TestParseImportDeclaration(t *testing.T) { result, ) }) + + t.Run("single import alias", func(t *testing.T) { + + t.Parallel() + + _, errs := testParseDeclarations(` + import foo as bar, lorem from 0x42 + `) + require.Empty(t, errs) + + // assert result after deciding how to parse + }) + + t.Run("multiple import alias", func(t *testing.T) { + + t.Parallel() + + _, errs := testParseDeclarations(` + import foo as bar, lorem as ipsum from 0x42 + `) + require.Empty(t, errs) + + // assert result after deciding how to parse + }) + + t.Run("combination import aliases", func(t *testing.T) { + + t.Parallel() + + _, errs := testParseDeclarations(` + import foo as bar, test as from, from from 0x42 + `) + require.Empty(t, errs) + + // assert result after deciding how to parse + }) } func TestParseEvent(t *testing.T) { From 739a8a9988dbb6d872bc57d41e19f3668271a2a1 Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Mon, 16 Dec 2024 12:51:27 -0500 Subject: [PATCH 3/8] Add map of aliases into import declaration, update tests. --- ast/import.go | 3 ++ parser/declaration.go | 21 ++++---- parser/declaration_test.go | 99 +++++++++++++++++++++++++++++++++++--- 3 files changed, 108 insertions(+), 15 deletions(-) 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/parser/declaration.go b/parser/declaration.go index 06e751eac..0ed51f781 100644 --- a/parser/declaration.go +++ b/parser/declaration.go @@ -652,6 +652,7 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { startPosition := p.current.StartPos var identifiers []ast.Identifier + aliases := make(map[string]string) var location common.Location var locationPos ast.Position @@ -684,7 +685,8 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { endPos = identifier.EndPosition(p.memoryGauge) } - parseOptionalImportAlias := func() error { + // pass in the identifier which would be aliased + parseOptionalImportAlias := func(identifier ast.Identifier) error { // stop early if the current token is not as if string(p.currentTokenSource()) != KeywordAs { return nil @@ -695,7 +697,7 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { switch p.current.Type { case lexer.TokenIdentifier: identifierAlias := p.tokenToIdentifier(p.current) - identifiers = append(identifiers, identifierAlias) + aliases[identifier.Identifier] = identifierAlias.Identifier // Skip the alias p.nextSemanticToken() @@ -729,15 +731,10 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { } parseMoreIdentifiers := func() error { - expectCommaOrFrom := false + expectCommaOrFrom := true atEnd := false for !atEnd { - p.nextSemanticToken() - - // Parse optional alias - parseOptionalImportAlias() - switch p.current.Type { case lexer.TokenComma: if !expectCommaOrFrom { @@ -749,6 +746,7 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { ) } expectCommaOrFrom = false + p.nextSemanticToken() case lexer.TokenIdentifier: @@ -782,6 +780,10 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { identifier := p.tokenToIdentifier(p.current) identifiers = append(identifiers, identifier) + p.nextSemanticToken() + + // Parse optional alias + parseOptionalImportAlias(identifier) expectCommaOrFrom = true @@ -841,7 +843,7 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { // Skip the identifier p.nextSemanticToken() // Parse optional alias - parseOptionalImportAlias() + parseOptionalImportAlias(identifier) switch p.current.Type { case lexer.TokenComma: @@ -883,6 +885,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 d8b1e8b8b..24f220f83 100644 --- a/parser/declaration_test.go +++ b/parser/declaration_test.go @@ -2436,36 +2436,123 @@ func TestParseImportDeclaration(t *testing.T) { t.Parallel() - _, errs := testParseDeclarations(` + result, errs := testParseDeclarations(` import foo as bar, lorem from 0x42 `) require.Empty(t, errs) - // assert result after deciding how to parse + 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() - _, errs := testParseDeclarations(` + result, errs := testParseDeclarations(` import foo as bar, lorem as ipsum from 0x42 `) require.Empty(t, errs) - // assert result after deciding how to parse + 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() - _, errs := testParseDeclarations(` + result, errs := testParseDeclarations(` import foo as bar, test as from, from from 0x42 `) require.Empty(t, errs) - // assert result after deciding how to parse + 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, + ) }) } From efc00c04a746abf939a02ba65b03eea03e781087 Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Mon, 16 Dec 2024 15:21:00 -0500 Subject: [PATCH 4/8] Update checker for import aliases. --- old_parser/declaration.go | 1 + parser/declaration_test.go | 55 +++++++++ sema/check_import_declaration.go | 11 +- sema/import_test.go | 192 +++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+), 2 deletions(-) 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_test.go b/parser/declaration_test.go index 24f220f83..cf53fc204 100644 --- a/parser/declaration_test.go +++ b/parser/declaration_test.go @@ -2554,6 +2554,61 @@ func TestParseImportDeclaration(t *testing.T) { 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, + ) + }) } func TestParseEvent(t *testing.T) { diff --git a/sema/check_import_declaration.go b/sema/check_import_declaration.go index 0dc96d8d4..78a6a6e47 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..d59923dbf 100644 --- a/sema/import_test.go +++ b/sema/import_test.go @@ -824,3 +824,195 @@ 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, 0) + + notExportedError := &sema.NotExportedError{} + assert.ErrorAs(t, errs[0], ¬ExportedError) + + }) + +} From e430f1b9e7052feaed40e7708bb768077978501c Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Mon, 16 Dec 2024 15:39:21 -0500 Subject: [PATCH 5/8] Add support for import aliasing in interpreter. --- interpreter/import_test.go | 2 +- interpreter/interpreter_import.go | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/interpreter/import_test.go b/interpreter/import_test.go index 6c8206b1a..c261b5656 100644 --- a/interpreter/import_test.go +++ b/interpreter/import_test.go @@ -446,7 +446,7 @@ func TestInterpretImportWithAlias(t *testing.T) { import a as a2 from 0x2 access(all) fun test(): Int { - return a() + a() + return a1() + a2() } `, ParseAndCheckOptions{ diff --git a/interpreter/interpreter_import.go b/interpreter/interpreter_import.go index 4d9296241..3d7f16983 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 { From 76a2aeca59141313f2098adb3bfc9e2c1b980aed Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Mon, 16 Dec 2024 16:05:33 -0500 Subject: [PATCH 6/8] Fix tests and linting. --- ast/import_test.go | 1 + parser/declaration.go | 21 ++++++++++++++++----- parser/declaration_test.go | 16 ++++++++++++++++ sema/import_test.go | 4 ++-- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/ast/import_test.go b/ast/import_test.go index 2b6490f8e..9badba2b0 100644 --- a/ast/import_test.go +++ b/ast/import_test.go @@ -63,6 +63,7 @@ func TestImportDeclaration_MarshalJSON(t *testing.T) { "EndPos": {"Offset": 3, "Line": 2, "Column": 5} } ], + "Aliases": null, "Location": { "Type": "StringLocation", "String": "test" diff --git a/parser/declaration.go b/parser/declaration.go index 0ed51f781..f78fc3544 100644 --- a/parser/declaration.go +++ b/parser/declaration.go @@ -652,7 +652,7 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { startPosition := p.current.StartPos var identifiers []ast.Identifier - aliases := make(map[string]string) + var aliases map[string]string var location common.Location var locationPos ast.Position @@ -688,12 +688,17 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { // pass in the identifier which would be aliased parseOptionalImportAlias := func(identifier ast.Identifier) error { // stop early if the current token is not as - if string(p.currentTokenSource()) != KeywordAs { + 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) @@ -703,7 +708,7 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { p.nextSemanticToken() default: return p.syntaxError( - "expected identifer in import alias: got %s", + "expected identifier in import alias: got %s", p.current.Type, ) } @@ -783,7 +788,10 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { p.nextSemanticToken() // Parse optional alias - parseOptionalImportAlias(identifier) + err := parseOptionalImportAlias(identifier) + if err != nil { + return err + } expectCommaOrFrom = true @@ -843,7 +851,10 @@ func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { // Skip the identifier p.nextSemanticToken() // Parse optional alias - parseOptionalImportAlias(identifier) + err := parseOptionalImportAlias(identifier) + if err != nil { + return nil, err + } switch p.current.Type { case lexer.TokenComma: diff --git a/parser/declaration_test.go b/parser/declaration_test.go index cf53fc204..216b149e5 100644 --- a/parser/declaration_test.go +++ b/parser/declaration_test.go @@ -2609,6 +2609,22 @@ func TestParseImportDeclaration(t *testing.T) { 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/import_test.go b/sema/import_test.go index d59923dbf..a9a71d715 100644 --- a/sema/import_test.go +++ b/sema/import_test.go @@ -973,7 +973,7 @@ func TestCheckImportAlias(t *testing.T) { }) - t.Run("invalid, missing aliased import", func(t *testing.T) { + t.Run("invalid missing aliased import", func(t *testing.T) { importedChecker, err := ParseAndCheckWithOptions(t, ` @@ -1008,7 +1008,7 @@ func TestCheckImportAlias(t *testing.T) { }, ) - errs := RequireCheckerErrors(t, err, 0) + errs := RequireCheckerErrors(t, err, 1) notExportedError := &sema.NotExportedError{} assert.ErrorAs(t, errs[0], ¬ExportedError) From 5689128262af6d044c81b3b3ce425b131b5bc91a Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Tue, 17 Dec 2024 11:11:06 -0500 Subject: [PATCH 7/8] Add invalid alias test case. --- sema/import_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/sema/import_test.go b/sema/import_test.go index a9a71d715..b424e2cfb 100644 --- a/sema/import_test.go +++ b/sema/import_test.go @@ -877,7 +877,7 @@ func TestCheckImportAlias(t *testing.T) { require.NoError(t, err) }) - t.Run("valid, multiple alias of same contract", func(t *testing.T) { + t.Run("valid multiple alias of same contract", func(t *testing.T) { importedChecker, err := ParseAndCheckWithOptions(t, ` @@ -926,7 +926,7 @@ func TestCheckImportAlias(t *testing.T) { require.NoError(t, err) }) - t.Run("invalid, duplicate aliases", func(t *testing.T) { + t.Run("invalid duplicate aliases", func(t *testing.T) { importedChecker, err := ParseAndCheckWithOptions(t, ` @@ -1015,4 +1015,46 @@ func TestCheckImportAlias(t *testing.T) { }) + 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) + + }) + } From ad709cb2624097c9284f5b47ee0670f065772090 Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Tue, 17 Dec 2024 16:54:51 -0500 Subject: [PATCH 8/8] Address comments. --- ast/import_test.go | 7 ++- interpreter/import_test.go | 92 +++++++++++++++++++++++++++++++ interpreter/interpreter_import.go | 6 +- parser/declaration.go | 2 +- sema/check_import_declaration.go | 8 +-- sema/import_test.go | 45 ++++++++------- 6 files changed, 128 insertions(+), 32 deletions(-) diff --git a/ast/import_test.go b/ast/import_test.go index 9badba2b0..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,7 +66,9 @@ func TestImportDeclaration_MarshalJSON(t *testing.T) { "EndPos": {"Offset": 3, "Line": 2, "Column": 5} } ], - "Aliases": null, + "Aliases": { + "foo": "bar" + }, "Location": { "Type": "StringLocation", "String": "test" diff --git a/interpreter/import_test.go b/interpreter/import_test.go index c261b5656..59b53df97 100644 --- a/interpreter/import_test.go +++ b/interpreter/import_test.go @@ -542,3 +542,95 @@ func TestInterpretImportWithAlias(t *testing.T) { 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 3d7f16983..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, &declaration.Aliases) + interpreter.importResolvedLocation(resolvedLocation, declaration.Aliases) } return nil } -func (interpreter *Interpreter) importResolvedLocation(resolvedLocation sema.ResolvedLocation, aliases *map[string]string) { +func (interpreter *Interpreter) importResolvedLocation(resolvedLocation sema.ResolvedLocation, aliases map[string]string) { config := interpreter.SharedState.Config // tracing @@ -63,7 +63,7 @@ func (interpreter *Interpreter) importResolvedLocation(resolvedLocation sema.Res variables = make(map[string]Variable, identifierLength) for _, identifier := range resolvedLocation.Identifiers { name := identifier.Identifier - alias, ok := (*aliases)[name] + alias, ok := aliases[name] if ok { name = alias } diff --git a/parser/declaration.go b/parser/declaration.go index f78fc3544..afde6f6f6 100644 --- a/parser/declaration.go +++ b/parser/declaration.go @@ -645,7 +645,7 @@ func parsePragmaDeclaration(p *parser) (*ast.PragmaDeclaration, error) { // // importDeclaration : // 'import' -// ( identifier (as identifier)? (',' identifier (as identifier)?)* 'from' )? +// ( identifier ('as' identifier)? (',' identifier ('as' identifier)?)* 'from' )? // ( string | hexadecimalLiteral | identifier ) func parseImportDeclaration(p *parser) (*ast.ImportDeclaration, error) { diff --git a/sema/check_import_declaration.go b/sema/check_import_declaration.go index 78a6a6e47..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, &declaration.Aliases) + 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, aliases *map[string]string) { +func (checker *Checker) importResolvedLocation(resolvedLocation ResolvedLocation, locationRange ast.Range, aliases map[string]string) { // First, get the Import for the resolved location @@ -304,7 +304,7 @@ func (checker *Checker) importElements( valueActivations *VariableActivations, requestedIdentifiers []ast.Identifier, availableElements *StringImportElementOrderedMap, - aliases *map[string]string, + aliases map[string]string, importValues bool, ) ( found map[ast.Identifier]bool, @@ -329,7 +329,7 @@ func (checker *Checker) importElements( if !ok { continue } - alias, ok := (*aliases)[name] + alias, ok := aliases[name] if ok { name = alias } diff --git a/sema/import_test.go b/sema/import_test.go index b424e2cfb..4e7ea5d8f 100644 --- a/sema/import_test.go +++ b/sema/import_test.go @@ -833,19 +833,20 @@ func TestCheckImportAlias(t *testing.T) { importedChecker, err := ParseAndCheckWithOptions(t, ` - access(all) contract Foo { - access(all) let x: [Int] + access(all) contract Foo { + access(all) let x: [Int] - access(all) fun answer(): Int { - return 42 - } + access(all) fun answer(): Int { + return 42 + } - access(all) struct Bar {} + access(all) struct Bar {} - init() { - self.x = [] - } - }`, + init() { + self.x = [] + } + } + `, ParseAndCheckOptions{ Location: ImportedLocation, }, @@ -881,19 +882,20 @@ func TestCheckImportAlias(t *testing.T) { importedChecker, err := ParseAndCheckWithOptions(t, ` - access(all) contract Foo { - access(all) let x: [Int] + access(all) contract Foo { + access(all) let x: [Int] - access(all) fun answer(): Int { - return 42 - } + access(all) fun answer(): Int { + return 42 + } - access(all) struct Bar {} + access(all) struct Bar {} - init() { - self.x = [] - } - }`, + init() { + self.x = [] + } + } + `, ParseAndCheckOptions{ Location: ImportedLocation, }, @@ -937,7 +939,6 @@ func TestCheckImportAlias(t *testing.T) { access(all) fun b(): Int { return 50 } - `, ParseAndCheckOptions{ Location: ImportedLocation, @@ -980,7 +981,6 @@ func TestCheckImportAlias(t *testing.T) { access(all) fun a(): Int { return 42 } - `, ParseAndCheckOptions{ Location: ImportedLocation, @@ -1022,7 +1022,6 @@ func TestCheckImportAlias(t *testing.T) { access(all) fun a(): Int { return 42 } - `, ParseAndCheckOptions{ Location: ImportedLocation,