diff --git a/ROADMAP.md b/ROADMAP.md index b6060eadcf..5f13c6b312 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -79,11 +79,6 @@ listed in no particular order. Cadence should provide a way to define type aliases. - For example, if a contract interface might declare a type requirement `NFT`, - then all concrete conforming types must provide a concrete type `NFT`. - - However, it would be nice to give the type an additional, more useful name. - - `Word128` and `Word256` types Cadence should provide `Word128` and `Word256` types, just like it provides `UInt128` and `UInt256` diff --git a/runtime/common/declarationkind.go b/runtime/common/declarationkind.go index f695193e54..ad037f6b43 100644 --- a/runtime/common/declarationkind.go +++ b/runtime/common/declarationkind.go @@ -211,6 +211,16 @@ func (k DeclarationKind) Keywords() string { } } +func (k DeclarationKind) IsInterfaceDeclaration() bool { + switch k { + case DeclarationKindContractInterface, + DeclarationKindStructureInterface, + DeclarationKindResourceInterface: + return true + } + return false +} + func (k DeclarationKind) MarshalJSON() ([]byte, error) { return json.Marshal(k.String()) } diff --git a/runtime/contract_update_validation_test.go b/runtime/contract_update_validation_test.go index dbf43a79b6..70a1fc485a 100644 --- a/runtime/contract_update_validation_test.go +++ b/runtime/contract_update_validation_test.go @@ -1750,21 +1750,6 @@ func TestRuntimeContractUpdateValidation(t *testing.T) { assertContractRemovalError(t, err, "Test") }) - t.Run("Remove contract interface with enum", func(t *testing.T) { - - const code = ` - access(all) contract interface Test { - access(all) enum TestEnum: Int { - } - } - ` - - err := testDeployAndRemove(t, "Test", code) - RequireError(t, err) - - assertContractRemovalError(t, err, "Test") - }) - t.Run("Remove contract without enum", func(t *testing.T) { t.Parallel() diff --git a/runtime/ft_test.go b/runtime/ft_test.go index 9ab65e4265..61c9d6d146 100644 --- a/runtime/ft_test.go +++ b/runtime/ft_test.go @@ -32,7 +32,7 @@ import ( "github.com/onflow/cadence/runtime/tests/utils" ) -const realFungibleTokenContractInterface = ` +const modifiedFungibleTokenContractInterface = ` /// FungibleToken /// /// The interface that fungible token contracts implement. @@ -89,7 +89,7 @@ access(all) contract interface FungibleToken { /// capability that allows all users to access the provider /// resource through a reference. /// - access(all) fun withdraw(amount: UFix64): @Vault { + access(all) fun withdraw(amount: UFix64): @{Vault} { post { // 'result' refers to the return value result.balance == amount: @@ -112,7 +112,7 @@ access(all) contract interface FungibleToken { /// deposit takes a Vault and deposits it into the implementing resource type /// - access(all) fun deposit(from: @Vault) + access(all) fun deposit(from: @{Vault}) } /// Balance @@ -139,7 +139,7 @@ access(all) contract interface FungibleToken { /// /// The resource that contains the functions to send and receive tokens. /// - access(all) resource Vault: Provider, Receiver, Balance { + access(all) resource interface Vault: Provider, Receiver, Balance { // The declaration of a concrete type in a contract interface means that // every Fungible Token contract that implements the FungibleToken interface @@ -158,7 +158,7 @@ access(all) contract interface FungibleToken { /// withdraw subtracts 'amount' from the Vault's balance /// and returns a new Vault with the subtracted balance /// - access(all) fun withdraw(amount: UFix64): @Vault { + access(all) fun withdraw(amount: UFix64): @{Vault} { pre { self.balance >= amount: "Amount withdrawn must be less than or equal than the balance of the Vault" @@ -174,7 +174,7 @@ access(all) contract interface FungibleToken { /// deposit takes a Vault and adds its balance to the balance of this Vault /// - access(all) fun deposit(from: @Vault) { + access(all) fun deposit(from: @{Vault}) { post { self.balance == before(self.balance) + before(from.balance): "New Vault balance must be the sum of the previous balance and the deposited Vault" @@ -184,7 +184,7 @@ access(all) contract interface FungibleToken { /// createEmptyVault allows any user to create a new Vault that has a zero balance /// - access(all) fun createEmptyVault(): @Vault { + access(all) fun createEmptyVault(): @{Vault} { post { result.balance == 0.0: "The newly created Vault must have zero balance" } @@ -192,7 +192,7 @@ access(all) contract interface FungibleToken { } ` -const realFlowContract = ` +const modifiedFlowContract = ` import FungibleToken from 0x1 access(all) contract FlowToken: FungibleToken { @@ -233,7 +233,7 @@ access(all) contract FlowToken: FungibleToken { // out of thin air. A special Minter resource needs to be defined to mint // new tokens. // - access(all) resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance { + access(all) resource Vault: FungibleToken.Vault { // holds the balance of a users tokens access(all) var balance: UFix64 @@ -252,7 +252,7 @@ access(all) contract FlowToken: FungibleToken { // created Vault to the context that called so it can be deposited // elsewhere. // - access(all) fun withdraw(amount: UFix64): @FungibleToken.Vault { + access(all) fun withdraw(amount: UFix64): @{FungibleToken.Vault} { self.balance = self.balance - amount emit TokensWithdrawn(amount: amount, from: self.owner?.address) return <-create Vault(balance: amount) @@ -265,7 +265,7 @@ access(all) contract FlowToken: FungibleToken { // It is allowed to destroy the sent Vault because the Vault // was a temporary holder of the tokens. The Vault's balance has // been consumed and therefore can be destroyed. - access(all) fun deposit(from: @FungibleToken.Vault) { + access(all) fun deposit(from: @{FungibleToken.Vault}) { let vault <- from as! @FlowToken.Vault self.balance = self.balance + vault.balance emit TokensDeposited(amount: vault.balance, to: self.owner?.address) @@ -285,7 +285,7 @@ access(all) contract FlowToken: FungibleToken { // and store the returned Vault in their storage in order to allow their // account to be able to receive deposits of this token type. // - access(all) fun createEmptyVault(): @FungibleToken.Vault { + access(all) fun createEmptyVault(): @{FungibleToken.Vault} { return <-create Vault(balance: 0.0) } @@ -352,7 +352,7 @@ access(all) contract FlowToken: FungibleToken { // Note: the burned tokens are automatically subtracted from the // total supply in the Vault destructor. // - access(all) fun burnTokens(from: @FungibleToken.Vault) { + access(all) fun burnTokens(from: @{FungibleToken.Vault}) { let vault <- from as! @FlowToken.Vault let amount = vault.balance destroy vault @@ -458,7 +458,7 @@ import FlowToken from 0x1 transaction(amount: UFix64, to: Address) { // The Vault resource that holds the tokens that are being transferred - let sentVault: @FungibleToken.Vault + let sentVault: @{FungibleToken.Vault} prepare(signer: AuthAccount) { @@ -548,7 +548,7 @@ func BenchmarkRuntimeFungibleTokenTransfer(b *testing.B) { Script{ Source: utils.DeploymentTransaction( "FungibleToken", - []byte(realFungibleTokenContractInterface), + []byte(modifiedFungibleTokenContractInterface), ), }, Context{ @@ -572,7 +572,7 @@ func BenchmarkRuntimeFungibleTokenTransfer(b *testing.B) { } } `, - hex.EncodeToString([]byte(realFlowContract)), + hex.EncodeToString([]byte(modifiedFlowContract)), )), }, Context{ diff --git a/runtime/interpreter/interpreter.go b/runtime/interpreter/interpreter.go index a3aae115fb..ebd7ae03e3 100644 --- a/runtime/interpreter/interpreter.go +++ b/runtime/interpreter/interpreter.go @@ -188,7 +188,7 @@ type CompositeTypeCode struct { type FunctionWrapper = func(inner FunctionValue) FunctionValue // WrapperCode contains the "prepared" / "callable" "code" -// for inherited types (interfaces and type requirements). +// for inherited types. // // These are "branch" nodes in the call chain, and are function wrappers, // i.e. they wrap the functions / function wrappers that inherit them. @@ -200,11 +200,10 @@ type WrapperCode struct { } // TypeCodes is the value which stores the "prepared" / "callable" "code" -// of all composite types, interface types, and type requirements. +// of all composite types and interface types. type TypeCodes struct { - CompositeCodes map[sema.TypeID]CompositeTypeCode - InterfaceCodes map[sema.TypeID]WrapperCode - TypeRequirementCodes map[sema.TypeID]WrapperCode + CompositeCodes map[sema.TypeID]CompositeTypeCode + InterfaceCodes map[sema.TypeID]WrapperCode } func (c TypeCodes) Merge(codes TypeCodes) { @@ -219,10 +218,6 @@ func (c TypeCodes) Merge(codes TypeCodes) { for typeID, code := range codes.InterfaceCodes { //nolint:maprange c.InterfaceCodes[typeID] = code } - - for typeID, code := range codes.TypeRequirementCodes { //nolint:maprange - c.TypeRequirementCodes[typeID] = code - } } type Storage interface { @@ -1196,26 +1191,12 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue( } } - // NOTE: First the conditions of the type requirements are evaluated, - // then the conditions of this composite's conformances - // - // Because the conditions are wrappers, they have to be applied - // in reverse order: first the conformances, then the type requirements; - // each conformances and type requirements in reverse order as well. - conformances := compositeType.EffectiveInterfaceConformances() for i := len(conformances) - 1; i >= 0; i-- { conformance := conformances[i].InterfaceType wrapFunctions(interpreter.SharedState.typeCodes.InterfaceCodes[conformance.ID()]) } - typeRequirements := compositeType.TypeRequirements() - - for i := len(typeRequirements) - 1; i >= 0; i-- { - typeRequirement := typeRequirements[i] - wrapFunctions(interpreter.SharedState.typeCodes.TypeRequirementCodes[typeRequirement.ID()]) - } - interpreter.SharedState.typeCodes.CompositeCodes[compositeType.ID()] = CompositeTypeCode{ DestructorFunction: destructorFunction, CompositeFunctions: functions, @@ -2248,7 +2229,8 @@ func (interpreter *Interpreter) declareInterface( if nestedCompositeDeclaration.Kind() == common.CompositeKindEvent { interpreter.declareNonEnumCompositeValue(nestedCompositeDeclaration, lexicalScope) } else { - interpreter.declareTypeRequirement(nestedCompositeDeclaration, lexicalScope) + // this is statically prevented in the checker + panic(errors.NewUnreachableError()) } } })() @@ -2273,46 +2255,6 @@ func (interpreter *Interpreter) declareInterface( } } -func (interpreter *Interpreter) declareTypeRequirement( - declaration *ast.CompositeDeclaration, - lexicalScope *VariableActivation, -) { - // Evaluate nested declarations in a new scope, so values - // of nested declarations won't be visible after the containing declaration - - (func() { - interpreter.activations.PushNewWithCurrent() - defer interpreter.activations.Pop() - - for _, nestedInterfaceDeclaration := range declaration.Members.Interfaces() { - interpreter.declareInterface(nestedInterfaceDeclaration, lexicalScope) - } - - for _, nestedCompositeDeclaration := range declaration.Members.Composites() { - interpreter.declareTypeRequirement(nestedCompositeDeclaration, lexicalScope) - } - })() - - compositeType := interpreter.Program.Elaboration.CompositeDeclarationType(declaration) - typeID := compositeType.ID() - - initializerFunctionWrapper := interpreter.initializerFunctionWrapper( - declaration.Members, - compositeType.ConstructorParameters, - lexicalScope, - ) - destructorFunctionWrapper := interpreter.destructorFunctionWrapper(declaration.Members, lexicalScope) - functionWrappers := interpreter.functionWrappers(declaration.Members, lexicalScope) - defaultFunctions := interpreter.defaultFunctions(declaration.Members, lexicalScope) - - interpreter.SharedState.typeCodes.TypeRequirementCodes[typeID] = WrapperCode{ - InitializerFunctionWrapper: initializerFunctionWrapper, - DestructorFunctionWrapper: destructorFunctionWrapper, - FunctionWrappers: functionWrappers, - Functions: defaultFunctions, - } -} - func (interpreter *Interpreter) initializerFunctionWrapper( members *ast.Members, parameters []sema.Parameter, diff --git a/runtime/interpreter/sharedstate.go b/runtime/interpreter/sharedstate.go index c7529d615b..9f2822a168 100644 --- a/runtime/interpreter/sharedstate.go +++ b/runtime/interpreter/sharedstate.go @@ -54,9 +54,8 @@ func NewSharedState(config *Config) *SharedState { allInterpreters: map[common.Location]*Interpreter{}, callStack: &CallStack{}, typeCodes: TypeCodes{ - CompositeCodes: map[sema.TypeID]CompositeTypeCode{}, - InterfaceCodes: map[sema.TypeID]WrapperCode{}, - TypeRequirementCodes: map[sema.TypeID]WrapperCode{}, + CompositeCodes: map[sema.TypeID]CompositeTypeCode{}, + InterfaceCodes: map[sema.TypeID]WrapperCode{}, }, inStorageIteration: false, storageMutatedDuringIteration: false, diff --git a/runtime/nft_test.go b/runtime/nft_test.go index 8bb7ee65c0..ed9edcc879 100644 --- a/runtime/nft_test.go +++ b/runtime/nft_test.go @@ -18,7 +18,7 @@ package runtime -const realNonFungibleTokenInterface = ` +const modifiedNonFungibleTokenInterface = ` access(all) contract interface NonFungibleToken { @@ -49,7 +49,7 @@ access(all) contract interface NonFungibleToken { // Requirement that all conforming NFT smart contracts have // to define a resource called NFT that conforms to INFT - access(all) resource NFT: INFT { + access(all) resource interface NFT: INFT { access(all) let id: UInt64 } @@ -57,7 +57,7 @@ access(all) contract interface NonFungibleToken { // access(all) resource interface Provider { // withdraw removes an NFT from the collection and moves it to the caller - access(all) fun withdraw(withdrawID: UInt64): @NFT { + access(all) fun withdraw(withdrawID: UInt64): @{NFT} { post { result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID" } @@ -70,38 +70,38 @@ access(all) contract interface NonFungibleToken { // deposit takes an NFT as an argument and adds it to the Collection // - access(all) fun deposit(token: @NFT) + access(all) fun deposit(token: @{NFT}) } // Interface that an account would commonly // publish for their collection access(all) resource interface CollectionPublic { - access(all) fun deposit(token: @NFT) + access(all) fun deposit(token: @{NFT}) access(all) fun getIDs(): [UInt64] - access(all) fun borrowNFT(id: UInt64): &NFT + access(all) fun borrowNFT(id: UInt64): &{NFT} } // Requirement for the the concrete resource type // to be declared in the implementing contract // - access(all) resource Collection: Provider, Receiver, CollectionPublic { + access(all) resource interface Collection: Provider, Receiver, CollectionPublic { // Dictionary to hold the NFTs in the Collection - access(all) var ownedNFTs: @{UInt64: NFT} + access(all) var ownedNFTs: @{UInt64: {NFT}} // withdraw removes an NFT from the collection and moves it to the caller - access(all) fun withdraw(withdrawID: UInt64): @NFT + access(all) fun withdraw(withdrawID: UInt64): @{NFT} // deposit takes a NFT and adds it to the collections dictionary // and adds the ID to the id array - access(all) fun deposit(token: @NFT) + access(all) fun deposit(token: @{NFT}) // getIDs returns an array of the IDs that are in the collection access(all) fun getIDs(): [UInt64] // Returns a borrowed reference to an NFT in the collection // so that the caller can read data and call methods from it - access(all) fun borrowNFT(id: UInt64): &NFT { + access(all) fun borrowNFT(id: UInt64): &{NFT} { pre { self.ownedNFTs[id] != nil: "NFT does not exist in the collection!" } @@ -110,7 +110,7 @@ access(all) contract interface NonFungibleToken { // createEmptyCollection creates an empty Collection // and returns it to the caller so that they can own NFTs - access(all) fun createEmptyCollection(): @Collection { + access(all) fun createEmptyCollection(): @{Collection} { post { result.ownedNFTs.length == 0: "The created collection must be empty!" } @@ -488,7 +488,7 @@ access(all) contract TopShot: NonFungibleToken { // The resource that represents the Moment NFTs // - access(all) resource NFT: NonFungibleToken.INFT { + access(all) resource NFT: NonFungibleToken.NFT { // global unique moment ID access(all) let id: UInt64 @@ -593,10 +593,10 @@ access(all) contract TopShot: NonFungibleToken { // This is the interface that users can cast their moment Collection as // to allow others to deposit moments into their collection access(all) resource interface MomentCollectionPublic { - access(all) fun deposit(token: @NonFungibleToken.NFT) - access(all) fun batchDeposit(tokens: @NonFungibleToken.Collection) + access(all) fun deposit(token: @{NonFungibleToken.NFT}) + access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) access(all) fun getIDs(): [UInt64] - access(all) fun borrowNFT(id: UInt64): &NonFungibleToken.NFT + access(all) fun borrowNFT(id: UInt64): &{NonFungibleToken.NFT} access(all) fun borrowMoment(id: UInt64): &TopShot.NFT? { // If the result isn't nil, the id of the returned reference // should be the same as the argument to the function @@ -610,17 +610,17 @@ access(all) contract TopShot: NonFungibleToken { // Collection is a resource that every user who owns NFTs // will store in their account to manage their NFTS // - access(all) resource Collection: MomentCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic { + access(all) resource Collection: MomentCollectionPublic, NonFungibleToken.Collection { // Dictionary of Moment conforming tokens // NFT is a resource type with a UInt64 ID field - access(all) var ownedNFTs: @{UInt64: NonFungibleToken.NFT} + access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}} init() { self.ownedNFTs <- {} } // withdraw removes an Moment from the collection and moves it to the caller - access(all) fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { + access(all) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} { let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Cannot withdraw: Moment does not exist in the collection") @@ -630,7 +630,7 @@ access(all) contract TopShot: NonFungibleToken { } // batchWithdraw withdraws multiple tokens and returns them as a Collection - access(all) fun batchWithdraw(ids: [UInt64]): @NonFungibleToken.Collection { + access(all) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} { var batchCollection <- create Collection() // iterate through the ids and withdraw them from the collection @@ -641,7 +641,7 @@ access(all) contract TopShot: NonFungibleToken { } // deposit takes a Moment and adds it to the collections dictionary - access(all) fun deposit(token: @NonFungibleToken.NFT) { + access(all) fun deposit(token: @{NonFungibleToken.NFT}) { let token <- token as! @TopShot.NFT let id = token.id @@ -657,7 +657,7 @@ access(all) contract TopShot: NonFungibleToken { // batchDeposit takes a Collection object as an argument // and deposits each contained NFT into this collection - access(all) fun batchDeposit(tokens: @NonFungibleToken.Collection) { + access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) { let keys = tokens.getIDs() // iterate through the keys in the collection and deposit each one @@ -678,8 +678,8 @@ access(all) contract TopShot: NonFungibleToken { // Parameters: id: The ID of the NFT to get the reference for // // Returns: A reference to the NFT - access(all) fun borrowNFT(id: UInt64): &NonFungibleToken.NFT { - return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)! + access(all) fun borrowNFT(id: UInt64): &{NonFungibleToken.NFT} { + return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)! } // borrowMoment Returns a borrowed reference to a Moment in the collection @@ -694,7 +694,7 @@ access(all) contract TopShot: NonFungibleToken { // Returns: A reference to the NFT access(all) fun borrowMoment(id: UInt64): &TopShot.NFT? { if self.ownedNFTs[id] != nil { - let ref = (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)! + let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)! return ref as! &TopShot.NFT } else { return nil @@ -720,7 +720,7 @@ access(all) contract TopShot: NonFungibleToken { // Once they have a Collection in their storage, they are able to receive // Moments in transactions // - access(all) fun createEmptyCollection(): @NonFungibleToken.Collection { + access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} { return <-create TopShot.Collection() } @@ -942,7 +942,7 @@ access(all) contract TopShotShardedCollection { // withdraw removes a Moment from one of the Collections // and moves it to the caller - access(all) fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { + access(all) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} { post { result.id == withdrawID: "The ID of the withdrawn NFT is incorrect" } @@ -961,7 +961,7 @@ access(all) contract TopShotShardedCollection { // // Returns: @NonFungibleToken.Collection a Collection containing the moments // that were withdrawn - access(all) fun batchWithdraw(ids: [UInt64]): @NonFungibleToken.Collection { + access(all) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} { var batchCollection <- TopShot.createEmptyCollection() // Iterate through the ids and withdraw them from the Collection @@ -972,7 +972,7 @@ access(all) contract TopShotShardedCollection { } // deposit takes a Moment and adds it to the Collections dictionary - access(all) fun deposit(token: @NonFungibleToken.NFT) { + access(all) fun deposit(token: @{NonFungibleToken.NFT}) { // Find the bucket this corresponds to let bucket = token.id % self.numBuckets @@ -989,7 +989,7 @@ access(all) contract TopShotShardedCollection { // batchDeposit takes a Collection object as an argument // and deposits each contained NFT into this Collection - access(all) fun batchDeposit(tokens: @NonFungibleToken.Collection) { + access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) { let keys = tokens.getIDs() // Iterate through the keys in the Collection and deposit each one @@ -1014,7 +1014,7 @@ access(all) contract TopShotShardedCollection { // borrowNFT Returns a borrowed reference to a Moment in the Collection // so that the caller can read data and call methods from it - access(all) fun borrowNFT(id: UInt64): &NonFungibleToken.NFT { + access(all) fun borrowNFT(id: UInt64): &{NonFungibleToken.NFT} { post { result.id == id: "The ID of the reference is incorrect" } diff --git a/runtime/resource_duplicate_test.go b/runtime/resource_duplicate_test.go index 69047f1237..828c472273 100644 --- a/runtime/resource_duplicate_test.go +++ b/runtime/resource_duplicate_test.go @@ -516,7 +516,7 @@ func TestRuntimeResourceDuplicationWithContractTransfer(t *testing.T) { Script{ Source: DeploymentTransaction( "FungibleToken", - []byte(realFungibleTokenContractInterface), + []byte(modifiedFungibleTokenContractInterface), ), }, Context{ @@ -539,7 +539,7 @@ func TestRuntimeResourceDuplicationWithContractTransfer(t *testing.T) { } } `, - hex.EncodeToString([]byte(realFlowContract)), + hex.EncodeToString([]byte(modifiedFlowContract)), )), }, Context{ diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index 86b1a4e224..6bcb43475f 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -26,7 +26,7 @@ import ( ) func (checker *Checker) VisitCompositeDeclaration(declaration *ast.CompositeDeclaration) (_ struct{}) { - checker.visitCompositeLikeDeclaration(declaration, ContainerKindComposite) + checker.visitCompositeLikeDeclaration(declaration) return } @@ -133,10 +133,10 @@ func (checker *Checker) checkAttachmentMembersAccess(attachmentType *CompositeTy } func (checker *Checker) VisitAttachmentDeclaration(declaration *ast.AttachmentDeclaration) (_ struct{}) { - return checker.visitAttachmentDeclaration(declaration, ContainerKindComposite) + return checker.visitAttachmentDeclaration(declaration) } -func (checker *Checker) visitAttachmentDeclaration(declaration *ast.AttachmentDeclaration, kind ContainerKind) (_ struct{}) { +func (checker *Checker) visitAttachmentDeclaration(declaration *ast.AttachmentDeclaration) (_ struct{}) { if !checker.Config.AttachmentsEnabled { checker.report(&AttachmentsNotEnabledError{ @@ -144,7 +144,7 @@ func (checker *Checker) visitAttachmentDeclaration(declaration *ast.AttachmentDe }) } - checker.visitCompositeLikeDeclaration(declaration, kind) + checker.visitCompositeLikeDeclaration(declaration) attachmentType := checker.Elaboration.CompositeDeclarationType(declaration) checker.checkAttachmentMembersAccess(attachmentType) checker.checkAttachmentBaseType( @@ -155,15 +155,12 @@ func (checker *Checker) visitAttachmentDeclaration(declaration *ast.AttachmentDe } // visitCompositeDeclaration checks a previously declared composite declaration. -// Checking behaviour depends on `kind`, i.e. if the composite declaration declares -// a composite (`kind` is `ContainerKindComposite`), or the composite declaration is -// nested in an interface and so acts as a type requirement (`kind` is `ContainerKindInterface`). // // NOTE: This function assumes that the composite type was previously declared using // `declareCompositeType` and exists in `checker.Elaboration.CompositeDeclarationTypes`, // and that the members and nested declarations for the composite type were declared // through `declareCompositeMembersAndValue`. -func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeLikeDeclaration, kind ContainerKind) { +func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeLikeDeclaration) { compositeType := checker.Elaboration.CompositeDeclarationType(declaration) if compositeType == nil { panic(errors.NewUnreachableError()) @@ -196,35 +193,30 @@ func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeL checker.typeActivations.Enter() defer checker.typeActivations.Leave(declaration.EndPosition) - if kind == ContainerKindComposite { - checker.enterValueScope() - defer checker.leaveValueScope(declaration.EndPosition, false) - } + checker.enterValueScope() + defer checker.leaveValueScope(declaration.EndPosition, false) - checker.declareCompositeLikeNestedTypes(declaration, kind, true) + checker.declareCompositeLikeNestedTypes(declaration, true) var initializationInfo *InitializationInfo + // The initializer must initialize all members that are fields, + // e.g. not composite functions (which are by definition constant and "initialized") - if kind == ContainerKindComposite { - // The initializer must initialize all members that are fields, - // e.g. not composite functions (which are by definition constant and "initialized") - - fields := members.Fields() - fieldMembers := orderedmap.New[MemberFieldDeclarationOrderedMap](len(fields)) + fields := members.Fields() + fieldMembers := orderedmap.New[MemberFieldDeclarationOrderedMap](len(fields)) - for _, field := range fields { - fieldName := field.Identifier.Identifier - member, ok := compositeType.Members.Get(fieldName) - if !ok { - continue - } - - fieldMembers.Set(member, field) + for _, field := range fields { + fieldName := field.Identifier.Identifier + member, ok := compositeType.Members.Get(fieldName) + if !ok { + continue } - initializationInfo = NewInitializationInfo(compositeType, fieldMembers) + fieldMembers.Set(member, field) } + initializationInfo = NewInitializationInfo(compositeType, fieldMembers) + checker.checkInitializers( members.Initializers(), members.Fields(), @@ -232,36 +224,17 @@ func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeL declaration.DeclarationDocString(), compositeType.ConstructorPurity, compositeType.ConstructorParameters, - kind, + ContainerKindComposite, initializationInfo, ) checker.checkUnknownSpecialFunctions(members.SpecialFunctions()) - switch kind { - case ContainerKindComposite: - checker.checkCompositeFunctions( - members.Functions(), - compositeType, - declaration.DeclarationDocString(), - ) - - case ContainerKindInterface: - checker.checkSpecialFunctionDefaultImplementation(declaration, "type requirement") - - declarationKind := declaration.Kind() - - checker.checkInterfaceFunctions( - members.Functions(), - compositeType, - declaration.DeclarationKind(), - &declarationKind, - declaration.DeclarationDocString(), - ) - - default: - panic(errors.NewUnreachableError()) - } + checker.checkCompositeFunctions( + members.Functions(), + compositeType, + declaration.DeclarationDocString(), + ) fieldPositionGetter := func(name string) ast.Position { return compositeType.FieldPosition(name, declaration) @@ -275,25 +248,8 @@ func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeL ) // Check conformances - // NOTE: perform after completing composite type (e.g. setting constructor parameter types) - - // If the composite declaration is declaring a composite (`kind` is `ContainerKindComposite`), - // rather than a type requirement (`kind` is `ContainerKindInterface`), check that the composite - // conforms to all interfaces the composite declared it conforms to, i.e. all members match, - // and no members are missing. - - // If the composite declaration is a type requirement (`kind` is `ContainerKindInterface`), - // DON'T check that the composite conforms to all interfaces the composite declared it - // conforms to – these are requirements that the composite declaration of the implementation - // of the containing interface must conform to. - // - // Thus, missing members are valid, but still check that members that are declared as requirements - // match the members of the conformances (members in the interface) - - checkMissingMembers := kind != ContainerKindInterface inheritedMembers := make(map[string][]*Member) - typeRequirementsInheritedMembers := make(map[string]map[string][]*Member) defaultFunctions := availableDefaultFunctions(compositeType) @@ -304,11 +260,9 @@ func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeL conformance.InterfaceType, conformance.ConformanceChainRoot, compositeConformanceCheckOptions{ - checkMissingMembers: checkMissingMembers, - interfaceTypeIsTypeRequirement: false, + checkMissingMembers: true, }, inheritedMembers, - typeRequirementsInheritedMembers, defaultFunctions, ) } @@ -323,7 +277,7 @@ func (checker *Checker) visitCompositeLikeDeclaration(declaration ast.CompositeL compositeType, declaration.DeclarationKind(), declaration.DeclarationDocString(), - kind, + ContainerKindComposite, ) }) @@ -380,7 +334,6 @@ func availableDefaultFunctions(compositeType *CompositeType) map[string]struct{} // and the type for the declaration was added to the elaboration in `CompositeDeclarationTypes`. func (checker *Checker) declareCompositeLikeNestedTypes( declaration ast.CompositeLikeDeclaration, - kind ContainerKind, declareConstructors bool, ) { compositeType := checker.Elaboration.CompositeDeclarationType(declaration) @@ -411,7 +364,7 @@ func (checker *Checker) declareCompositeLikeNestedTypes( }) checker.report(err) - if declareConstructors && kind == ContainerKindComposite { + if declareConstructors { // NOTE: Re-declare the constructor function for the nested composite declaration: // The constructor was previously declared in `declareCompositeMembersAndValue` @@ -472,7 +425,7 @@ func (checker *Checker) declareNestedDeclarations( ) { nestedDeclarations = map[string]ast.Declaration{} - // Only contracts and contract interfaces support nested composite declarations + // Only concrete contracts support nested composite declarations if containerCompositeKind != common.CompositeKindContract { reportInvalidNesting := func(nestedDeclarationKind common.DeclarationKind, identifier ast.Identifier) { @@ -526,6 +479,7 @@ func (checker *Checker) declareNestedDeclarations( firstNestedAttachmentDeclaration.DeclarationKind(), firstNestedAttachmentDeclaration.Identifier, ) + } // NOTE: don't return, so nested declarations / types are still declared @@ -539,23 +493,38 @@ func (checker *Checker) declareNestedDeclarations( nestedDeclarationKind common.DeclarationKind, identifier ast.Identifier, ) { + if containerDeclarationKind.IsInterfaceDeclaration() && !nestedDeclarationKind.IsInterfaceDeclaration() { + switch nestedCompositeKind { + case common.CompositeKindEvent: + break - switch nestedCompositeKind { - case common.CompositeKindResource, - common.CompositeKindStructure, - common.CompositeKindAttachment, - common.CompositeKindEvent, - common.CompositeKindEnum: - break + default: + checker.report( + &InvalidNestedDeclarationError{ + NestedDeclarationKind: nestedDeclarationKind, + ContainerDeclarationKind: containerDeclarationKind, + Range: ast.NewRangeFromPositioned(checker.memoryGauge, identifier), + }, + ) + } + } else { + switch nestedCompositeKind { + case common.CompositeKindResource, + common.CompositeKindStructure, + common.CompositeKindAttachment, + common.CompositeKindEvent, + common.CompositeKindEnum: + break - default: - checker.report( - &InvalidNestedDeclarationError{ - NestedDeclarationKind: nestedDeclarationKind, - ContainerDeclarationKind: containerDeclarationKind, - Range: ast.NewRangeFromPositioned(checker.memoryGauge, identifier), - }, - ) + default: + checker.report( + &InvalidNestedDeclarationError{ + NestedDeclarationKind: nestedDeclarationKind, + ContainerDeclarationKind: containerDeclarationKind, + Range: ast.NewRangeFromPositioned(checker.memoryGauge, identifier), + }, + ) + } } } @@ -782,8 +751,8 @@ func (checker *Checker) declareCompositeType(declaration ast.CompositeLikeDeclar return compositeType } -func (checker *Checker) declareAttachmentMembersAndValue(declaration *ast.AttachmentDeclaration, kind ContainerKind) { - checker.declareCompositeLikeMembersAndValue(declaration, kind) +func (checker *Checker) declareAttachmentMembersAndValue(declaration *ast.AttachmentDeclaration) { + checker.declareCompositeLikeMembersAndValue(declaration) } // declareCompositeMembersAndValue declares the members and the value @@ -794,7 +763,6 @@ func (checker *Checker) declareAttachmentMembersAndValue(declaration *ast.Attach // `declareCompositeType` and exists in `checker.Elaboration.CompositeDeclarationTypes`. func (checker *Checker) declareCompositeLikeMembersAndValue( declaration ast.CompositeLikeDeclaration, - containerKind ContainerKind, ) { compositeType := checker.Elaboration.CompositeDeclarationType(declaration) if compositeType == nil { @@ -818,7 +786,7 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( checker.enterValueScope() defer checker.leaveValueScope(declaration.EndPosition, false) - checker.declareCompositeLikeNestedTypes(declaration, containerKind, false) + checker.declareCompositeLikeNestedTypes(declaration, false) // NOTE: determine initializer parameter types while nested types are in scope, // and after declaring nested types as the initializer may use nested type in parameters @@ -847,7 +815,7 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( // } // ``` declareNestedComposite := func(nestedCompositeDeclaration ast.CompositeLikeDeclaration) { - checker.declareCompositeLikeMembersAndValue(nestedCompositeDeclaration, containerKind) + checker.declareCompositeLikeMembersAndValue(nestedCompositeDeclaration) // Declare nested composites' values (constructor/instance) as members of the containing composite @@ -884,15 +852,6 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( declareNestedComposite(nestedAttachmentDeclaration) } - // Declare implicit type requirement conformances, if any, - // after nested types are declared, and - // after explicit conformances are declared. - // - // For each nested composite type, check if a conformance - // declares a nested composite type with the same identifier, - // in which case it is a type requirement, - // and this nested composite type implicitly conforms to it. - compositeType.GetNestedTypes().Foreach(func(nestedTypeIdentifier string, nestedType Type) { nestedCompositeType, ok := nestedType.(*CompositeType) @@ -902,67 +861,6 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( var inheritedMembers StringMemberOrderedMap - for _, compositeTypeConformance := range compositeType.EffectiveInterfaceConformances() { - conformanceNestedTypes := compositeTypeConformance.InterfaceType.GetNestedTypes() - - nestedType, ok := conformanceNestedTypes.Get(nestedTypeIdentifier) - if !ok { - continue - } - - typeRequirement, ok := nestedType.(*CompositeType) - if !ok { - continue - } - - nestedCompositeType.addImplicitTypeRequirementConformance(typeRequirement) - - // Add default functions - - typeRequirement.Members.Foreach(func(memberName string, member *Member) { - - if member.Predeclared || - member.DeclarationKind != common.DeclarationKindFunction { - - return - } - - _, existing := nestedCompositeType.Members.Get(memberName) - if existing { - return - } - - if _, ok := inheritedMembers.Get(memberName); ok { - errorRange := ast.NewRangeFromPositioned(checker.memoryGauge, declaration.DeclarationIdentifier()) - - if member.HasImplementation { - checker.report( - &MultipleInterfaceDefaultImplementationsError{ - CompositeKindedType: nestedCompositeType, - Member: member, - Range: errorRange, - }, - ) - } else { - checker.report( - &DefaultFunctionConflictError{ - CompositeKindedType: nestedCompositeType, - Member: member, - Range: errorRange, - }, - ) - } - - return - } - - if member.HasImplementation { - inheritedMembers.Set(memberName, member) - } - }) - - } - inheritedMembers.Foreach(func(memberName string, member *Member) { inheritedMember := *member inheritedMember.ContainerType = nestedCompositeType @@ -997,7 +895,7 @@ func (checker *Checker) declareCompositeLikeMembersAndValue( members, fields, origins = checker.defaultMembersAndOrigins( declaration.DeclarationMembers(), compositeType, - containerKind, + ContainerKindComposite, declaration.DeclarationKind(), ) } @@ -1360,8 +1258,7 @@ func (checker *Checker) enumRawType(declaration *ast.CompositeDeclaration) Type } type compositeConformanceCheckOptions struct { - checkMissingMembers bool - interfaceTypeIsTypeRequirement bool + checkMissingMembers bool } // checkCompositeLikeConformance checks if the given composite declaration with the given composite type @@ -1370,10 +1267,6 @@ type compositeConformanceCheckOptions struct { // inheritedMembers is an "input/output parameter": // It tracks which members were inherited from the interface. // It allows tracking this across conformance checks of multiple interfaces. -// -// typeRequirementsInheritedMembers is an "input/output parameter": -// It tracks which members were inherited in each nested type, which may be a conformance to a type requirement. -// It allows tracking this across conformance checks of multiple interfaces' type requirements. func (checker *Checker) checkCompositeLikeConformance( compositeDeclaration ast.CompositeLikeDeclaration, compositeType *CompositeType, @@ -1381,8 +1274,6 @@ func (checker *Checker) checkCompositeLikeConformance( conformanceChainRoot *InterfaceType, options compositeConformanceCheckOptions, inheritedMembers map[string][]*Member, - // type requirement name -> inherited members - typeRequirementsInheritedMembers map[string]map[string][]*Member, defaultFunctions map[string]struct{}, ) { @@ -1482,33 +1373,6 @@ func (checker *Checker) checkCompositeLikeConformance( }) - // Determine missing nested composite type definitions - - conformance.NestedTypes.Foreach(func(name string, typeRequirement Type) { - - // Only non-event nested composite declarations are type requirements of the interface - - requiredCompositeType, ok := typeRequirement.(*CompositeType) - if !ok || requiredCompositeType.Kind == common.CompositeKindEvent { - return - } - - nestedCompositeType, ok := compositeType.NestedTypes.Get(name) - if !ok { - - missingNestedCompositeTypes = append(missingNestedCompositeTypes, requiredCompositeType) - return - } - - inherited := typeRequirementsInheritedMembers[name] - if inherited == nil { - inherited = make(map[string][]*Member) - typeRequirementsInheritedMembers[name] = inherited - } - - checker.checkTypeRequirement(nestedCompositeType, compositeDeclaration, requiredCompositeType, inherited) - }) - if len(missingMembers) > 0 || len(memberMismatches) > 0 || len(missingNestedCompositeTypes) > 0 || @@ -1516,16 +1380,15 @@ func (checker *Checker) checkCompositeLikeConformance( checker.report( &ConformanceError{ - CompositeDeclaration: compositeDeclaration, - CompositeType: compositeType, - InterfaceType: conformanceChainRoot, - Pos: compositeDeclaration.DeclarationIdentifier().Pos, - InitializerMismatch: initializerMismatch, - MissingMembers: missingMembers, - MemberMismatches: memberMismatches, - MissingNestedCompositeTypes: missingNestedCompositeTypes, - InterfaceTypeIsTypeRequirement: options.interfaceTypeIsTypeRequirement, - NestedInterfaceType: conformance, + CompositeDeclaration: compositeDeclaration, + CompositeType: compositeType, + InterfaceType: conformanceChainRoot, + Pos: compositeDeclaration.DeclarationIdentifier().Pos, + InitializerMismatch: initializerMismatch, + MissingMembers: missingMembers, + MemberMismatches: memberMismatches, + MissingNestedCompositeTypes: missingNestedCompositeTypes, + NestedInterfaceType: conformance, }, ) } @@ -1613,8 +1476,7 @@ func (checker *Checker) checkConformanceKindMatch( conformances := conformingDeclaration.ConformanceList() if len(conformances) == 0 { - // For type requirements, there is no explicit conformance. - // Hence, log the error at the type requirement (i.e: declaration identifier) + // If there are no explicit conformances, log the error at the declaration identifier compositeKindMismatchIdentifier = conformingDeclaration.DeclarationIdentifier() } else { // Otherwise, find the conformance which resulted in the mismatch, @@ -1749,179 +1611,6 @@ func (checker *Checker) memberSatisfied( return !effectiveCompositeMemberAccess.IsLessPermissiveThan(effectiveInterfaceMemberAccess) } -// checkTypeRequirement checks conformance of a nested type declaration -// to a type requirement of an interface. -func (checker *Checker) checkTypeRequirement( - declaredType Type, - containerDeclaration ast.CompositeLikeDeclaration, - requiredCompositeType *CompositeType, - inherited map[string][]*Member, -) { - - members := containerDeclaration.DeclarationMembers() - - // A nested interface doesn't satisfy the type requirement, - // it must be a composite - - if declaredInterfaceType, ok := declaredType.(*InterfaceType); ok { - - // Find the interface declaration of the interface type - - var errorRange ast.Range - var foundInterfaceDeclaration bool - - for _, nestedInterfaceDeclaration := range members.Interfaces() { - nestedInterfaceIdentifier := nestedInterfaceDeclaration.Identifier.Identifier - if nestedInterfaceIdentifier == declaredInterfaceType.Identifier { - foundInterfaceDeclaration = true - errorRange = ast.NewRangeFromPositioned(checker.memoryGauge, nestedInterfaceDeclaration.Identifier) - break - } - } - - if !foundInterfaceDeclaration { - panic(errors.NewUnreachableError()) - } - - checker.report( - &DeclarationKindMismatchError{ - ExpectedDeclarationKind: requiredCompositeType.Kind.DeclarationKind(false), - ActualDeclarationKind: declaredInterfaceType.CompositeKind.DeclarationKind(true), - Range: errorRange, - }, - ) - - return - } - - // If the nested type is neither an interface nor a composite, - // something must be wrong in the checker - - declaredCompositeType, ok := declaredType.(*CompositeType) - if !ok { - panic(errors.NewUnreachableError()) - } - - // Find the composite declaration of the composite type - - var compositeDeclaration ast.CompositeLikeDeclaration - var foundRedeclaration bool - - findDeclaration := func(nestedCompositeDeclaration ast.CompositeLikeDeclaration) { - identifier := nestedCompositeDeclaration.DeclarationIdentifier() - nestedCompositeIdentifier := identifier.Identifier - if nestedCompositeIdentifier == declaredCompositeType.Identifier { - // If we detected a second nested composite declaration with the same identifier, - // report an error and stop further type requirement checking - if compositeDeclaration != nil { - foundRedeclaration = true - checker.report(&RedeclarationError{ - Kind: nestedCompositeDeclaration.DeclarationKind(), - Name: identifier.Identifier, - Pos: identifier.Pos, - PreviousPos: &compositeDeclaration.DeclarationIdentifier().Pos, - }) - } - compositeDeclaration = nestedCompositeDeclaration - // NOTE: Do not break / stop iteration, but keep looking for - // another (invalid) nested composite declaration with the same identifier, - // as the first found declaration is not necessarily the correct one - } - } - - for _, nestedCompositeDeclaration := range members.Composites() { - findDeclaration(nestedCompositeDeclaration) - } - - for _, nestedAttachmentDeclaration := range members.Attachments() { - findDeclaration(nestedAttachmentDeclaration) - } - - if foundRedeclaration { - return - } - - if compositeDeclaration == nil { - panic(errors.NewUnreachableError()) - } - - // Check that the composite declaration declares at least the conformances - // that the type requirement stated - - for _, requiredConformance := range requiredCompositeType.EffectiveInterfaceConformances() { - found := false - - for _, conformance := range declaredCompositeType.EffectiveInterfaceConformances() { - if conformance.InterfaceType == requiredConformance.InterfaceType { - found = true - break - } - - } - - if !found { - checker.report( - &MissingConformanceError{ - CompositeType: declaredCompositeType, - InterfaceType: requiredConformance.InterfaceType, - Range: ast.NewRangeFromPositioned(checker.memoryGauge, compositeDeclaration.DeclarationIdentifier()), - }, - ) - } - - } - - // Check the conformance of the composite to the type requirement - // like a top-level composite declaration to an interface type - - requiredInterfaceType := requiredCompositeType.InterfaceType() - - // while attachments cannot be declared as interfaces, an attachment type requirement essentially functions - // as an interface, so we must enforce that the concrete attachment's base type is a compatible with the requirement's. - // Specifically, attachment base types are contravariant; if the contract interface requires a struct attachment with a base type - // of `S`, the concrete contract can fulfill this requirement by implementing an attachment with a base type of `AnyStruct`: - // if the attachment is valid on any structure, then clearly it is a valid attachment for `S`. See the example below: - // - // resource interface RI { /* ... */ } - // resource R: RI { /* ... */ } - // contract interface CI { - // attachment A for R { /* ... */ } - // } - // contract C: CI { - // attachment A for RI { /* ... */ } - // } - // - // In this example, as long as `A` in `C` contains the expected member declarations as defined in `CI`, this is a valid - // implementation of the type requirement, as an `A` that can accept any `RI` as a base can clearly function for an `R` as well. - // It may also be helpful to conceptualize an attachment as a sort of implicit function that takes a `base` argument and returns a composite value. - if requiredCompositeType.Kind == common.CompositeKindAttachment && declaredCompositeType.Kind == common.CompositeKindAttachment { - if !IsSubType(requiredCompositeType.baseType, declaredCompositeType.baseType) { - checker.report( - &ConformanceError{ - CompositeDeclaration: compositeDeclaration, - CompositeType: declaredCompositeType, - InterfaceType: requiredCompositeType.InterfaceType(), - Pos: compositeDeclaration.DeclarationIdentifier().Pos, - }, - ) - } - } - - checker.checkCompositeLikeConformance( - compositeDeclaration, - declaredCompositeType, - requiredInterfaceType, - requiredInterfaceType, - compositeConformanceCheckOptions{ - checkMissingMembers: true, - interfaceTypeIsTypeRequirement: true, - }, - inherited, - map[string]map[string][]*Member{}, - map[string]struct{}{}, - ) -} - func CompositeLikeConstructorType( elaboration *Elaboration, compositeDeclaration ast.CompositeLikeDeclaration, diff --git a/runtime/sema/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index 1a11d9a2ab..3768f98e1c 100644 --- a/runtime/sema/check_interface_declaration.go +++ b/runtime/sema/check_interface_declaration.go @@ -94,7 +94,7 @@ func (checker *Checker) VisitInterfaceDeclaration(declaration *ast.InterfaceDecl for _, nestedCompositeDeclaration := range declaration.Members.Composites() { if nestedCompositeDeclaration.Kind() == common.CompositeKindEvent { - checker.declareCompositeLikeMembersAndValue(nestedCompositeDeclaration, ContainerKindComposite) + checker.declareCompositeLikeMembersAndValue(nestedCompositeDeclaration) } } @@ -156,23 +156,13 @@ func (checker *Checker) VisitInterfaceDeclaration(declaration *ast.InterfaceDecl } for _, nestedComposite := range declaration.Members.Composites() { - // Non-event composite declarations nested in interface declarations are type requirements, - // i.e. they should be checked like interfaces - - if nestedComposite.Kind() != common.CompositeKindEvent { - checker.visitCompositeLikeDeclaration(nestedComposite, kind) - } else { - // events should be checked like composites, as they are not type requirements - checker.visitCompositeLikeDeclaration(nestedComposite, ContainerKindComposite) + // only event types may be declared in interfaces. + // However, the error will be reported later in `declareNestedDeclarations`` + if nestedComposite.Kind() == common.CompositeKindEvent { + checker.visitCompositeLikeDeclaration(nestedComposite) } } - for _, nestedAttachments := range declaration.Members.Attachments() { - // Attachment declarations nested in interface declarations are type requirements, - // i.e. they should be checked like interfaces - checker.visitAttachmentDeclaration(nestedAttachments, kind) - } - return } @@ -362,7 +352,7 @@ func (checker *Checker) declareNestedEvent( eventMembers *orderedmap.OrderedMap[string, *Member], interfaceType Type, ) { - checker.declareCompositeLikeMembersAndValue(nestedCompositeDeclaration, ContainerKindComposite) + checker.declareCompositeLikeMembersAndValue(nestedCompositeDeclaration) // Declare nested composites' values (constructor/instance) as members of the containing composite identifier := *nestedCompositeDeclaration.DeclarationIdentifier() @@ -454,14 +444,8 @@ func (checker *Checker) declareInterfaceMembersAndValue(declaration *ast.Interfa for _, nestedCompositeDeclaration := range declaration.Members.Composites() { if nestedCompositeDeclaration.Kind() == common.CompositeKindEvent { checker.declareNestedEvent(nestedCompositeDeclaration, eventMembers, interfaceType) - } else { - checker.declareCompositeLikeMembersAndValue(nestedCompositeDeclaration, ContainerKindInterface) } } - - for _, nestedAttachmentDeclaration := range declaration.Members.Attachments() { - checker.declareAttachmentMembersAndValue(nestedAttachmentDeclaration, ContainerKindInterface) - } })() } @@ -646,55 +630,6 @@ func (checker *Checker) checkInterfaceConformance( inheritedMembersByName[name] = inheritedMembers } }) - - // Check for nested type conflicts - - reportTypeConflictError := func(typeName string, typ CompositeKindedType, otherType Type) { - otherCompositeType, ok := otherType.(CompositeKindedType) - if !ok { - return - } - - _, isInterface := typ.(*InterfaceType) - _, isOtherTypeInterface := otherCompositeType.(*InterfaceType) - - checker.report(&InterfaceMemberConflictError{ - InterfaceType: interfaceType, - ConflictingInterfaceType: conformance, - MemberName: typeName, - MemberKind: typ.GetCompositeKind().DeclarationKind(isInterface), - ConflictingMemberKind: otherCompositeType.GetCompositeKind().DeclarationKind(isOtherTypeInterface), - Range: ast.NewRangeFromPositioned( - checker.memoryGauge, - interfaceDeclaration.Identifier, - ), - }) - } - - conformance.NestedTypes.Foreach(func(name string, typeRequirement Type) { - compositeType, ok := typeRequirement.(CompositeKindedType) - if !ok { - return - } - - // Check if the type definitions coming from other conformances have conflicts. - if inheritedType, ok := inheritedNestedTypes[name]; ok { - inheritedCompositeType, ok := inheritedType.(CompositeKindedType) - if !ok { - return - } - - reportTypeConflictError(name, compositeType, inheritedCompositeType) - } - - inheritedNestedTypes[name] = typeRequirement - - // Check if the type definitions coming from the current declaration have conflicts. - nestedType, ok := interfaceType.NestedTypes.Get(name) - if ok { - reportTypeConflictError(name, compositeType, nestedType) - } - }) } func (checker *Checker) checkDuplicateInterfaceMember( diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index 873158895a..9814d127b8 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -409,11 +409,11 @@ func (checker *Checker) CheckProgram(program *ast.Program) { } for _, declaration := range program.CompositeDeclarations() { - checker.declareCompositeLikeMembersAndValue(declaration, ContainerKindComposite) + checker.declareCompositeLikeMembersAndValue(declaration) } for _, declaration := range program.AttachmentDeclarations() { - checker.declareAttachmentMembersAndValue(declaration, ContainerKindComposite) + checker.declareAttachmentMembersAndValue(declaration) } // Declare events, functions, and transactions diff --git a/runtime/sema/errors.go b/runtime/sema/errors.go index 54fcf8b049..d974ef4f3a 100644 --- a/runtime/sema/errors.go +++ b/runtime/sema/errors.go @@ -1441,16 +1441,15 @@ type InitializerMismatch struct { InterfaceParameters []Parameter } type ConformanceError struct { - CompositeDeclaration ast.CompositeLikeDeclaration - CompositeType *CompositeType - InterfaceType *InterfaceType - NestedInterfaceType *InterfaceType - InitializerMismatch *InitializerMismatch - MissingMembers []*Member - MemberMismatches []MemberMismatch - MissingNestedCompositeTypes []*CompositeType - Pos ast.Position - InterfaceTypeIsTypeRequirement bool + CompositeDeclaration ast.CompositeLikeDeclaration + CompositeType *CompositeType + InterfaceType *InterfaceType + NestedInterfaceType *InterfaceType + InitializerMismatch *InitializerMismatch + MissingMembers []*Member + MemberMismatches []MemberMismatch + MissingNestedCompositeTypes []*CompositeType + Pos ast.Position } var _ SemanticError = &ConformanceError{} @@ -1462,19 +1461,11 @@ func (*ConformanceError) isSemanticError() {} func (*ConformanceError) IsUserError() {} func (e *ConformanceError) Error() string { - var interfaceDescription string - if e.InterfaceTypeIsTypeRequirement { - interfaceDescription = "type requirement" - } else { - interfaceDescription = "interface" - } - return fmt.Sprintf( - "%s `%s` does not conform to %s %s `%s`", + "%s `%s` does not conform to %s interface `%s`", e.CompositeType.Kind.Name(), e.CompositeType.QualifiedString(), e.InterfaceType.CompositeKind.Name(), - interfaceDescription, e.InterfaceType.QualifiedString(), ) } @@ -1540,14 +1531,6 @@ func (e *ConformanceError) ErrorNotes() (notes []errors.ErrorNote) { }) } - if e.NestedInterfaceType != e.InterfaceType { - compositeIdentifierRange := ast.NewUnmeteredRangeFromPositioned(e.CompositeDeclaration.DeclarationIdentifier()) - notes = append(notes, &NestedConformanceMismatchNote{ - nestedInterfaceType: e.NestedInterfaceType, - Range: compositeIdentifierRange, - }) - } - return } @@ -1561,20 +1544,6 @@ func (n MemberMismatchNote) Message() string { return "mismatch here" } -// NestedConformanceMismatchNote - -type NestedConformanceMismatchNote struct { - nestedInterfaceType *InterfaceType - ast.Range -} - -func (n NestedConformanceMismatchNote) Message() string { - return fmt.Sprintf( - "does not conform to nested interface requirement `%s`", - n.nestedInterfaceType, - ) -} - // DuplicateConformanceError // // TODO: just make this a warning? diff --git a/runtime/sema/type.go b/runtime/sema/type.go index c7d61d5293..e0aa722a7b 100644 --- a/runtime/sema/type.go +++ b/runtime/sema/type.go @@ -4083,12 +4083,11 @@ type CompositeType struct { TypeID TypeID QualifiedIdentifier string } - Members *StringMemberOrderedMap - memberResolvers map[string]MemberResolver - Identifier string - Fields []string - ConstructorParameters []Parameter - ImplicitTypeRequirementConformances []*CompositeType + Members *StringMemberOrderedMap + memberResolvers map[string]MemberResolver + Identifier string + Fields []string + ConstructorParameters []Parameter // an internal set of field `effectiveInterfaceConformances` effectiveInterfaceConformanceSet *InterfaceSet effectiveInterfaceConformances []Conformance @@ -4143,11 +4142,6 @@ func (t *CompositeType) EffectiveInterfaceConformances() []Conformance { return t.effectiveInterfaceConformances } -func (t *CompositeType) addImplicitTypeRequirementConformance(typeRequirement *CompositeType) { - t.ImplicitTypeRequirementConformances = - append(t.ImplicitTypeRequirementConformances, typeRequirement) -} - func (*CompositeType) IsType() {} func (t *CompositeType) String() string { @@ -4447,29 +4441,6 @@ func (t *CompositeType) InterfaceType() *InterfaceType { } } -func (t *CompositeType) TypeRequirements() []*CompositeType { - - var typeRequirements []*CompositeType - - if containerComposite, ok := t.containerType.(*CompositeType); ok { - for _, conformance := range containerComposite.EffectiveInterfaceConformances() { - ty, ok := conformance.InterfaceType.NestedTypes.Get(t.Identifier) - if !ok { - continue - } - - typeRequirement, ok := ty.(*CompositeType) - if !ok { - continue - } - - typeRequirements = append(typeRequirements, typeRequirement) - } - } - - return typeRequirements -} - func (*CompositeType) Unify(_ Type, _ *TypeParameterTypeOrderedMap, _ func(err error), _ ast.Range) bool { // TODO: return false @@ -6386,21 +6357,15 @@ func checkSubTypeWithoutEquality(subType Type, superType Type) bool { // NOTE: type equality case (composite type `T` is subtype of composite type `U`) // is already handled at beginning of function - switch typedSubType := subType.(type) { + switch subType.(type) { case *IntersectionType: // A intersection type `{Us}` is never a subtype of a type `V`: return false case *CompositeType: - // The supertype composite type might be a type requirement. - // Check if the subtype composite type implicitly conforms to it. - - for _, conformance := range typedSubType.ImplicitTypeRequirementConformances { - if conformance == typedSuperType { - return true - } - } + // Non-equal composite types are never subtypes of each other + return false } case *InterfaceType: diff --git a/runtime/sema/type_test.go b/runtime/sema/type_test.go index 75faf04bf0..eaa082063d 100644 --- a/runtime/sema/type_test.go +++ b/runtime/sema/type_test.go @@ -508,12 +508,12 @@ func TestIdentifierCacheUpdate(t *testing.T) { fun test(): Bool } - struct Nested: NestedInterface {} + struct interface Nested: NestedInterface {} } contract TestImpl { - struct Nested { + struct Nested: Test.Nested { fun test(): Bool { return true } diff --git a/runtime/storage_test.go b/runtime/storage_test.go index 4a26716014..81be0853fe 100644 --- a/runtime/storage_test.go +++ b/runtime/storage_test.go @@ -526,7 +526,7 @@ func TestRuntimeTopShotContractDeployment(t *testing.T) { common.AddressLocation{ Address: nftAddress, Name: "NonFungibleToken", - }: realNonFungibleTokenInterface, + }: modifiedNonFungibleTokenInterface, } events := make([]cadence.Event, 0) @@ -613,7 +613,7 @@ func TestRuntimeTopShotBatchTransfer(t *testing.T) { common.AddressLocation{ Address: nftAddress, Name: "NonFungibleToken", - }: realNonFungibleTokenInterface, + }: modifiedNonFungibleTokenInterface, } deployTx := DeploymentTransaction("TopShot", []byte(realTopShotContract)) @@ -747,7 +747,7 @@ func TestRuntimeTopShotBatchTransfer(t *testing.T) { import TopShot from 0x0b2a3299cc857e29 transaction(momentIDs: [UInt64]) { - let transferTokens: @NonFungibleToken.Collection + let transferTokens: @{NonFungibleToken.Collection} prepare(acct: AuthAccount) { let ref = acct.borrow<&TopShot.Collection>(from: /storage/MomentCollection)! diff --git a/runtime/tests/checker/access_test.go b/runtime/tests/checker/access_test.go index 1891eb6909..2851d52214 100644 --- a/runtime/tests/checker/access_test.go +++ b/runtime/tests/checker/access_test.go @@ -2134,38 +2134,6 @@ func TestCheckRestrictiveAccessModifier(t *testing.T) { t.Run(access.Keyword(), func(t *testing.T) { - t.Run("type requirement", func(t *testing.T) { - - _, err := ParseAndCheck(t, - fmt.Sprintf( - ` - access(all) contract interface CI { - - access(all) resource R { - - %[1]s var x: Int - } - } - - access(all) contract C: CI { - - access(all) resource R { - - %[1]s var x: Int - - init () { - self.x = 0 - } - } - } - `, - access.Keyword(), - ), - ) - - require.NoError(t, err) - }) - t.Run("interface", func(t *testing.T) { _, err := ParseAndCheck(t, @@ -2210,26 +2178,6 @@ func TestCheckInvalidRestrictiveAccessModifier(t *testing.T) { t.Run(access.Keyword(), func(t *testing.T) { - t.Run("type requirement", func(t *testing.T) { - - _, err := ParseAndCheck(t, - fmt.Sprintf( - ` - access(all) contract interface CI { - - access(all) resource R { - - %[1]s var x: Int - } - } - `, - access.Keyword(), - ), - ) - - expectInvalidAccessModifierError(t, err) - }) - t.Run("interface", func(t *testing.T) { _, err := ParseAndCheck(t, diff --git a/runtime/tests/checker/attachments_test.go b/runtime/tests/checker/attachments_test.go index d96a244bf2..4b8c95c4e3 100644 --- a/runtime/tests/checker/attachments_test.go +++ b/runtime/tests/checker/attachments_test.go @@ -420,23 +420,6 @@ func TestCheckNestedBaseType(t *testing.T) { assert.IsType(t, &sema.InvalidBaseTypeError{}, errs[0]) }) - t.Run("contract interface", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - attachment A for Test {} - } - `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.InvalidBaseTypeError{}, errs[0]) - }) - t.Run("qualified base type", func(t *testing.T) { t.Parallel() @@ -481,7 +464,7 @@ func TestCheckNestedBaseType(t *testing.T) { }) } -func TestCheckTypeRequirement(t *testing.T) { +func TestCheckTypeRequirementsNoLongerAllowed(t *testing.T) { t.Parallel() @@ -496,195 +479,12 @@ func TestCheckTypeRequirement(t *testing.T) { fun foo(): Int } } - contract C: Test { - - } - `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("concrete struct", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - attachment A for AnyStruct {} - } - contract C: Test { - struct A {} - } - `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[0]) - }) - - t.Run("concrete resource", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - attachment A for AnyStruct {} - } - contract C: Test { - resource A {} - } - `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[0]) - }) - - t.Run("missing method", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - attachment A for AnyStruct { - fun foo(): Int - } - } - contract C: Test { - attachment A for AnyStruct { - - } - } - `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("missing field", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - attachment A for AnyStruct { - let x: Int - } - } - contract C: Test { - attachment A for AnyStruct { - - } - } - `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("incompatible base type", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - struct S {} - attachment A for S { - } - } - contract C: Test { - struct S {} - struct S2 {} - attachment A for S2 { - } - } `, ) errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("basetype subtype", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - attachment A for AnyStruct { - } - } - contract C: Test { - struct S {} - attachment A for S { - } - } - `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("base type Basetype", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - struct S {} - attachment A for S { - } - } - contract C: Test { - struct S {} - attachment A for AnyStruct { - } - } - `, - ) - - require.NoError(t, err) - }) - - t.Run("conforms", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - attachment A for AnyStruct { - fun foo(): Int - } - } - contract C: Test { - attachment A for AnyStruct { - fun foo(): Int {return 3} - } - } - `, - ) - - require.NoError(t, err) + assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) }) } diff --git a/runtime/tests/checker/conformance_test.go b/runtime/tests/checker/conformance_test.go index ea92bc1e9b..c50896d743 100644 --- a/runtime/tests/checker/conformance_test.go +++ b/runtime/tests/checker/conformance_test.go @@ -19,7 +19,6 @@ package checker import ( - "fmt" "testing" "github.com/stretchr/testify/require" @@ -31,9 +30,6 @@ import ( func TestCheckEventNonTypeRequirementConformance(t *testing.T) { t.Parallel() - - // events do not create type requirements - _, err := ParseAndCheck(t, ` access(all) contract interface CI { @@ -49,224 +45,6 @@ func TestCheckEventNonTypeRequirementConformance(t *testing.T) { require.NoError(t, err) } -func TestCheckTypeRequirementConformance(t *testing.T) { - - t.Parallel() - - test := func(preparationCode string, interfaceCode string, conformanceCode string, valid bool) { - _, err := ParseAndCheck(t, - fmt.Sprintf( - ` - %s - - access(all) contract interface CI { - %s - } - - access(all) contract C: CI { - %s - } - `, - preparationCode, - interfaceCode, - conformanceCode, - ), - ) - - if valid { - require.NoError(t, err) - } else { - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.ConformanceError{}, errs[0]) - } - } - - t.Run("Both empty", func(t *testing.T) { - - t.Parallel() - - test( - ``, - `access(all) struct S {}`, - `access(all) struct S {}`, - true, - ) - }) - - t.Run("Conformance with additional function", func(t *testing.T) { - - t.Parallel() - - test( - ``, - ` - access(all) struct S {} - `, - ` - access(all) struct S { - fun foo() {} - } - `, - true, - ) - }) - - t.Run("Conformance with missing function", func(t *testing.T) { - - t.Parallel() - - test( - ``, - ` - access(all) struct S { - fun foo() - } - `, - ` - access(all) struct S {} - `, - false, - ) - }) - - t.Run("Conformance with same name, same parameter type, but different argument label", func(t *testing.T) { - - t.Parallel() - - test( - ``, - ` - access(all) struct S { - fun foo(x: Int) - } - `, - ` - access(all) struct S { - fun foo(y: Int) {} - } - `, - false, - ) - }) - - t.Run("Conformance with same name, same argument label, but different parameter type", func(t *testing.T) { - - t.Parallel() - - test( - ``, - ` - access(all) struct S { - fun foo(x: Int) - } - `, - ` - access(all) struct S { - fun foo(x: String) {} - } - `, - false, - ) - }) - - t.Run("Conformance with same name, same argument label, same parameter type, different parameter name", func(t *testing.T) { - - t.Parallel() - - test( - ``, - ` - access(all) struct S { - fun foo(x y: String) - } - `, - ` - access(all) struct S { - fun foo(x z: String) {} - } - `, - true, - ) - }) - - t.Run("Conformance with more specific parameter type", func(t *testing.T) { - - t.Parallel() - - test( - ` - access(all) struct interface I {} - access(all) struct T: I {} - `, - ` - access(all) struct S { - fun foo(bar: {I}) - } - `, - ` - access(all) struct S { - fun foo(bar: T) {} - } - `, - false, - ) - }) - - t.Run("Conformance with same nested parameter type", func(t *testing.T) { - - t.Parallel() - - test( - ` - access(all) contract X { - struct Bar {} - } - `, - ` - access(all) struct S { - fun foo(bar: X.Bar) - } - `, - ` - access(all) struct S { - fun foo(bar: X.Bar) {} - } - `, - true, - ) - }) - - t.Run("Conformance with different nested parameter type", func(t *testing.T) { - - t.Parallel() - - test( - ` - access(all) contract X { - struct Bar {} - } - - access(all) contract Y { - struct Bar {} - } - `, - ` - access(all) struct S { - fun foo(bar: X.Bar) - } - `, - ` - access(all) struct S { - fun foo(bar: Y.Bar) {} - } - `, - false, - ) - - }) -} - func TestCheckConformanceWithFunctionSubtype(t *testing.T) { t.Parallel() @@ -416,89 +194,6 @@ func TestCheckConformanceWithFunctionSubtype(t *testing.T) { }) } -func TestCheckTypeRequirementDuplicateDeclaration(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface CI { - // Checking if CI_TR1 conforms to CI here, - // requires checking the type requirement CI_TR2 of CI. - // - // Note that CI_TR1 here declares 2 (!) - // nested composite declarations named CI_TR2. - // - // Checking should not just use the first declaration named CI_TR2, - // but detect the second / duplicate, error, - // and stop further conformance checking - // - contract CI_TR1: CI { - contract CI_TR2 {} - contract CI_TR2: CI { - contract CI_TR2_TR {} - } - } - - contract CI_TR2: CI { - contract CI_TR2_TR {} - } - } - `) - - errs := RequireCheckerErrors(t, err, 13) - - require.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) - require.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[1]) - require.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[2]) - require.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[3]) - require.IsType(t, &sema.RedeclarationError{}, errs[4]) - require.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[5]) - require.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[6]) - require.IsType(t, &sema.RedeclarationError{}, errs[7]) - require.IsType(t, &sema.RedeclarationError{}, errs[8]) - require.IsType(t, &sema.RedeclarationError{}, errs[9]) - require.IsType(t, &sema.ConformanceError{}, errs[10]) - require.IsType(t, &sema.ConformanceError{}, errs[11]) - require.IsType(t, &sema.ConformanceError{}, errs[12]) -} - -func TestCheckMultipleTypeRequirements(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface IA { - - struct X { - let a: Int - } - } - - contract interface IB { - - struct X { - let b: Int - } - } - - contract Test: IA, IB { - - struct X { - let a: Int - // missing b - - init() { - self.a = 0 - } - } - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.ConformanceError{}, errs[0]) -} - func TestCheckInitializerConformanceErrorMessages(t *testing.T) { t.Parallel() @@ -578,66 +273,4 @@ func TestCheckInitializerConformanceErrorMessages(t *testing.T) { conformanceErr := errs[0].(*sema.ConformanceError) require.Equal(t, "`R` is missing definitions for members: `foo`, `bar`", conformanceErr.SecondaryError()) }) - - t.Run("1 missing type", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - access(all) contract interface I { - access(all) struct S {} - } - - access(all) contract C: I { - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.ConformanceError{}, errs[0]) - conformanceErr := errs[0].(*sema.ConformanceError) - require.Equal(t, "`C` is missing definitions for types: `I.S`", conformanceErr.SecondaryError()) - }) - - t.Run("2 missing type", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - access(all) contract interface I { - access(all) struct S {} - access(all) resource R {} - } - - access(all) contract C: I { - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.ConformanceError{}, errs[0]) - conformanceErr := errs[0].(*sema.ConformanceError) - require.Equal(t, "`C` is missing definitions for types: `I.S`, `I.R`", conformanceErr.SecondaryError()) - }) - - t.Run("missing type and member", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - access(all) contract interface I { - access(all) struct S {} - access(all) fun foo() - } - - access(all) contract C: I { - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.ConformanceError{}, errs[0]) - conformanceErr := errs[0].(*sema.ConformanceError) - require.Equal(t, "`C` is missing definitions for members: `foo`. `C` is also missing definitions for types: `I.S`", conformanceErr.SecondaryError()) - }) } diff --git a/runtime/tests/checker/contract_test.go b/runtime/tests/checker/contract_test.go index 955dfd7359..fe05a3993b 100644 --- a/runtime/tests/checker/contract_test.go +++ b/runtime/tests/checker/contract_test.go @@ -434,6 +434,10 @@ func TestCheckContractNestedDeclarationsComplex(t *testing.T) { for _, secondKind := range compositeKinds { for _, secondIsInterface := range interfacePossibilities { + if contractIsInterface && (!firstIsInterface || !secondIsInterface) { + continue + } + contractInterfaceKeyword := "" if contractIsInterface { contractInterfaceKeyword = "interface" @@ -722,7 +726,7 @@ func TestCheckBadContractNesting(t *testing.T) { _, err := ParseAndCheck(t, "contract signatureAlgorithm { resource interface payer { contract foo : payer { contract foo { contract foo { } contract foo { contract interface account { } } contract account { } } } } }") - errs := RequireCheckerErrors(t, err, 14) + errs := RequireCheckerErrors(t, err, 9) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[1]) @@ -733,11 +737,6 @@ func TestCheckBadContractNesting(t *testing.T) { assert.IsType(t, &sema.RedeclarationError{}, errs[6]) assert.IsType(t, &sema.RedeclarationError{}, errs[7]) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[8]) - assert.IsType(t, &sema.RedeclarationError{}, errs[9]) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[10]) - assert.IsType(t, &sema.MissingConformanceError{}, errs[11]) - assert.IsType(t, &sema.RedeclarationError{}, errs[12]) - assert.IsType(t, &sema.RedeclarationError{}, errs[13]) } func TestCheckContractEnumAccessRestricted(t *testing.T) { diff --git a/runtime/tests/checker/interface_test.go b/runtime/tests/checker/interface_test.go index c632716766..8d99d7c8d6 100644 --- a/runtime/tests/checker/interface_test.go +++ b/runtime/tests/checker/interface_test.go @@ -1525,299 +1525,113 @@ func TestCheckInterfaceSelfUse(t *testing.T) { } } -func TestCheckInvalidContractInterfaceConformanceMissingTypeRequirement(t *testing.T) { +func TestCheckInvalidTypeRequirementDeclaration(t *testing.T) { t.Parallel() - _, err := ParseAndCheck(t, - ` + t.Run("struct", func(t *testing.T) { + _, err := ParseAndCheck(t, + ` contract interface Test { struct Nested {} } - - contract TestImpl: Test { - // missing 'Nested' - } `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) -} + ) -func TestCheckInvalidContractInterfaceConformanceTypeRequirementKindMismatch(t *testing.T) { + errs := RequireCheckerErrors(t, err, 1) - t.Parallel() + assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) + }) - _, err := ParseAndCheck(t, - ` + t.Run("struct interface ok", func(t *testing.T) { + _, err := ParseAndCheck(t, + ` contract interface Test { - struct Nested {} - } - - contract TestImpl: Test { - // expected struct, not struct interface struct interface Nested {} } + contract C { + struct S: Test.Nested {} + } `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.DeclarationKindMismatchError{}, errs[0]) -} - -func TestCheckInvalidContractInterfaceConformanceTypeRequirementMismatch(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - struct Nested {} - } - - contract TestImpl: Test { - // expected struct - resource Nested {} - } - `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[0]) -} - -func TestCheckContractInterfaceTypeRequirement(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - struct Nested { - fun test(): Int - } - } - `, - ) - - require.NoError(t, err) -} - -func TestCheckContractInterfaceTypeRequirementFunctionImplementation(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - struct Nested { - fun test(): Int { - return 1 - } - } - } - `, - ) - - require.NoError(t, err) - -} - -func TestCheckInvalidContractInterfaceTypeRequirementMissingFunction(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - contract interface Test { - struct Nested { - fun test(): Int - } - } - - contract TestImpl: Test { - struct Nested { - // missing function 'test' - } - } - `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) -} - -func TestCheckContractInterfaceTypeRequirementWithFunction(t *testing.T) { + ) - t.Parallel() + require.NoError(t, err) + }) - _, err := ParseAndCheck(t, - ` + t.Run("resource", func(t *testing.T) { + _, err := ParseAndCheck(t, + ` contract interface Test { - struct Nested { - fun test(): Int - } - } - - contract TestImpl: Test { - struct Nested { - fun test(): Int { - return 1 - } - } + resource Nested {} } `, - ) - - require.NoError(t, err) -} + ) -func TestCheckContractInterfaceTypeRequirementConformanceMissingMembers(t *testing.T) { + errs := RequireCheckerErrors(t, err, 1) - t.Parallel() + assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) + }) - _, err := ParseAndCheck(t, - ` + t.Run("resource interface ok", func(t *testing.T) { + _, err := ParseAndCheck(t, + ` contract interface Test { - - struct interface NestedInterface { - fun test(): Bool - } - - struct Nested: NestedInterface { - // missing function 'test' is valid: - // 'Nested' is a requirement, not an actual declaration - } + resource interface Nested {} } + contract C { + resource S: Test.Nested {} + } `, - ) - - require.NoError(t, err) -} - -func TestCheckInvalidContractInterfaceTypeRequirementConformance(t *testing.T) { + ) - t.Parallel() + require.NoError(t, err) + }) - _, err := ParseAndCheck(t, - ` + t.Run("enum", func(t *testing.T) { + _, err := ParseAndCheck(t, + ` contract interface Test { - - struct interface NestedInterface { - fun test(): Bool - } - - struct Nested: NestedInterface { - // return type mismatch, should be 'Bool' - fun test(): Int - } + enum Nested: Int {} } `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) -} + ) -func TestCheckInvalidContractInterfaceTypeRequirementConformanceMissingFunction(t *testing.T) { + errs := RequireCheckerErrors(t, err, 1) - t.Parallel() + assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) + }) - _, err := ParseAndCheck(t, - ` + t.Run("contract", func(t *testing.T) { + _, err := ParseAndCheck(t, + ` contract interface Test { - - struct interface NestedInterface { - fun test(): Bool - } - - struct Nested: NestedInterface {} - } - - contract TestImpl: Test { - - struct Nested: Test.NestedInterface { - // missing function 'test' - } + contract Nested {} } `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ConformanceError{}, errs[0]) -} + ) -func TestCheckInvalidContractInterfaceTypeRequirementMissingConformance(t *testing.T) { + errs := RequireCheckerErrors(t, err, 1) - t.Parallel() + assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) + }) - _, err := ParseAndCheck(t, - ` + t.Run("contract interface", func(t *testing.T) { + _, err := ParseAndCheck(t, + ` contract interface Test { - - struct interface NestedInterface { - fun test(): Bool - } - - struct Nested: NestedInterface {} - } - - contract TestImpl: Test { - - // missing conformance to 'Test.NestedInterface' - struct Nested { - fun test(): Bool { - return true - } - } + contract interface Nested {} } `, - ) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.MissingConformanceError{}, errs[0]) -} - -func TestCheckContractInterfaceTypeRequirementImplementation(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, - ` - struct interface OtherInterface {} - - contract interface Test { - - struct interface NestedInterface { - fun test(): Bool - } - - struct Nested: NestedInterface {} - } - - contract TestImpl: Test { + ) - struct Nested: Test.NestedInterface, OtherInterface { - fun test(): Bool { - return true - } - } - } - `, - ) + errs := RequireCheckerErrors(t, err, 1) - require.NoError(t, err) + assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) + }) } -func TestCheckContractInterfaceFungibleToken(t *testing.T) { +// TODO: re-enable this test with the v2 fungible token contract +/* func TestCheckContractInterfaceFungibleToken(t *testing.T) { t.Parallel() @@ -1825,9 +1639,10 @@ func TestCheckContractInterfaceFungibleToken(t *testing.T) { _, err := ParseAndCheck(t, code) require.NoError(t, err) -} +} */ -func TestCheckContractInterfaceFungibleTokenConformance(t *testing.T) { +// TODO: re-enable this test with the v2 fungible token contract +/* func TestCheckContractInterfaceFungibleTokenConformance(t *testing.T) { t.Parallel() @@ -1835,7 +1650,7 @@ func TestCheckContractInterfaceFungibleTokenConformance(t *testing.T) { _, err := ParseAndCheckWithPanic(t, code) require.NoError(t, err) -} +} */ func BenchmarkContractInterfaceFungibleToken(b *testing.B) { @@ -1903,7 +1718,8 @@ func BenchmarkCheckContractInterfaceFungibleTokenConformance(b *testing.B) { } } -func TestCheckContractInterfaceFungibleTokenUse(t *testing.T) { +// TODO: re-enable this test with the v2 fungible token contract +/* func TestCheckContractInterfaceFungibleTokenUse(t *testing.T) { t.Parallel() @@ -1930,7 +1746,7 @@ func TestCheckContractInterfaceFungibleTokenUse(t *testing.T) { _, err := ParseAndCheckWithPanic(t, code) require.NoError(t, err) -} +} */ // TestCheckInvalidInterfaceUseAsTypeSuggestion tests that an interface // can not be used as a type, and the suggestion to fix it is correct @@ -2012,44 +1828,6 @@ func TestCheckInvalidMultipleInterfaceDefaultImplementation(t *testing.T) { require.IsType(t, &sema.MultipleInterfaceDefaultImplementationsError{}, errs[0]) }) - - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface IA { - - struct X { - fun test(): Int { - return 41 - } - } - } - - contract interface IB { - - struct X { - fun test(): Int { - return 41 - } - } - } - - contract Test: IA, IB { - - struct X {} - } - - fun test(): Int { - return Test.X().test() - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.MultipleInterfaceDefaultImplementationsError{}, errs[0]) - }) } func TestCheckMultipleInterfaceDefaultImplementationWhenOverriden(t *testing.T) { @@ -2086,46 +1864,6 @@ func TestCheckMultipleInterfaceDefaultImplementationWhenOverriden(t *testing.T) require.NoError(t, err) }) - - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface IA { - - struct X { - fun test(): Int { - return 41 - } - } - } - - contract interface IB { - - struct X { - fun test(): Int { - return 41 - } - } - } - - contract Test: IA, IB { - - struct X { - fun test(): Int { - return 42 - } - } - } - - fun test(): Int { - return Test.X().test() - } - `) - - require.NoError(t, err) - }) } func TestCheckMultipleInterfaceSingleInterfaceDefaultImplementation(t *testing.T) { @@ -2160,40 +1898,6 @@ func TestCheckMultipleInterfaceSingleInterfaceDefaultImplementation(t *testing.T require.IsType(t, &sema.DefaultFunctionConflictError{}, errs[0]) }) - - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface IA { - - struct X { - fun test(): Int { - return 41 - } - } - } - - contract interface IB { - struct X { - fun test(): Int - } - } - - contract Test: IA, IB { - struct X {} - } - - fun test(): Int { - return Test.X().test() - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.DefaultFunctionConflictError{}, errs[0]) - }) } func TestCheckMultipleInterfaceSingleInterfaceDefaultImplementationWhenOverridden(t *testing.T) { @@ -2227,61 +1931,24 @@ func TestCheckMultipleInterfaceSingleInterfaceDefaultImplementationWhenOverridde `) require.NoError(t, err) }) +} + +func TestCheckInterfaceDefaultImplementation(t *testing.T) { - t.Run("type requirement", func(t *testing.T) { + t.Parallel() + + t.Run("interface", func(t *testing.T) { t.Parallel() _, err := ParseAndCheck(t, ` - contract interface IA { - - struct X { - fun test(): Int { - return 41 - } + struct interface IA { + fun test(): Int { + return 42 } } - contract interface IB { - - struct X { - fun test(): Int - } - } - - contract Test: IA, IB { - - struct X { - fun test(): Int { - return 42 - } - } - } - - fun test(): Int { - return Test.X().test() - } - `) - require.NoError(t, err) - }) -} - -func TestCheckInterfaceDefaultImplementation(t *testing.T) { - - t.Parallel() - - t.Run("interface", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - struct interface IA { - fun test(): Int { - return 42 - } - } - - struct Test: IA {} + struct Test: IA {} fun test(): Int { return Test().test() @@ -2289,32 +1956,6 @@ func TestCheckInterfaceDefaultImplementation(t *testing.T) { `) require.NoError(t, err) }) - - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface IA { - - struct X { - fun test(): Int { - return 42 - } - } - } - - contract Test: IA { - - struct X {} - } - - fun test(): Int { - return Test.X().test() - } - `) - require.NoError(t, err) - }) } func TestCheckInterfaceDefaultImplementationOverriden(t *testing.T) { @@ -2344,36 +1985,6 @@ func TestCheckInterfaceDefaultImplementationOverriden(t *testing.T) { `) require.NoError(t, err) }) - - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface IA { - - struct X { - fun test(): Int { - return 41 - } - } - } - - contract Test: IA { - - struct X { - fun test(): Int { - return 42 - } - } - } - - fun test(): Int { - return Test.X().test() - } - `) - require.NoError(t, err) - }) } func TestSpecialFunctionDefaultImplementationUsage(t *testing.T) { @@ -2406,43 +2017,6 @@ func TestSpecialFunctionDefaultImplementationUsage(t *testing.T) { require.IsType(t, &sema.SpecialFunctionDefaultImplementationError{}, errs[0]) }) - - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface IA { - - struct X { - var x: Int - - init() { - self.x = 1 - } - } - } - - contract Test: IA { - - struct X { - var x: Int - - init() { - self.x = 0 - } - } - } - - fun test() { - Test.X() - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.SpecialFunctionDefaultImplementationError{}, errs[0]) - }) } func TestCheckInvalidInterfaceDefaultImplementationConcreteTypeUsage(t *testing.T) { @@ -2477,41 +2051,6 @@ func TestCheckInvalidInterfaceDefaultImplementationConcreteTypeUsage(t *testing. require.IsType(t, &sema.NotDeclaredMemberError{}, errs[0]) }) - - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface IA { - - struct X { - fun test(): Int { - return self.x - } - } - } - - contract Test: IA { - - struct X { - let x: Int - - init() { - self.x = 0 - } - } - } - - fun test() { - Test.X() - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.NotDeclaredMemberError{}, errs[0]) - }) } func TestCheckInvalidInterfaceDefaultImplementationConcreteTypeUsage2(t *testing.T) { @@ -2548,43 +2087,6 @@ func TestCheckInvalidInterfaceDefaultImplementationConcreteTypeUsage2(t *testing require.IsType(t, &sema.AssignmentToConstantMemberError{}, errs[0]) }) - - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface IA { - - struct X { - x: Int - - fun test() { - self.x = 1 - } - } - } - - contract Test: IA { - - struct X { - let x: Int - - init() { - self.x = 0 - } - } - } - - fun test() { - Test.X() - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.AssignmentToConstantMemberError{}, errs[0]) - }) } func TestCheckInterfaceDefaultImplementationConcreteTypeUsage(t *testing.T) { @@ -2618,40 +2120,6 @@ func TestCheckInterfaceDefaultImplementationConcreteTypeUsage(t *testing.T) { `) require.NoError(t, err) }) - - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface IA { - - struct X { - let x: Int - - fun test(): Int { - return self.x - } - } - } - - contract Test: IA { - - struct X { - let x: Int - - init() { - self.x = 0 - } - } - } - - fun test(): Int { - return Test.X().test() - } - `) - require.NoError(t, err) - }) } func TestCheckBadStructInterface(t *testing.T) { @@ -2659,7 +2127,7 @@ func TestCheckBadStructInterface(t *testing.T) { _, err := ParseAndCheck(t, "struct interface foo { contract h : foo { contract h { } contract h { contract h { } } } }") - errs := RequireCheckerErrors(t, err, 12) + errs := RequireCheckerErrors(t, err, 7) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[1]) @@ -2668,11 +2136,6 @@ func TestCheckBadStructInterface(t *testing.T) { assert.IsType(t, &sema.RedeclarationError{}, errs[4]) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[5]) assert.IsType(t, &sema.RedeclarationError{}, errs[6]) - assert.IsType(t, &sema.RedeclarationError{}, errs[7]) - assert.IsType(t, &sema.RedeclarationError{}, errs[8]) - assert.IsType(t, &sema.RedeclarationError{}, errs[9]) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[10]) - assert.IsType(t, &sema.RedeclarationError{}, errs[11]) } func TestCheckInterfaceInheritance(t *testing.T) { @@ -3941,244 +3404,6 @@ func TestCheckInterfaceTypeDefinitionInheritance(t *testing.T) { t.Parallel() - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface A { - struct Nested { - access(all) fun test(): Int { - return 3 - } - } - } - - contract interface B: A {} - - contract interface C: B {} - - contract X: C { - struct Nested { - access(all) fun test(): Int { - return 3 - } - } - } - `) - - require.NoError(t, err) - }) - - t.Run("type requirement negative", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface A { - struct Nested { - access(all) fun test(): Int { - return 3 - } - } - } - - contract interface B: A {} - - contract interface C: B {} - - contract X: C {} - `) - - errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("type requirement wrong entitlement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - entitlement E - - contract interface A { - struct Nested { - access(all) fun test(): Int { - return 3 - } - } - } - - contract interface B: A {} - - contract interface C: B {} - - contract X: C { - struct Nested { - access(E) fun test(): Int { - return 3 - } - } - } - `) - - errs := RequireCheckerErrors(t, err, 1) - assert.IsType(t, &sema.ConformanceError{}, errs[0]) - }) - - t.Run("type requirement multiple", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface A { - struct ANested { - access(all) fun test(): Int { - return 3 - } - } - } - - contract interface B { - struct BNested { - access(all) fun test(): Int { - return 4 - } - } - } - - contract interface C: A, B {} - - contract X: C { - struct ANested { - access(all) fun test(): Int { - return 3 - } - } - - struct BNested { - access(all) fun test(): Int { - return 3 - } - } - } - `) - - require.NoError(t, err) - }) - - t.Run("type requirement multiple not conforming", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface A { - struct ANested { - access(all) fun test(): Int { - return 3 - } - } - } - - contract interface B { - struct BNested { - access(all) fun test(): Int { - return 4 - } - } - } - - contract interface C: A, B {} - - contract X: C { - struct ANested { - access(all) fun test(): Int { - return 3 - } - } - } - - contract Y: C { - struct BNested { - access(all) fun test(): Int { - return 3 - } - } - } - `) - - errs := RequireCheckerErrors(t, err, 2) - - conformanceError := &sema.ConformanceError{} - require.ErrorAs(t, errs[0], &conformanceError) - assert.Empty(t, conformanceError.MissingMembers) - assert.Len(t, conformanceError.MissingNestedCompositeTypes, 1) - assert.Equal(t, conformanceError.MissingNestedCompositeTypes[0].Identifier, "BNested") - - require.ErrorAs(t, errs[1], &conformanceError) - assert.Empty(t, conformanceError.MissingMembers) - assert.Len(t, conformanceError.MissingNestedCompositeTypes, 1) - assert.Equal(t, conformanceError.MissingNestedCompositeTypes[0].Identifier, "ANested") - }) - - t.Run("nested struct conflicting", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface A { - struct Nested { - access(all) fun test(): Int { - return 3 - } - } - } - - contract interface B: A { - struct Nested { - access(all) fun test(): String { - return "three" - } - } - } - `) - - errs := RequireCheckerErrors(t, err, 1) - memberConflictError := &sema.InterfaceMemberConflictError{} - require.ErrorAs(t, errs[0], &memberConflictError) - assert.Equal(t, common.DeclarationKindStructure, memberConflictError.MemberKind) - assert.Equal(t, common.DeclarationKindStructure, memberConflictError.ConflictingMemberKind) - }) - - t.Run("nested identical struct conflict", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface A { - struct Nested { - access(all) fun test(): Int { - return 3 - } - } - } - - contract interface B: A { - struct Nested { - access(all) fun test(): Int { - return 3 - } - } - } - `) - - errs := RequireCheckerErrors(t, err, 1) - memberConflictError := &sema.InterfaceMemberConflictError{} - require.ErrorAs(t, errs[0], &memberConflictError) - assert.Equal(t, common.DeclarationKindStructure, memberConflictError.MemberKind) - assert.Equal(t, common.DeclarationKindStructure, memberConflictError.ConflictingMemberKind) - }) - t.Run("nested resource interface conflicting", func(t *testing.T) { t.Parallel() @@ -4201,103 +3426,8 @@ func TestCheckInterfaceTypeDefinitionInheritance(t *testing.T) { } `) - errs := RequireCheckerErrors(t, err, 1) - memberConflictError := &sema.InterfaceMemberConflictError{} - require.ErrorAs(t, errs[0], &memberConflictError) - assert.Equal(t, common.DeclarationKindResourceInterface, memberConflictError.MemberKind) - assert.Equal(t, common.DeclarationKindResourceInterface, memberConflictError.ConflictingMemberKind) - }) - - t.Run("nested mixed types conflicting", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface A { - struct interface Nested { - access(all) fun test(): Int { - return 3 - } - } - } - - contract interface B: A { - resource Nested { - access(all) fun test(): String { - return "three" - } - } - } - `) - - errs := RequireCheckerErrors(t, err, 1) - memberConflictError := &sema.InterfaceMemberConflictError{} - require.ErrorAs(t, errs[0], &memberConflictError) - assert.Equal(t, common.DeclarationKindStructureInterface, memberConflictError.MemberKind) - assert.Equal(t, common.DeclarationKindResource, memberConflictError.ConflictingMemberKind) - }) - - t.Run("nested struct conflicting indirect", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface A { - struct Nested { - access(all) fun test(): Int { - return 3 - } - } - } - - contract interface B { - struct Nested { - access(all) fun test(): String { - return "three" - } - } - } - - contract interface C: A, B {} - `) - - errs := RequireCheckerErrors(t, err, 1) - memberConflictError := &sema.InterfaceMemberConflictError{} - require.ErrorAs(t, errs[0], &memberConflictError) - assert.Equal(t, common.DeclarationKindStructure, memberConflictError.MemberKind) - assert.Equal(t, common.DeclarationKindStructure, memberConflictError.ConflictingMemberKind) - }) - - t.Run("nested type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface A { - struct NestedA { - access(all) fun test(): Int { - return 3 - } - } - } - - contract interface B { - struct NestedB { - access(all) fun test(): String { - return "three" - } - } - } - - contract interface C: A, B {} - - contract D: C {} - `) - - errs := RequireCheckerErrors(t, err, 2) - conformanceError := &sema.ConformanceError{} - require.ErrorAs(t, errs[0], &conformanceError) - require.ErrorAs(t, errs[1], &conformanceError) + // A.Nested and B.Nested are two distinct separate functions + require.NoError(t, err) }) t.Run("nested interface inheritance", func(t *testing.T) { diff --git a/runtime/tests/checker/intersection_test.go b/runtime/tests/checker/intersection_test.go index 5ace34afdc..d3395b740c 100644 --- a/runtime/tests/checker/intersection_test.go +++ b/runtime/tests/checker/intersection_test.go @@ -264,36 +264,6 @@ func TestCheckIntersectionType(t *testing.T) { assert.IsType(t, &sema.AmbiguousIntersectionTypeError{}, errs[0]) }) - - t.Run("intersection type requirement", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - contract interface CI { - resource interface RI {} - - resource R: RI {} - - fun createR(): @R - } - - contract C: CI { - resource R: CI.RI {} - - fun createR(): @R { - return <- create R() - } - } - - fun test() { - let r <- C.createR() - let r2: @{CI.RI} <- r - destroy r2 - } - `) - require.NoError(t, err) - }) } func TestCheckIntersectionTypeMemberAccess(t *testing.T) { diff --git a/runtime/tests/checker/nesting_test.go b/runtime/tests/checker/nesting_test.go index 4f235ee487..273915aa34 100644 --- a/runtime/tests/checker/nesting_test.go +++ b/runtime/tests/checker/nesting_test.go @@ -103,13 +103,21 @@ func TestCheckCompositeDeclarationNesting(t *testing.T) { assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) + case common.CompositeKindEvent: + require.NoError(t, err) + case common.CompositeKindResource, common.CompositeKindStructure, - common.CompositeKindEvent, common.CompositeKindAttachment, common.CompositeKindEnum: - require.NoError(t, err) + if outerIsInterface && !innerIsInterface { + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) + } else { + require.NoError(t, err) + } default: t.Errorf("unknown outer composite kind %s", outerComposite) diff --git a/runtime/tests/checker/nft_test.go b/runtime/tests/checker/nft_test.go index 7e4efb3ff0..a32cd6a324 100644 --- a/runtime/tests/checker/nft_test.go +++ b/runtime/tests/checker/nft_test.go @@ -18,17 +18,8 @@ package checker -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/onflow/cadence/runtime/ast" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/sema" - "github.com/onflow/cadence/runtime/stdlib" -) - +// TODO: re-enable this test with the v2 fungible token contract +/* const realNonFungibleTokenContractInterface = ` // The main NFT contract interface. Other NFT contracts will @@ -137,7 +128,6 @@ access(all) contract interface NonFungibleToken { } } ` - const topShotContract = ` import NonFungibleToken from 0x1 @@ -199,7 +189,7 @@ access(all) contract TopShot: NonFungibleToken { access(self) var sets: @{UInt32: Set} // The ID that is used to create Plays. - // Every time a Play is created, playID is assigne + // Every time a Play is created, playID is assigne // to the new Play's ID and then is incremented by 1. access(all) var nextPlayID: UInt32 @@ -512,7 +502,7 @@ access(all) contract TopShot: NonFungibleToken { // Global unique moment ID access(all) let id: UInt64 - + // Struct of Moment metadata access(all) let data: MomentData @@ -535,13 +525,13 @@ access(all) contract TopShot: NonFungibleToken { } } - // Admin is a special authorization resource that - // allows the owner to perform important functions to modify the + // Admin is a special authorization resource that + // allows the owner to perform important functions to modify the // various aspects of the Plays, Sets, and Moments // access(all) resource Admin { - // createPlay creates a new Play struct + // createPlay creates a new Play struct // and stores it in the Plays dictionary in the TopShot smart contract // // Parameters: metadata: A dictionary mapping metadata titles to their data @@ -628,16 +618,16 @@ access(all) contract TopShot: NonFungibleToken { // If the result isn't nil, the id of the returned reference // should be the same as the argument to the function post { - (result == nil) || (result?.id == id): + (result == nil) || (result?.id == id): "Cannot borrow Moment reference: The ID of the returned reference is incorrect" } } } - // Collection is a resource that every user who owns NFTs + // Collection is a resource that every user who owns NFTs // will store in their account to manage their NFTS // - access(all) resource Collection: MomentCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic { + access(all) resource Collection: MomentCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic { // Dictionary of Moment conforming tokens // NFT is a resource type with a UInt64 ID field access(all) var ownedNFTs: @{UInt64: NonFungibleToken.NFT} @@ -648,14 +638,14 @@ access(all) contract TopShot: NonFungibleToken { // withdraw removes an Moment from the Collection and moves it to the caller // - // Parameters: withdrawID: The ID of the NFT + // Parameters: withdrawID: The ID of the NFT // that is to be removed from the Collection // // returns: @NonFungibleToken.NFT the token that was withdrawn access(NonFungibleToken.Withdrawable) fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { // Remove the nft from the Collection - let token <- self.ownedNFTs.remove(key: withdrawID) + let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Cannot withdraw: Moment does not exist in the collection") emit Withdraw(id: token.id, from: self.owner?.address) @@ -700,7 +690,7 @@ access(all) contract TopShot: NonFungibleToken { // Add the new token to the dictionary let oldToken <- self.ownedNFTs[id] <- token - // Only emit a deposit event if the Collection + // Only emit a deposit event if the Collection // is in an account's storage if self.owner?.address != nil { emit Deposit(id: id, to: self.owner?.address) @@ -739,7 +729,7 @@ access(all) contract TopShot: NonFungibleToken { // Returns: A reference to the NFT // // Note: This only allows the caller to read the ID of the NFT, - // not any topshot specific data. Please use borrowMoment to + // not any topshot specific data. Please use borrowMoment to // read Moment data. // access(all) fun borrowNFT(id: UInt64): &NonFungibleToken.NFT { @@ -796,7 +786,7 @@ access(all) contract TopShot: NonFungibleToken { } // getPlayMetaData returns all the metadata associated with a specific Play - // + // // Parameters: playID: The id of the Play that is being searched // // Returns: The metadata as a String to String mapping optional @@ -804,11 +794,11 @@ access(all) contract TopShot: NonFungibleToken { return self.playDatas[playID]?.metadata } - // getPlayMetaDataByField returns the metadata associated with a + // getPlayMetaDataByField returns the metadata associated with a // specific field of the metadata // Ex: field: "Team" will return something // like "Memphis Grizzlies" - // + // // Parameters: playID: The id of the Play that is being searched // field: The field to search for // @@ -824,7 +814,7 @@ access(all) contract TopShot: NonFungibleToken { // getSetName returns the name that the specified Set // is associated with. - // + // // Parameters: setID: The id of the Set that is being searched // // Returns: The name of the Set @@ -835,7 +825,7 @@ access(all) contract TopShot: NonFungibleToken { // getSetSeries returns the series that the specified Set // is associated with. - // + // // Parameters: setID: The id of the Set that is being searched // // Returns: The series that the Set belongs to @@ -846,7 +836,7 @@ access(all) contract TopShot: NonFungibleToken { // getSetIDsByName returns the IDs that the specified Set name // is associated with. - // + // // Parameters: setName: The name of the Set that is being searched // // Returns: An array of the IDs of the Set if it exists, or nil if doesn't @@ -871,7 +861,7 @@ access(all) contract TopShot: NonFungibleToken { } // getPlaysInSet returns the list of Play IDs that are in the Set - // + // // Parameters: setID: The id of the Set that is being searched // // Returns: An array of Play IDs @@ -884,7 +874,7 @@ access(all) contract TopShot: NonFungibleToken { // (otherwise known as an edition) is retired. // If an edition is retired, it still remains in the Set, // but Moments can no longer be minted from it. - // + // // Parameters: setID: The id of the Set that is being searched // playID: The id of the Play that is being searched // @@ -910,10 +900,10 @@ access(all) contract TopShot: NonFungibleToken { } // isSetLocked returns a boolean that indicates if a Set - // is locked. If it's locked, + // is locked. If it's locked, // new Plays can no longer be added to it, // but Moments can still be minted from Plays the set contains. - // + // // Parameters: setID: The id of the Set that is being searched // // Returns: Boolean indicating if the Set is locked or not @@ -922,13 +912,13 @@ access(all) contract TopShot: NonFungibleToken { return TopShot.sets[setID]?.locked } - // getNumMomentsInEdition return the number of Moments that have been + // getNumMomentsInEdition return the number of Moments that have been // minted from a certain edition. // // Parameters: setID: The id of the Set that is being searched // playID: The id of the Play that is being searched // - // Returns: The total number of Moments + // Returns: The total number of Moments // that have been minted from an edition access(all) fun getNumMomentsInEdition(setID: UInt32, playID: UInt32): UInt32? { // Don't force a revert if the Set or play ID is invalid @@ -976,8 +966,7 @@ access(all) contract TopShot: NonFungibleToken { } } ` - -func TestCheckTopShotContract(t *testing.T) { + func TestCheckTopShotContract(t *testing.T) { t.Parallel() @@ -1013,4 +1002,4 @@ func TestCheckTopShotContract(t *testing.T) { }, ) require.NoError(t, err) -} +} */ diff --git a/runtime/tests/checker/reference_test.go b/runtime/tests/checker/reference_test.go index dc9527b1e8..5b5909ffe9 100644 --- a/runtime/tests/checker/reference_test.go +++ b/runtime/tests/checker/reference_test.go @@ -1365,57 +1365,6 @@ func TestCheckArrayAccessReference(t *testing.T) { require.NoError(t, err) } -func TestCheckReferenceTypeImplicitConformance(t *testing.T) { - - t.Parallel() - - t.Run("valid", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - - contract interface CI { - struct S {} - } - - contract C: CI { - struct S {} - } - - let s = C.S() - - let refS: &CI.S = &s as &C.S - `) - - require.NoError(t, err) - }) - - t.Run("invalid", func(t *testing.T) { - - t.Parallel() - - _, err := ParseAndCheck(t, ` - - contract interface CI { - struct S {} - } - - contract C { - struct S {} - } - - let s = C.S() - - let refS: &CI.S = &s as &C.S - `) - - errs := RequireCheckerErrors(t, err, 1) - - require.IsType(t, &sema.TypeMismatchError{}, errs[0]) - }) -} - func TestCheckInvalidatedReferenceUse(t *testing.T) { t.Parallel() diff --git a/runtime/tests/checker/resources_test.go b/runtime/tests/checker/resources_test.go index 0155571c93..a00f7df1f7 100644 --- a/runtime/tests/checker/resources_test.go +++ b/runtime/tests/checker/resources_test.go @@ -4680,29 +4680,6 @@ func TestCheckInvalidResourceOptionalBindingFailableCastMissingElse(t *testing.T assert.IsType(t, &sema.ResourceLossError{}, errs[0]) }) - - t.Run("contract interface resource to contract to resource", func(t *testing.T) { - - _, err := ParseAndCheck(t, ` - contract interface CI { - resource R {} - } - - contract C: CI { - resource R {} - } - - fun test(r: @CI.R) { - if let r2 <- r as? @C.R { - destroy r2 - } - } - `) - - errs := RequireCheckerErrors(t, err, 1) - - assert.IsType(t, &sema.ResourceLossError{}, errs[0]) - }) } func TestCheckInvalidResourceFailableCastOutsideOptionalBinding(t *testing.T) { @@ -9353,7 +9330,7 @@ func TestCheckBadResourceInterface(t *testing.T) { _, err := ParseAndCheck(t, "resource interface foo{struct d:foo{ struct d:foo{ }struct d:foo{ struct d:foo{ }}}}") - errs := RequireCheckerErrors(t, err, 17) + errs := RequireCheckerErrors(t, err, 6) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[1]) @@ -9361,24 +9338,13 @@ func TestCheckBadResourceInterface(t *testing.T) { assert.IsType(t, &sema.RedeclarationError{}, errs[3]) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[4]) assert.IsType(t, &sema.RedeclarationError{}, errs[5]) - assert.IsType(t, &sema.RedeclarationError{}, errs[6]) - assert.IsType(t, &sema.RedeclarationError{}, errs[7]) - assert.IsType(t, &sema.RedeclarationError{}, errs[8]) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[9]) - assert.IsType(t, &sema.RedeclarationError{}, errs[10]) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[11]) - assert.IsType(t, &sema.ConformanceError{}, errs[12]) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[13]) - assert.IsType(t, &sema.ConformanceError{}, errs[14]) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[15]) - assert.IsType(t, &sema.ConformanceError{}, errs[16]) }) t.Run("bad resource interface: longer", func(t *testing.T) { _, err := ParseAndCheck(t, "resource interface foo{struct d:foo{ contract d:foo{ contract x:foo{ struct d{} contract d:foo{ contract d:foo {}}}}}}") - errs := RequireCheckerErrors(t, err, 22) + errs := RequireCheckerErrors(t, err, 9) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[0]) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[1]) @@ -9389,19 +9355,6 @@ func TestCheckBadResourceInterface(t *testing.T) { assert.IsType(t, &sema.RedeclarationError{}, errs[6]) assert.IsType(t, &sema.InvalidNestedDeclarationError{}, errs[7]) assert.IsType(t, &sema.RedeclarationError{}, errs[8]) - assert.IsType(t, &sema.RedeclarationError{}, errs[9]) - assert.IsType(t, &sema.RedeclarationError{}, errs[10]) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[11]) - assert.IsType(t, &sema.ConformanceError{}, errs[12]) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[13]) - assert.IsType(t, &sema.ConformanceError{}, errs[14]) - assert.IsType(t, &sema.RedeclarationError{}, errs[15]) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[16]) - assert.IsType(t, &sema.RedeclarationError{}, errs[17]) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[18]) - assert.IsType(t, &sema.ConformanceError{}, errs[19]) - assert.IsType(t, &sema.CompositeKindMismatchError{}, errs[20]) - assert.IsType(t, &sema.ConformanceError{}, errs[21]) }) } diff --git a/runtime/tests/interpreter/condition_test.go b/runtime/tests/interpreter/condition_test.go index 0852d56955..41ed2f404d 100644 --- a/runtime/tests/interpreter/condition_test.go +++ b/runtime/tests/interpreter/condition_test.go @@ -983,141 +983,6 @@ func TestInterpretInitializerWithInterfacePreCondition(t *testing.T) { } } -func TestInterpretTypeRequirementWithPreCondition(t *testing.T) { - - t.Parallel() - - var events []testEvent - - inter, err := parseCheckAndInterpretWithOptions(t, - ` - event AlsoPre(x: Int) - - access(all) struct interface Also { - access(all) fun test(x: Int) { - pre { - x >= 0: "x >= 0" - emit AlsoPre(x: x) - } - } - } - - event ReqPre(x: Int) - - access(all) contract interface Test { - - access(all) struct Nested { - access(all) fun test(x: Int) { - pre { - x >= 1: "x >= 1" - emit ReqPre(x: x) - } - } - } - } - - event ImplPre(x: Int) - - access(all) contract TestImpl: Test { - - access(all) struct Nested: Also { - access(all) fun test(x: Int) { - pre { - x < 2: "x < 2" - emit ImplPre(x: x) - } - } - } - } - - access(all) fun test(x: Int) { - TestImpl.Nested().test(x: x) - } - `, - ParseCheckAndInterpretOptions{ - Config: &interpreter.Config{ - ContractValueHandler: makeContractValueHandler(nil, nil, nil), - OnEventEmitted: func( - _ *interpreter.Interpreter, - _ interpreter.LocationRange, - event *interpreter.CompositeValue, - eventType *sema.CompositeType, - ) error { - events = append(events, testEvent{ - event: event, - eventType: eventType, - }) - return nil - }, - }, - }, - ) - require.NoError(t, err) - - t.Run("-1", func(t *testing.T) { - events = nil - - _, err := inter.Invoke("test", interpreter.NewUnmeteredIntValueFromInt64(-1)) - RequireError(t, err) - - var conditionErr interpreter.ConditionError - require.ErrorAs(t, err, &conditionErr) - - // NOTE: The type requirement condition (`Test.Nested`) is evaluated first, - // before the type's conformances (`Also`) - - assert.Equal(t, "x >= 1", conditionErr.Message) - - require.Len(t, events, 0) - }) - - t.Run("0", func(t *testing.T) { - events = nil - - _, err := inter.Invoke("test", interpreter.NewUnmeteredIntValueFromInt64(0)) - RequireError(t, err) - - var conditionErr interpreter.ConditionError - require.ErrorAs(t, err, &conditionErr) - - assert.Equal(t, "x >= 1", conditionErr.Message) - - require.Len(t, events, 0) - }) - - t.Run("1", func(t *testing.T) { - events = nil - - value, err := inter.Invoke("test", interpreter.NewUnmeteredIntValueFromInt64(1)) - require.NoError(t, err) - - assert.IsType(t, - interpreter.Void, - value, - ) - - require.Len(t, events, 3) - }) - - t.Run("2", func(t *testing.T) { - events = nil - - _, err := inter.Invoke("test", interpreter.NewUnmeteredIntValueFromInt64(2)) - require.IsType(t, - interpreter.Error{}, - err, - ) - interpreterErr := err.(interpreter.Error) - - require.IsType(t, - interpreter.ConditionError{}, - interpreterErr.Err, - ) - - require.Len(t, events, 2) - }) -} - func TestInterpretResourceInterfaceInitializerAndDestructorPreConditions(t *testing.T) { t.Parallel() @@ -1225,158 +1090,6 @@ func TestInterpretResourceInterfaceInitializerAndDestructorPreConditions(t *test }) } -func TestInterpretResourceTypeRequirementInitializerAndDestructorPreConditions(t *testing.T) { - - t.Parallel() - - newInterpreter := func(t *testing.T) (inter *interpreter.Interpreter, getEvents func() []testEvent) { - var events []testEvent - - var err error - inter, err = parseCheckAndInterpretWithOptions(t, ` - access(all) - event InitPre(x: Int) - - access(all) - event DestroyPre(x: Int) - - access(all) - contract interface CI { - - access(all) - resource R { - - access(all) - x: Int - - init(_ x: Int) { - pre { - x > 1: "invalid init" - emit InitPre(x: x) - } - } - - destroy() { - pre { - self.x < 3: "invalid destroy" - emit DestroyPre(x: self.x) - } - } - } - } - - access(all) - contract C: CI { - - access(all) - resource R { - - access(all) - let x: Int - - init(_ x: Int) { - self.x = x - } - } - - access(all) - fun test(_ x: Int) { - let r <- create C.R(x) - destroy r - } - } - - access(all) - fun test(_ x: Int) { - C.test(x) - } - `, - ParseCheckAndInterpretOptions{ - Config: &interpreter.Config{ - ContractValueHandler: makeContractValueHandler(nil, nil, nil), - OnEventEmitted: func( - _ *interpreter.Interpreter, - _ interpreter.LocationRange, - event *interpreter.CompositeValue, - eventType *sema.CompositeType, - ) error { - events = append(events, testEvent{ - event: event, - eventType: eventType, - }) - return nil - }, - }, - }, - ) - require.NoError(t, err) - - getEvents = func() []testEvent { - return events - } - - return - } - - t.Run("1", func(t *testing.T) { - t.Parallel() - - inter, getEvents := newInterpreter(t) - _, err := inter.Invoke("test", interpreter.NewUnmeteredIntValueFromInt64(1)) - RequireError(t, err) - - require.IsType(t, - interpreter.Error{}, - err, - ) - interpreterErr := err.(interpreter.Error) - - require.IsType(t, - interpreter.ConditionError{}, - interpreterErr.Err, - ) - conditionError := interpreterErr.Err.(interpreter.ConditionError) - - assert.Equal(t, "invalid init", conditionError.Message) - - require.Len(t, getEvents(), 0) - }) - - t.Run("2", func(t *testing.T) { - t.Parallel() - - inter, getEvents := newInterpreter(t) - _, err := inter.Invoke("test", interpreter.NewUnmeteredIntValueFromInt64(2)) - require.NoError(t, err) - - require.Len(t, getEvents(), 2) - }) - - t.Run("3", func(t *testing.T) { - t.Parallel() - - inter, getEvents := newInterpreter(t) - _, err := inter.Invoke("test", interpreter.NewUnmeteredIntValueFromInt64(3)) - RequireError(t, err) - - require.IsType(t, - interpreter.Error{}, - err, - ) - interpreterErr := err.(interpreter.Error) - - require.IsType(t, - interpreter.ConditionError{}, - interpreterErr.Err, - ) - conditionError := interpreterErr.Err.(interpreter.ConditionError) - - assert.Equal(t, "invalid destroy", conditionError.Message) - - require.Len(t, getEvents(), 1) - }) -} - func TestInterpretFunctionPostConditionInInterface(t *testing.T) { t.Parallel() @@ -1566,8 +1279,8 @@ func TestInterpretIsInstanceCheckInPreCondition(t *testing.T) { fmt.Sprintf( ` contract interface CI { - struct X { - fun use(_ x: X) { + struct interface X { + fun use(_ x: {X}) { pre { %s } @@ -1576,14 +1289,14 @@ func TestInterpretIsInstanceCheckInPreCondition(t *testing.T) { } contract C1: CI { - struct X { - fun use(_ x: CI.X) {} + struct X: CI.X { + fun use(_ x: {CI.X}) {} } } contract C2: CI { - struct X { - fun use(_ x: CI.X) {} + struct X: CI.X { + fun use(_ x: {CI.X}) {} } } diff --git a/runtime/tests/interpreter/interface_test.go b/runtime/tests/interpreter/interface_test.go index 833357cd0a..43d3aac568 100644 --- a/runtime/tests/interpreter/interface_test.go +++ b/runtime/tests/interpreter/interface_test.go @@ -65,47 +65,6 @@ func TestInterpretInterfaceDefaultImplementation(t *testing.T) { ) }) - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - inter, err := parseCheckAndInterpretWithOptions(t, ` - - contract interface IA { - - struct X { - fun test(): Int { - return 42 - } - } - } - - contract Test: IA { - struct X { - } - } - - fun main(): Int { - return Test.X().test() - } - `, - ParseCheckAndInterpretOptions{ - Config: &interpreter.Config{ - ContractValueHandler: makeContractValueHandler(nil, nil, nil), - }, - }, - ) - require.NoError(t, err) - - value, err := inter.Invoke("main") - require.NoError(t, err) - - assert.Equal(t, - interpreter.NewUnmeteredIntValueFromInt64(42), - value, - ) - }) - t.Run("interface variable", func(t *testing.T) { t.Parallel() @@ -232,52 +191,6 @@ func TestInterpretInterfaceDefaultImplementationWhenOverriden(t *testing.T) { ) }) - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - inter, err := parseCheckAndInterpretWithOptions(t, - ` - contract interface IA { - - struct X { - fun test(): Int { - return 41 - } - } - } - - contract Test: IA { - - struct X { - fun test(): Int { - return 42 - } - } - } - - fun main(): Int { - return Test.X().test() - } - `, - ParseCheckAndInterpretOptions{ - Config: &interpreter.Config{ - ContractValueHandler: makeContractValueHandler(nil, nil, nil), - }, - }, - ) - - require.NoError(t, err) - - value, err := inter.Invoke("main") - require.NoError(t, err) - - assert.Equal(t, - interpreter.NewUnmeteredIntValueFromInt64(42), - value, - ) - }) - } func TestInterpretInterfaceInheritance(t *testing.T) { @@ -461,64 +374,6 @@ func TestInterpretInterfaceInheritance(t *testing.T) { value, ) }) - - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - inter, err := parseCheckAndInterpretWithOptions(t, ` - contract interface A { - struct NestedA { - access(all) fun test(): Int { - return 3 - } - } - } - - contract interface B { - struct NestedB { - access(all) fun test(): String { - return "three" - } - } - } - - contract interface C: A, B {} - - contract D: C { - struct NestedA {} - - struct NestedB {} - - access(all) fun getNestedA(): NestedA { - return NestedA() - } - - access(all) fun getNestedB(): NestedB { - return NestedB() - } - } - - access(all) fun main(): Int { - return D.getNestedA().test() - }`, - - ParseCheckAndInterpretOptions{ - Config: &interpreter.Config{ - ContractValueHandler: makeContractValueHandler(nil, nil, nil), - }, - }, - ) - require.NoError(t, err) - - value, err := inter.Invoke("main") - require.NoError(t, err) - - assert.Equal(t, - interpreter.NewUnmeteredIntValueFromInt64(3), - value, - ) - }) } func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { @@ -916,6 +771,93 @@ func TestInterpretInterfaceFunctionConditionsInheritance(t *testing.T) { // The post-conditions of the interfaces are executed after that, with the reversed depth-first pre-order. assert.Equal(t, []string{"A", "D", "F", "E", "C", "B"}, logs) }) + + t.Run("nested resource interface unrelated", func(t *testing.T) { + + t.Parallel() + + logFunctionType := sema.NewSimpleFunctionType( + sema.FunctionPurityView, + []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "value", + TypeAnnotation: sema.AnyStructTypeAnnotation, + }, + }, + sema.VoidTypeAnnotation, + ) + + var logs []string + valueDeclaration := stdlib.NewStandardLibraryFunction( + "log", + logFunctionType, + "", + func(invocation interpreter.Invocation) interpreter.Value { + msg := invocation.Arguments[0].(*interpreter.StringValue).Str + logs = append(logs, msg) + return interpreter.Void + }, + ) + + baseValueActivation := sema.NewVariableActivation(sema.BaseValueActivation) + baseValueActivation.DeclareValue(valueDeclaration) + baseActivation := activations.NewActivation(nil, interpreter.BaseActivation) + interpreter.Declare(baseActivation, valueDeclaration) + + inter, err := parseCheckAndInterpretWithOptions(t, ` + contract interface A { + struct interface Nested { + access(all) fun test(): Int { + post { print("A") } + } + } + } + + contract interface B: A { + struct interface Nested { + access(all) fun test(): String { + post { print("B") } + } + } + } + + contract C { + struct Nested: B.Nested { + fun test(): String { + return "C" + } + } + } + + access(all) view fun print(_ msg: String): Bool { + log(msg) + return true + } + + access(all) fun main() { + let n = C.Nested() + n.test() + } + `, + ParseCheckAndInterpretOptions{ + CheckerConfig: &sema.Config{ + BaseValueActivation: baseValueActivation, + }, + Config: &interpreter.Config{ + BaseActivation: baseActivation, + ContractValueHandler: makeContractValueHandler(nil, nil, nil), + }, + }, + ) + require.NoError(t, err) + + _, err = inter.Invoke("main") + require.NoError(t, err) + + // A.Nested and B.Nested are two distinct separate functions + assert.Equal(t, []string{"B"}, logs) + }) } func TestRuntimeNestedInterfaceCast(t *testing.T) { diff --git a/runtime/tests/interpreter/interpreter_test.go b/runtime/tests/interpreter/interpreter_test.go index 2ced0a1e3e..74f693db60 100644 --- a/runtime/tests/interpreter/interpreter_test.go +++ b/runtime/tests/interpreter/interpreter_test.go @@ -40,7 +40,6 @@ import ( "github.com/onflow/cadence/runtime/sema" "github.com/onflow/cadence/runtime/stdlib" "github.com/onflow/cadence/runtime/tests/checker" - "github.com/onflow/cadence/runtime/tests/examples" . "github.com/onflow/cadence/runtime/tests/utils" ) @@ -8335,7 +8334,8 @@ func TestInterpretCompositeDeclarationNestedConstructor(t *testing.T) { ) } -func TestInterpretFungibleTokenContract(t *testing.T) { +// TODO: re-enable this test with the v2 fungible token contract +/* func TestInterpretFungibleTokenContract(t *testing.T) { t.Parallel() @@ -8404,7 +8404,7 @@ func TestInterpretFungibleTokenContract(t *testing.T) { ), value, ) -} +} */ func TestInterpretContractAccountFieldUse(t *testing.T) { @@ -10757,41 +10757,4 @@ func TestInterpretConditionsWrapperFunctionType(t *testing.T) { _, err := inter.Invoke("test") require.NoError(t, err) }) - - t.Run("type requirement", func(t *testing.T) { - - t.Parallel() - - inter, err := parseCheckAndInterpretWithOptions(t, - ` - contract interface CI { - struct S { - fun test(x: Int) { - pre { true } - } - } - } - - contract C: CI { - struct S { - fun test(x: Int) {} - } - } - - fun test(): fun (Int): Void { - let s = C.S() - return s.test - } - `, - ParseCheckAndInterpretOptions{ - Config: &interpreter.Config{ - ContractValueHandler: makeContractValueHandler(nil, nil, nil), - }, - }, - ) - require.NoError(t, err) - - _, err = inter.Invoke("test") - require.NoError(t, err) - }) } diff --git a/runtime/tests/interpreter/transfer_test.go b/runtime/tests/interpreter/transfer_test.go index 36702a8394..a7b4b5a4fd 100644 --- a/runtime/tests/interpreter/transfer_test.go +++ b/runtime/tests/interpreter/transfer_test.go @@ -100,10 +100,6 @@ func TestInterpretTransferCheck(t *testing.T) { ` contract interface CI { resource interface RI {} - - resource R: RI {} - - fun createR(): @R } contract C: CI { @@ -116,7 +112,7 @@ func TestInterpretTransferCheck(t *testing.T) { fun test() { let r <- C.createR() - let r2: @CI.R <- r as @CI.R + let r2: @C.R <- r as @C.R let r3: @{CI.RI} <- r2 destroy r3 } @@ -141,10 +137,6 @@ func TestInterpretTransferCheck(t *testing.T) { ` contract interface CI { resource interface RI {} - - resource R: RI {} - - fun createR(): @R } contract C: CI { @@ -157,7 +149,7 @@ func TestInterpretTransferCheck(t *testing.T) { fun test() { let r <- C.createR() - let ref: &CI.R = &r as &CI.R + let ref: &C.R = &r as &C.R let intersectionRef: &{CI.RI} = ref destroy r }