Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove type requirements #2670

Merged
merged 13 commits into from
Aug 16, 2023
5 changes: 0 additions & 5 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
10 changes: 10 additions & 0 deletions runtime/common/declarationkind.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
15 changes: 0 additions & 15 deletions runtime/contract_update_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1750,21 +1750,6 @@ func TestRuntimeContractUpdateValidation(t *testing.T) {
assertContractRemovalError(t, err, "Test")
})

t.Run("Remove contract interface with enum", func(t *testing.T) {
turbolent marked this conversation as resolved.
Show resolved Hide resolved

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()
Expand Down
32 changes: 16 additions & 16 deletions runtime/ft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
"github.com/onflow/cadence/runtime/tests/utils"
)

const realFungibleTokenContractInterface = `
const modifiedFungibleTokenContractInterface = `
/// FungibleToken
///
/// The interface that fungible token contracts implement.
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -184,15 +184,15 @@ 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"
}
}
}
`

const realFlowContract = `
const modifiedFlowContract = `
import FungibleToken from 0x1

access(all) contract FlowToken: FungibleToken {
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {

Expand Down Expand Up @@ -548,7 +548,7 @@ func BenchmarkRuntimeFungibleTokenTransfer(b *testing.B) {
Script{
Source: utils.DeploymentTransaction(
"FungibleToken",
[]byte(realFungibleTokenContractInterface),
[]byte(modifiedFungibleTokenContractInterface),
),
},
Context{
Expand All @@ -572,7 +572,7 @@ func BenchmarkRuntimeFungibleTokenTransfer(b *testing.B) {
}
}
`,
hex.EncodeToString([]byte(realFlowContract)),
hex.EncodeToString([]byte(modifiedFlowContract)),
)),
},
Context{
Expand Down
70 changes: 6 additions & 64 deletions runtime/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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())
}
}
})()
Expand All @@ -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,
Expand Down
5 changes: 2 additions & 3 deletions runtime/interpreter/sharedstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading