Skip to content

Commit

Permalink
Merge pull request #1076 from bluesign/master
Browse files Browse the repository at this point in the history
Interface default implementation support
  • Loading branch information
turbolent authored Aug 17, 2022
2 parents 674324f + 5653108 commit 1b072da
Show file tree
Hide file tree
Showing 12 changed files with 1,395 additions and 136 deletions.
43 changes: 42 additions & 1 deletion docs/language/interfaces.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ resource interface OuterInterface {
struct interface InnerInterface {}
}
// Declare a resource named `SomeOuter` that implements the interface `OuterInterface`
// Declare a resource named `SomeOuter` that implements the interface `OuterInterface`.
//
// The resource is not required to implement `OuterInterface.InnerInterface`.
//
Expand All @@ -467,3 +467,44 @@ resource SomeOuter: OuterInterface {}
struct SomeInner: OuterInterface.InnerInterface {}
```

## Interface Default Functions

Interfaces can provide default functions:
If the concrete type implementing the interface does not provide an implementation
for the function required by the interface,
then the interface's default function is used in the implementation.

```cadence
// Declare a struct interface `Container`,
// which declares a default function `getCount`.
//
struct interface Container {
let items: [AnyStruct]
fun getCount(): Int {
return self.items.length
}
}
// Declare a concrete struct named `Numbers` that implements the interface `Container`.
//
// The struct does not implement the function `getCount` of the interface `Container`,
// so the default function for `getCount` is used.
//
struct Numbers: Container {
let items: [AnyStruct]
init() {
self.items = []
}
}
let numbers = Numbers()
numbers.getCount() // is 0
```

Interfaces cannot provide default initializers or default destructors.

Only one conformance may provide a default function.
4 changes: 4 additions & 0 deletions runtime/ast/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ func (b *FunctionBlock) String() string {
return Prettier(b)
}

func (b *FunctionBlock) HasStatements() bool {
return b != nil && len(b.Block.Statements) > 0
}

// Condition

type Condition struct {
Expand Down
51 changes: 50 additions & 1 deletion runtime/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ type WrapperCode struct {
InitializerFunctionWrapper FunctionWrapper
DestructorFunctionWrapper FunctionWrapper
FunctionWrappers map[string]FunctionWrapper
Functions map[string]FunctionValue
}

// TypeCodes is the value which stores the "prepared" / "callable" "code"
Expand Down Expand Up @@ -1667,6 +1668,22 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue(
destructorFunction = destructorFunctionWrapper(destructorFunction)
}

// Apply default functions, if conforming type does not provide the function

// Iterating over the map in a non-deterministic way is OK,
// we only apply the function wrapper to each function,
// the order does not matter.

for name, function := range code.Functions { //nolint:maprangecheck
if functions[name] != nil {
continue
}
if functions == nil {
functions = map[string]FunctionValue{}
}
functions[name] = function
}

// Wrap functions

// Iterating over the map in a non-deterministic way is OK,
Expand Down Expand Up @@ -1695,7 +1712,6 @@ func (interpreter *Interpreter) declareNonEnumCompositeValue(

for i := len(typeRequirements) - 1; i >= 0; i-- {
typeRequirement := typeRequirements[i]

wrapFunctions(interpreter.typeCodes.TypeRequirementCodes[typeRequirement.ID()])
}

Expand Down Expand Up @@ -2049,6 +2065,35 @@ func (interpreter *Interpreter) compositeDestructorFunction(
)
}

func (interpreter *Interpreter) defaultFunctions(
members *ast.Members,
lexicalScope *VariableActivation,
) map[string]FunctionValue {

functionDeclarations := members.Functions()
functionCount := len(functionDeclarations)

if functionCount == 0 {
return nil
}

functions := make(map[string]FunctionValue, functionCount)

for _, functionDeclaration := range functionDeclarations {
name := functionDeclaration.Identifier.Identifier
if !functionDeclaration.FunctionBlock.HasStatements() {
continue
}

functions[name] = interpreter.compositeFunction(
functionDeclaration,
lexicalScope,
)
}

return functions
}

func (interpreter *Interpreter) compositeFunctions(
compositeDeclaration *ast.CompositeDeclaration,
lexicalScope *VariableActivation,
Expand Down Expand Up @@ -2400,11 +2445,13 @@ func (interpreter *Interpreter) declareInterface(
initializerFunctionWrapper := interpreter.initializerFunctionWrapper(declaration.Members, lexicalScope)
destructorFunctionWrapper := interpreter.destructorFunctionWrapper(declaration.Members, lexicalScope)
functionWrappers := interpreter.functionWrappers(declaration.Members, lexicalScope)
defaultFunctions := interpreter.defaultFunctions(declaration.Members, lexicalScope)

interpreter.typeCodes.InterfaceCodes[typeID] = WrapperCode{
InitializerFunctionWrapper: initializerFunctionWrapper,
DestructorFunctionWrapper: destructorFunctionWrapper,
FunctionWrappers: functionWrappers,
Functions: defaultFunctions,
}
}

Expand Down Expand Up @@ -2434,11 +2481,13 @@ func (interpreter *Interpreter) declareTypeRequirement(
initializerFunctionWrapper := interpreter.initializerFunctionWrapper(declaration.Members, lexicalScope)
destructorFunctionWrapper := interpreter.destructorFunctionWrapper(declaration.Members, lexicalScope)
functionWrappers := interpreter.functionWrappers(declaration.Members, lexicalScope)
defaultFunctions := interpreter.defaultFunctions(declaration.Members, lexicalScope)

interpreter.typeCodes.TypeRequirementCodes[typeID] = WrapperCode{
InitializerFunctionWrapper: initializerFunctionWrapper,
DestructorFunctionWrapper: destructorFunctionWrapper,
FunctionWrappers: functionWrappers,
Functions: defaultFunctions,
}
}

Expand Down
2 changes: 1 addition & 1 deletion runtime/sema/check_assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ func (checker *Checker) visitMemberExpressionAssignment(
)
}

targetIsConstant := member.VariableKind == ast.VariableKindConstant
targetIsConstant := member.VariableKind != ast.VariableKindVariable

// If this is an assignment to a `self` field, it needs special handling
// depending on if the assignment is in an initializer or not
Expand Down
Loading

0 comments on commit 1b072da

Please sign in to comment.