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

Fix positioning of default function conflict errors #2665

Merged
merged 3 commits into from
Jul 19, 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
259 changes: 259 additions & 0 deletions runtime/error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ import (

"github.com/stretchr/testify/require"

"github.com/onflow/cadence"
"github.com/onflow/cadence/runtime/ast"
"github.com/onflow/cadence/runtime/common"
"github.com/onflow/cadence/runtime/interpreter"
"github.com/onflow/cadence/runtime/sema"
"github.com/onflow/cadence/runtime/stdlib"
)

func TestRuntimeError(t *testing.T) {
Expand Down Expand Up @@ -493,3 +496,259 @@ 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: AuthAccount) {
let acct = AuthAccount(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{
getCode: func(location Location) (bytes []byte, err error) {
return accountCodes[location], nil
},
storage: newTestLedger(nil, nil),
createAccount: func(payer Address) (address Address, err error) {
result := interpreter.NewUnmeteredAddressValueFromBytes([]byte{nextAccount})
nextAccount++
return result.ToAddress(), nil
},
getSigningAccounts: func() ([]Address, error) {
return []Address{{0x1}}, nil
},
resolveLocation: singleIdentifierLocationResolver(t),
getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) {
return accountCodes[location], nil
},
updateAccountContractCode: func(location common.AddressLocation, code []byte) error {
accountCodes[location] = code
return nil
},
emitEvent: 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()

runtime := newTestInterpreterRuntime()

makeDeployTransaction := func(name, code string) []byte {
return []byte(fmt.Sprintf(
`
transaction {
prepare(signer: AuthAccount) {
let acct = AuthAccount(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() {
let x = 4
}
}
}
`

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{
getCode: func(location Location) (bytes []byte, err error) {
return accountCodes[location], nil
},
storage: newTestLedger(nil, nil),
createAccount: func(payer Address) (address Address, err error) {
result := interpreter.NewUnmeteredAddressValueFromBytes([]byte{nextAccount})
nextAccount++
return result.ToAddress(), nil
},
getSigningAccounts: func() ([]Address, error) {
return []Address{{0x1}}, nil
},
resolveLocation: singleIdentifierLocationResolver(t),
getAccountContractCode: func(location common.AddressLocation) (code []byte, err error) {
return accountCodes[location], nil
},
updateAccountContractCode: func(location common.AddressLocation, code []byte) error {
accountCodes[location] = code
return nil
},
emitEvent: 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 {}")
dsainati1 marked this conversation as resolved.
Show resolved Hide resolved

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.MultipleInterfaceDefaultImplementationsError
require.ErrorAs(t, checkerErr.Errors[0], &specificErrType)

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

require.Equal(t, errorRange.StartPos.Line, 4)
}
7 changes: 7 additions & 0 deletions runtime/sema/check_composite_declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -738,18 +738,22 @@ func (checker *Checker) declareCompositeLikeMembersAndValue(
}

if _, ok := inheritedMembers.Get(memberName); ok {
errorRange := ast.NewRangeFromPositioned(checker.memoryGauge, declaration.DeclarationIdentifier())

if member.HasImplementation {
checker.report(
&MultipleInterfaceDefaultImplementationsError{
CompositeType: nestedCompositeType,
Member: member,
Range: errorRange,
},
)
} else {
checker.report(
&DefaultFunctionConflictError{
CompositeType: nestedCompositeType,
Member: member,
Range: errorRange,
},
)
}
Expand Down Expand Up @@ -1243,18 +1247,21 @@ func (checker *Checker) checkCompositeLikeConformance(
if interfaceMember.DeclarationKind == common.DeclarationKindFunction {

if _, ok := inheritedMembers[name]; ok {
errorRange := ast.NewRangeFromPositioned(checker.memoryGauge, compositeDeclaration.DeclarationIdentifier())
if interfaceMember.HasImplementation {
checker.report(
&MultipleInterfaceDefaultImplementationsError{
CompositeType: compositeType,
Member: interfaceMember,
Range: errorRange,
},
)
} else {
checker.report(
&DefaultFunctionConflictError{
CompositeType: compositeType,
Member: interfaceMember,
Range: errorRange,
},
)
}
Expand Down
20 changes: 3 additions & 17 deletions runtime/sema/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -1563,6 +1563,7 @@ func (e *DuplicateConformanceError) Error() string {
type MultipleInterfaceDefaultImplementationsError struct {
CompositeType *CompositeType
Member *Member
ast.Range
}

var _ SemanticError = &MultipleInterfaceDefaultImplementationsError{}
Expand All @@ -1581,14 +1582,6 @@ func (e *MultipleInterfaceDefaultImplementationsError) Error() string {
)
}

func (e *MultipleInterfaceDefaultImplementationsError) StartPosition() ast.Position {
return e.Member.Identifier.StartPosition()
}

func (e *MultipleInterfaceDefaultImplementationsError) EndPosition(memoryGauge common.MemoryGauge) ast.Position {
return e.Member.Identifier.EndPosition(memoryGauge)
}

// SpecialFunctionDefaultImplementationError
type SpecialFunctionDefaultImplementationError struct {
Container ast.Declaration
Expand Down Expand Up @@ -1624,6 +1617,7 @@ func (e *SpecialFunctionDefaultImplementationError) EndPosition(memoryGauge comm
type DefaultFunctionConflictError struct {
CompositeType *CompositeType
Member *Member
ast.Range
}

var _ SemanticError = &DefaultFunctionConflictError{}
Expand All @@ -1635,21 +1629,13 @@ func (*DefaultFunctionConflictError) IsUserError() {}

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

func (e *DefaultFunctionConflictError) StartPosition() ast.Position {
return e.Member.Identifier.StartPosition()
}

func (e *DefaultFunctionConflictError) EndPosition(memoryGauge common.MemoryGauge) ast.Position {
return e.Member.Identifier.EndPosition(memoryGauge)
}

// MissingConformanceError
type MissingConformanceError struct {
CompositeType *CompositeType
Expand Down
Loading