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

Consider inherited interfaces when checking for member clashes in intersection types #3093

Merged
Show file tree
Hide file tree
Changes from 2 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
18 changes: 12 additions & 6 deletions runtime/sema/check_composite_declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,8 +762,19 @@ func (checker *Checker) declareCompositeLikeMembersAndValue(
checker.enterValueScope()
defer checker.leaveValueScope(declaration.EndPosition, false)

// Declare nested types

checker.declareCompositeLikeNestedTypes(declaration, false)

// Declare nested types' explicit conformances

for _, nestedInterfaceDeclaration := range members.Interfaces() {
// resolve conformances
nestedInterfaceType := checker.Elaboration.InterfaceDeclarationType(nestedInterfaceDeclaration)
nestedInterfaceType.ExplicitInterfaceConformances =
checker.explicitInterfaceConformances(nestedInterfaceDeclaration, nestedInterfaceType)
}

// 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

Expand Down Expand Up @@ -828,12 +839,7 @@ func (checker *Checker) declareCompositeLikeMembersAndValue(
},
)
}
for _, nestedInterfaceDeclaration := range members.Interfaces() {
// resolve conformances
nestedInterfaceType := checker.Elaboration.InterfaceDeclarationType(nestedInterfaceDeclaration)
nestedInterfaceType.ExplicitInterfaceConformances =
checker.explicitInterfaceConformances(nestedInterfaceDeclaration, nestedInterfaceType)
}

