From efc00c04a746abf939a02ba65b03eea03e781087 Mon Sep 17 00:00:00 2001 From: Raymond Zhang Date: Mon, 16 Dec 2024 15:21:00 -0500 Subject: [PATCH] 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 ac34e98799..101d60497c 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 24f220f83a..cf53fc2044 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 0dc96d8d4e..78a6a6e471 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 c5bf92d36e..d59923dbf9 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) + + }) + +}