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

Allow default functions to co-exist with empty function declarations #2725

Merged
merged 4 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 0 additions & 127 deletions runtime/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,133 +485,6 @@ func TestRuntimeError(t *testing.T) {
})
}

func TestRuntimeDefaultFunctionConflictPrintingError(t *testing.T) {
t.Parallel()

runtime := NewTestInterpreterRuntime()

makeDeployTransaction := func(name, code string) []byte {
return []byte(fmt.Sprintf(
`
transaction {
prepare(signer: auth(BorrowValue) &Account) {
let acct = Account(payer: signer)
acct.contracts.add(name: "%s", code: "%s".decodeHex())
}
}
`,
name,
hex.EncodeToString([]byte(code)),
))
}

contractInterfaceCode := `
access(all) contract TestInterfaces {

access(all) resource interface A {
access(all) fun foo() {
let x = 3
}
}

access(all) resource interface B {
access(all) fun foo()
}
}
`

contractCode := `
import TestInterfaces from 0x2
access(all) contract TestContract {
access(all) resource R: TestInterfaces.A, TestInterfaces.B {}
// fill space
// fill space
// fill space
// fill space
// fill space
// fill space
// filling lots of space
// filling lots of space
// filling lots of space
}
`

accountCodes := map[Location][]byte{}
var events []cadence.Event

var nextAccount byte = 0x2

runtimeInterface := &TestRuntimeInterface{
OnGetCode: func(location Location) (bytes []byte, err error) {
return accountCodes[location], nil
},
Storage: NewTestLedger(nil, nil),
OnCreateAccount: func(payer Address) (address Address, err error) {
result := interpreter.NewUnmeteredAddressValueFromBytes([]byte{nextAccount})
nextAccount++
return result.ToAddress(), nil
},
OnGetSigningAccounts: func() ([]Address, error) {
return []Address{{0x1}}, nil
},
OnResolveLocation: NewSingleIdentifierLocationResolver(t),
OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) {
return accountCodes[location], nil
},
OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error {
accountCodes[location] = code
return nil
},
OnEmitEvent: func(event cadence.Event) error {
events = append(events, event)
return nil
},
}

nextTransactionLocation := NewTransactionLocationGenerator()

deployTransaction := makeDeployTransaction("TestInterfaces", contractInterfaceCode)
err := runtime.ExecuteTransaction(
Script{
Source: deployTransaction,
},
Context{
Interface: runtimeInterface,
Location: nextTransactionLocation(),
},
)
require.NoError(t, err)

deployTransaction = makeDeployTransaction("TestContract", contractCode)
err = runtime.ExecuteTransaction(
Script{
Source: deployTransaction,
},
Context{
Interface: runtimeInterface,
Location: nextTransactionLocation(),
},
)
require.Error(t, err)
require.Contains(t, err.Error(), "access(all) resource R: TestInterfaces.A, TestInterfaces.B {}")

var errType *sema.CheckerError
require.ErrorAs(t, err, &errType)

checkerErr := err.(Error).
Err.(interpreter.Error).
Err.(*stdlib.InvalidContractDeploymentError).
Err.(*ParsingCheckingError).
Err.(*sema.CheckerError)

var specificErrType *sema.DefaultFunctionConflictError
require.ErrorAs(t, checkerErr.Errors[0], &specificErrType)

errorRange := checkerErr.Errors[0].(*sema.DefaultFunctionConflictError).Range

require.Equal(t, errorRange.StartPos.Line, 4)
}

func TestRuntimeMultipleInterfaceDefaultImplementationsError(t *testing.T) {
t.Parallel()

Expand Down
27 changes: 0 additions & 27 deletions runtime/sema/check_composite_declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -1436,33 +1436,6 @@ func (checker *Checker) checkMemberConflicts(

return true
}

// At most one of them have could default impls.
// If one has a default impl, then the other MUST have a condition.
// FLIP: https://github.com/onflow/flips/pull/83

if newMember.HasImplementation {
if existingMember.HasConditions {
continue
}
} else if existingMember.HasImplementation {
if newMember.HasConditions {
continue
}
} else {
// None of them have default impls
continue
}

checker.report(
&DefaultFunctionConflictError{
CompositeKindedType: compositeType,
Member: newMember,
Range: errorRange,
},
)

return true
}