for _, nestedCompositeDeclaration := range nestedComposites {
declareNestedComposite(nestedCompositeDeclaration)
}
Expand Down
25 changes: 15 additions & 10 deletions runtime/sema/check_interface_declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,9 @@ func (checker *Checker) declareInterfaceMembersAndValue(declaration *ast.Interfa

compositeKind := declaration.Kind()

eventMembers := orderedmap.New[StringMemberOrderedMap](len(declaration.Members.Composites()))
declarationMembers := declaration.Members

eventMembers := orderedmap.New[StringMemberOrderedMap](len(declarationMembers.Composites()))

(func() { // Activate new scope for nested declarations

Expand All @@ -412,10 +414,18 @@ func (checker *Checker) declareInterfaceMembersAndValue(declaration *ast.Interfa

checker.declareInterfaceNestedTypes(declaration)

// Declare nested types' explicit conformances
for _, nestedInterfaceDeclaration := range declarationMembers.Interfaces() {
// resolve conformances
nestedInterfaceType := checker.Elaboration.InterfaceDeclarationType(nestedInterfaceDeclaration)
nestedInterfaceType.ExplicitInterfaceConformances =
checker.explicitInterfaceConformances(nestedInterfaceDeclaration, nestedInterfaceType)
}

// Declare members

members, fields, origins := checker.defaultMembersAndOrigins(
declaration.Members,
declarationMembers,
interfaceType,
ContainerKindInterface,
declaration.DeclarationKind(),
Expand All @@ -434,21 +444,16 @@ func (checker *Checker) declareInterfaceMembersAndValue(declaration *ast.Interfa
// 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

initializers := declaration.Members.Initializers()
initializers := declarationMembers.Initializers()
interfaceType.InitializerParameters = checker.initializerParameters(initializers)
interfaceType.InitializerPurity = checker.initializerPurity(compositeKind, initializers)

// Declare nested declarations' members
for _, nestedInterfaceDeclaration := range declaration.Members.Interfaces() {
// resolve conformances
nestedInterfaceType := checker.Elaboration.InterfaceDeclarationType(nestedInterfaceDeclaration)
nestedInterfaceType.ExplicitInterfaceConformances =
checker.explicitInterfaceConformances(nestedInterfaceDeclaration, nestedInterfaceType)

for _, nestedInterfaceDeclaration := range declarationMembers.Interfaces() {
checker.declareInterfaceMembersAndValue(nestedInterfaceDeclaration)
}

for _, nestedCompositeDeclaration := range declaration.Members.Composites() {
for _, nestedCompositeDeclaration := range declarationMembers.Composites() {
if nestedCompositeDeclaration.Kind() == common.CompositeKindEvent {
if nestedCompositeDeclaration.IsResourceDestructionDefaultEvent() {

Expand Down
57 changes: 31 additions & 26 deletions runtime/sema/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -937,40 +937,45 @@

// The intersections may not have clashing members

// TODO: also include interface conformances' members
// once interfaces can have conformances
checkClashingMember := func(interfaceType *InterfaceType) {
turbolent marked this conversation as resolved.
Show resolved Hide resolved
interfaceType.Members.Foreach(func(name string, member *Member) {

interfaceType.Members.Foreach(func(name string, member *Member) {
if previousDeclaringInterfaceType, ok := memberSet[name]; ok {
if previousDeclaringInterfaceType, ok := memberSet[name]; ok {

// If there is an overlap in members, ensure the members have the same type
// If there is an overlap in members, ensure the members have the same type

memberType := member.TypeAnnotation.Type
memberType := member.TypeAnnotation.Type

prevMemberType, ok := previousDeclaringInterfaceType.Members.Get(name)
if !ok {
panic(errors.NewUnreachableError())
}
prevMemberType, ok := previousDeclaringInterfaceType.Members.Get(name)
if !ok {
panic(errors.NewUnreachableError())

Check warning on line 951 in runtime/sema/checker.go

View check run for this annotation

Codecov / codecov/patch

runtime/sema/checker.go#L951

Added line #L951 was not covered by tests
}

previousMemberType := prevMemberType.TypeAnnotation.Type
previousMemberType := prevMemberType.TypeAnnotation.Type

if !memberType.IsInvalidType() &&
!previousMemberType.IsInvalidType() &&
!memberType.Equal(previousMemberType) {
if !memberType.IsInvalidType() &&
!previousMemberType.IsInvalidType() &&
!memberType.Equal(previousMemberType) {

report(func(t *ast.IntersectionType) error {
return &IntersectionMemberClashError{
Name: name,
RedeclaringType: interfaceType,
OriginalDeclaringType: previousDeclaringInterfaceType,
Range: ast.NewRangeFromPositioned(memoryGauge, t.Types[i]),
}
})
report(func(t *ast.IntersectionType) error {
return &IntersectionMemberClashError{
Name: name,
RedeclaringType: interfaceType,
OriginalDeclaringType: previousDeclaringInterfaceType,
Range: ast.NewRangeFromPositioned(memoryGauge, t.Types[i]),
}
})
}
} else {
memberSet[name] = interfaceType
}
} else {
memberSet[name] = interfaceType
}
})
})
}

checkClashingMember(interfaceType)

interfaceType.EffectiveInterfaceConformanceSet().
ForEach(checkClashingMember)
}

// If no intersection type is given, infer `AnyResource`/`AnyStruct`
Expand Down
92 changes: 92 additions & 0 deletions runtime/tests/checker/intersection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package checker

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -442,6 +443,97 @@ func TestCheckIntersectionTypeMemberAccess(t *testing.T) {
})
}

func TestCheckIntersectionTypeWithInheritanceMemberClash(t *testing.T) {

t.Parallel()

const firstMember = `let n: Int`
const secondMember = `let n: Bool`

test := func(
memberInA, memberInC bool,
firstType, secondType string,
) {

testName := fmt.Sprintf(
"memberInA: %v, memberInC: %v, firstType: %s, secondType: %s",
memberInA, memberInC, firstType, secondType,
)

t.Run(testName, func(t *testing.T) {
t.Parallel()

bodyA := ""
bodyB := ""
bodyC := ""
bodyD := ""

if memberInA {
bodyA = firstMember
} else {
bodyB = firstMember
}

if memberInC {
bodyC = secondMember
} else {
bodyD = secondMember
}

_, err := ParseAndCheck(t,
fmt.Sprintf(
`
struct interface A {
%s
}

struct interface B: A {
%s
}

struct interface C {
%s
}

struct interface D: C {
%s
}

fun test(_ v: {%s, %s}) {}
`,
bodyA,
bodyB,
bodyC,
bodyD,
firstType,
secondType,
),
)

errs := RequireCheckerErrors(t, err, 1)

assert.IsType(t, &sema.IntersectionMemberClashError{}, errs[0])
})
}

for _, memberInA := range []bool{true, false} {
for _, memberInC := range []bool{true, false} {
for _, firstType := range []string{"A", "B"} {
for _, secondType := range []string{"C", "D"} {

if (firstType == "A" && !memberInA) ||
(secondType == "C" && !memberInC) {

continue
}

test(memberInA, memberInC, firstType, secondType)
}
}
}
}
}

func TestCheckIntersectionTypeSubtyping(t *testing.T) {

t.Parallel()
Expand Down
Loading