From b564d5c6023af2d2b6c537d074965739c15c7df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 9 Feb 2024 17:26:57 -0800 Subject: [PATCH 1/3] fix order of declarting nested types' explicit interface conformances setting the explicit interface conformances must occur before checking members --- runtime/sema/check_composite_declaration.go | 18 ++++++++++----- runtime/sema/check_interface_declaration.go | 25 ++++++++++++--------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/runtime/sema/check_composite_declaration.go b/runtime/sema/check_composite_declaration.go index b85f2e2aa3..11da300672 100644 --- a/runtime/sema/check_composite_declaration.go +++ b/runtime/sema/check_composite_declaration.go @@ -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 @@ -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) } diff --git a/runtime/sema/check_interface_declaration.go b/runtime/sema/check_interface_declaration.go index 241c3d92ae..70a1bff111 100644 --- a/runtime/sema/check_interface_declaration.go +++ b/runtime/sema/check_interface_declaration.go @@ -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 @@ -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(), @@ -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() { From c3041a40847c462e276d8749f6a28b861cbdbacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 9 Feb 2024 17:27:20 -0800 Subject: [PATCH 2/3] consider inherited interfaces when checking for member clashes in intersection types --- runtime/sema/checker.go | 57 ++++++++------ runtime/tests/checker/intersection_test.go | 92 ++++++++++++++++++++++ 2 files changed, 123 insertions(+), 26 deletions(-) diff --git a/runtime/sema/checker.go b/runtime/sema/checker.go index a049b545cf..6779e8437a 100644 --- a/runtime/sema/checker.go +++ b/runtime/sema/checker.go @@ -937,40 +937,45 @@ func CheckIntersectionType( // The intersections may not have clashing members - // TODO: also include interface conformances' members - // once interfaces can have conformances + checkClashingMember := func(interfaceType *InterfaceType) { + 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()) + } - 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` diff --git a/runtime/tests/checker/intersection_test.go b/runtime/tests/checker/intersection_test.go index d3395b740c..aaf602fd35 100644 --- a/runtime/tests/checker/intersection_test.go +++ b/runtime/tests/checker/intersection_test.go @@ -19,6 +19,7 @@ package checker import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -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() From cc6258f5267be7344341b791ca60d9bd6ee00554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Mon, 12 Feb 2024 14:31:36 -0800 Subject: [PATCH 3/3] test non-clashing member in common inherited interface --- runtime/tests/checker/intersection_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/runtime/tests/checker/intersection_test.go b/runtime/tests/checker/intersection_test.go index aaf602fd35..da17ec1706 100644 --- a/runtime/tests/checker/intersection_test.go +++ b/runtime/tests/checker/intersection_test.go @@ -534,6 +534,25 @@ func TestCheckIntersectionTypeWithInheritanceMemberClash(t *testing.T) { } } +func TestCheckIntersectionTypeWithInheritedMember(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + struct interface A { + let n: Int + } + + struct interface B: A {} + + struct interface C: A {} + + fun test(_ v: {B, C}) {} + `) + + require.NoError(t, err) +} + func TestCheckIntersectionTypeSubtyping(t *testing.T) { t.Parallel()