return false
Expand Down
34 changes: 22 additions & 12 deletions runtime/sema/check_interface_declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,7 @@ func (checker *Checker) checkInterfaceConformance(

var isDuplicate bool

// Check if the members coming from other conformances have conflicts.
// Check if the members coming from other conformances (siblings) have conflicts.
inheritedMembers, ok := inheritedMembersByName[name]
if ok {
for _, conflictingMember := range inheritedMembers {
Expand All @@ -609,6 +609,7 @@ func (checker *Checker) checkInterfaceConformance(
conflictingInterface,
conflictingMember,
interfaceDeclaration.Identifier,
false, // conflicting member is a sibling
)
}
}
Expand All @@ -622,6 +623,7 @@ func (checker *Checker) checkInterfaceConformance(
conformance,
conformanceMember,
declarationMember.Identifier,
true, // conflicting member is an inherited member
)
}

Expand All @@ -639,6 +641,7 @@ func (checker *Checker) checkDuplicateInterfaceMember(
conflictingInterfaceType *InterfaceType,
conflictingMember *Member,
hasPosition ast.HasPosition,
isConflictingMemberInherited bool,
) (isDuplicate bool) {

reportMemberConflictError := func() {
Expand Down Expand Up @@ -672,24 +675,31 @@ func (checker *Checker) checkDuplicateInterfaceMember(
// Having conflicting identical functions with:
// - (1) and (1) - OK
// - (1) and (2) - OK
// - (1) and (3) - Not OK
// - (1) and (3) - OK
// - (2) and (1) - OK
// - (2) and (2) - OK
// - (2) and (3) - OK
// - (3) and (1) - Not OK (order matters)
// - (3) and (2) - OK
// - (3) and (3) - Not OK

if interfaceMember.HasImplementation {
if conflictingMember.HasImplementation || !conflictingMember.HasConditions {
reportMemberConflictError()
return
}
if interfaceMember.HasImplementation && conflictingMember.HasImplementation {
reportMemberConflictError()
return
}

if conflictingMember.HasImplementation {
if !interfaceMember.HasConditions {
reportMemberConflictError()
return
}
// If the conflicting member is an inherited member, it is OK to override
// the inherited declaration, by a default implementation.
// However, a default implementation cannot be overridden by an empty declaration.

if isConflictingMemberInherited &&
conflictingMember.HasImplementation && !interfaceMember.HasConditions {
reportMemberConflictError()
return
}

// If the conflicting member is not an inherited one, (i.e.a member from a sibling conformance)
// then default implementation takes the precedence.

return
}
23 changes: 0 additions & 23 deletions runtime/sema/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -1645,29 +1645,6 @@ func (e *SpecialFunctionDefaultImplementationError) EndPosition(memoryGauge comm
return e.Identifier.EndPosition(memoryGauge)
}

// DefaultFunctionConflictError
type DefaultFunctionConflictError struct {
CompositeKindedType CompositeKindedType
Member *Member
ast.Range
}

var _ SemanticError = &DefaultFunctionConflictError{}
var _ errors.UserError = &DefaultFunctionConflictError{}

func (*DefaultFunctionConflictError) isSemanticError() {}

func (*DefaultFunctionConflictError) IsUserError() {}

func (e *DefaultFunctionConflictError) Error() string {
return fmt.Sprintf(
"%s `%s` has conflicting requirements for function `%s`",
e.CompositeKindedType.GetCompositeKind().Name(),
e.CompositeKindedType.QualifiedString(),
e.Member.Identifier.Identifier,
)
}

// InterfaceMemberConflictError
type InterfaceMemberConflictError struct {
InterfaceType *InterfaceType
Expand Down
50 changes: 25 additions & 25 deletions runtime/tests/checker/interface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1894,9 +1894,7 @@ func TestCheckMultipleInterfaceSingleInterfaceDefaultImplementation(t *testing.T
}
`)

errs := RequireCheckerErrors(t, err, 1)

require.IsType(t, &sema.DefaultFunctionConflictError{}, errs[0])
require.NoError(t, err)
})
}

Expand Down Expand Up @@ -3017,12 +3015,28 @@ func TestCheckInterfaceDefaultMethodsInheritance(t *testing.T) {
}
`)

errs := RequireCheckerErrors(t, err, 1)
require.NoError(t, err)
})

memberConflictError := &sema.InterfaceMemberConflictError{}
require.ErrorAs(t, errs[0], &memberConflictError)
assert.Equal(t, "hello", memberConflictError.MemberName)
assert.Equal(t, "A", memberConflictError.ConflictingInterfaceType.QualifiedIdentifier())
t.Run("default impl in child, declaration in parent, concrete type", func(t *testing.T) {

t.Parallel()

_, err := ParseAndCheck(t, `
struct interface A {
access(all) fun hello()
}

struct interface B: A {
access(all) fun hello() {
var a = 1
}
}

struct C: B {}
`)

require.NoError(t, err)
})

t.Run("default impl in both", func(t *testing.T) {
Expand Down Expand Up @@ -3073,7 +3087,7 @@ func TestCheckInterfaceDefaultMethodsInheritance(t *testing.T) {
}
`)

errs := RequireCheckerErrors(t, err, 3)
errs := RequireCheckerErrors(t, err, 2)

memberConflictError := &sema.InterfaceMemberConflictError{}
require.ErrorAs(t, errs[0], &memberConflictError)
Expand All @@ -3084,11 +3098,6 @@ func TestCheckInterfaceDefaultMethodsInheritance(t *testing.T) {
require.ErrorAs(t, errs[1], &memberConflictError)
assert.Equal(t, "C", memberConflictError.InterfaceType.QualifiedIdentifier())
assert.Equal(t, "hello", memberConflictError.MemberName)
assert.Equal(t, "B", memberConflictError.ConflictingInterfaceType.QualifiedIdentifier())

require.ErrorAs(t, errs[2], &memberConflictError)
assert.Equal(t, "C", memberConflictError.InterfaceType.QualifiedIdentifier())
assert.Equal(t, "hello", memberConflictError.MemberName)
assert.Equal(t, "A", memberConflictError.ConflictingInterfaceType.QualifiedIdentifier())
})

Expand Down Expand Up @@ -3360,13 +3369,7 @@ func TestCheckInterfaceDefaultMethodsInheritance(t *testing.T) {
struct interface D: A, B, C {}
`)

errs := RequireCheckerErrors(t, err, 1)

memberConflictError := &sema.InterfaceMemberConflictError{}
require.ErrorAs(t, errs[0], &memberConflictError)
assert.Equal(t, "hello", memberConflictError.MemberName)
assert.Equal(t, "A", memberConflictError.ConflictingInterfaceType.QualifiedIdentifier())
assert.Equal(t, "C", memberConflictError.InterfaceType.QualifiedIdentifier())
require.NoError(t, err)
})

t.Run("all three formats of function, concrete type", func(t *testing.T) {
Expand All @@ -3393,10 +3396,7 @@ func TestCheckInterfaceDefaultMethodsInheritance(t *testing.T) {
struct D: A, B, C {}
`)

errs := RequireCheckerErrors(t, err, 1)

defaultFunctionConflictError := &sema.DefaultFunctionConflictError{}
require.ErrorAs(t, errs[0], &defaultFunctionConflictError)
require.NoError(t, err)
})
}

Expand Down