Skip to content

[5.5] [IDE] Fix superclass constraint handling for extension merging #37308

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

Merged
merged 1 commit into from
May 7, 2021
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
27 changes: 23 additions & 4 deletions lib/IDE/IDETypeChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ struct SynthesizedExtensionAnalyzer::Implementation {
continue;

if (!BaseType->isExistentialType()) {
// Apply any substitutions we need to map the requirements from a
// a protocol extension to an extension on the conforming type.
First = First.subst(subMap);
Second = Second.subst(subMap);

Expand All @@ -317,11 +319,12 @@ struct SynthesizedExtensionAnalyzer::Implementation {
}
}

assert(!First->hasArchetype() && !Second->hasArchetype());
switch (Kind) {
case RequirementKind::Conformance: {
auto *M = DC->getParentModule();
auto *Proto = Second->castTo<ProtocolType>()->getDecl();
if (!First->isTypeParameter() && !First->is<ArchetypeType>() &&
if (!First->isTypeParameter() &&
M->conformsToProtocol(First, Proto).isInvalid())
return true;
if (M->conformsToProtocol(First, Proto).isInvalid())
Expand All @@ -330,11 +333,27 @@ struct SynthesizedExtensionAnalyzer::Implementation {
}

case RequirementKind::Superclass:
if (!Second->isBindableToSuperclassOf(First)) {
return true;
} else if (!Second->isExactSuperclassOf(Second)) {
// If the subject type of the requirement is still a type parameter,
// we need to check if the contextual type could possibly be bound to
// the superclass. If not, this extension isn't applicable.
if (First->isTypeParameter()) {
if (!Target->mapTypeIntoContext(First)->isBindableTo(
Target->mapTypeIntoContext(Second))) {
return true;
}
MergeInfo.addRequirement(GenericSig, First, Second, Kind);
break;
}

// If we've substituted in a concrete type for the subject, we can
// check for an exact superclass match, and disregard the extension if
// it missed.
// FIXME: What if it ends being something like `C<U> : C<Int>`?
// Arguably we should allow that to be mirrored with a U == Int
// constraint.
if (!Second->isExactSuperclassOf(First))
return true;

break;

case RequirementKind::SameType:
Expand Down
59 changes: 59 additions & 0 deletions test/IDE/print_synthesized_extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
// RUN: %FileCheck %s -check-prefix=CHECK12 < %t.syn.txt
// RUN: %FileCheck %s -check-prefix=CHECK13 < %t.syn.txt
// RUN: %FileCheck %s -check-prefix=CHECK14 < %t.syn.txt
// RUN: %FileCheck %s -check-prefix=CHECK15 < %t.syn.txt
// RUN: %FileCheck %s -check-prefix=CHECK16 < %t.syn.txt

public protocol P1 {
associatedtype T1
Expand Down Expand Up @@ -339,3 +341,60 @@ extension S13 : P5 {
// CHECK14-NEXT: <decl:Func>/// This should not crash
// CHECK14-NEXT: public func <loc>foo5()</loc></decl>
// CHECK14-NEXT: }</synthesized>

// rdar://76868074: Make sure we print the extensions for C.
public class C<T> {}
public class D {}
public class E {}

extension C where T : D {
public func foo() {}
}

public protocol P8 {
associatedtype T
}

extension P8 where T : D {
public func bar() {}
}

extension P8 where T : E {
public func baz() {}
}

extension C : P8 {}

// CHECK15: <decl:Class>public class <loc>C<<decl:GenericTypeParam>T</decl>></loc> {
// CHECK15-NEXT: }</decl>

// CHECK15: <decl:Extension>extension <loc><ref:Class>C</ref></loc> : <ref:module>print_synthesized_extensions</ref>.<ref:Protocol>P8</ref> {
// CHECK15-NEXT: }</decl>

// CHECK15: <decl:Extension>extension <loc><ref:Class>C</ref></loc> where <ref:GenericTypeParam>T</ref> : <ref:module>print_synthesized_extensions</ref>.<ref:Class>D</ref> {
// CHECK15-NEXT: <decl:Func>public func <loc>foo()</loc></decl></decl>
// CHECK15-NEXT: <decl:Func>public func <loc>bar()</loc></decl>
// CHECK15-NEXT: }</synthesized>

// CHECK15: <synthesized>extension <ref:Class>C</ref> where <ref:GenericTypeParam>T</ref> : <ref:Class>E</ref> {
// CHECK15-NEXT: <decl:Func>public func <loc>baz()</loc></decl>
// CHECK15-NEXT: }</synthesized>

// CHECK15: <decl:Extension>extension <loc><ref:Protocol>P8</ref></loc> where <ref:GenericTypeParam>Self</ref>.<ref:AssociatedType>T</ref> : <ref:module>print_synthesized_extensions</ref>.<ref:Class>D</ref> {
// CHECK15-NEXT: <decl:Func>public func <loc>bar()</loc></decl>
// CHECK15-NEXT: }</decl>

// CHECK15: <decl:Extension>extension <loc><ref:Protocol>P8</ref></loc> where <ref:GenericTypeParam>Self</ref>.<ref:AssociatedType>T</ref> : <ref:module>print_synthesized_extensions</ref>.<ref:Class>E</ref> {
// CHECK15-NEXT: <decl:Func>public func <loc>baz()</loc></decl>
// CHECK15-NEXT: }</decl>

// For this class, make sure we mirror the extension P8 where T : D, but not
// the extension P8 where T : E.
public class F<T : D> {}
extension F : P8 {}

// CHECK16: <synthesized>extension <ref:Class>F</ref> where <ref:GenericTypeParam>T</ref> : <ref:Class>D</ref> {
// CHECK16-NEXT: <decl:Func>public func <loc>bar()</loc></decl>
// CHECK16-NEXT: }</synthesized>

// CHECK16-NOT: <synthesized>extension <ref:Class>F</ref> where <ref:GenericTypeParam>T</ref> : <ref:Class>E</ref> {
87 changes: 87 additions & 0 deletions test/IDE/print_synthesized_extensions_generics.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module-path %t/print_synthesized_extensions_generics.swiftmodule -emit-module-doc -emit-module-doc-path %t/print_synthesized_extensions_generics.swiftdoc %s
// RUN: %target-swift-ide-test -print-module -synthesize-extension -print-interface -no-empty-line-between-members -module-to-print=print_synthesized_extensions_generics -I %t -source-filename=%s | %FileCheck %s

public protocol P1 {
associatedtype T
associatedtype U
}

public class A {}
public class B<T> : A {}

extension P1 where T : B<U> {
public func qux() {}
}
extension P1 where T : B<Int> {
public func flob() {}
}

public class C<T : A, U> {}
extension C : P1 {}
// CHECK: extension C where T : B<U> {
// CHECK-NEXT: func qux()
// CHECK-NEXT: }

// CHECK: extension C where T : B<Int> {
// CHECK-NEXT: func flob()
// CHECK-NEXT: }

public class D<U> {}
extension D : P1 {
public typealias T = B<U>
}
// CHECK: class D<U> {
// CHECK-NEXT: func qux()
// CHECK-NEXT: }

// FIXME: Arguably we should support this
// CHECK-NOT: extension D where U == Int

public class E {}
extension E: P1 {
public typealias T = B<U>
public typealias U = Int
}
// CHECK: class E {
// CHECK-NEXT: func qux()
// CHECK-NEXT: func flob()
// CHECK-NEXT: }

public class F {}
extension F : P1 {
public typealias T = B<U>
public typealias U = String
}
// CHECK: class F {
// CHECK-NEXT: func qux()
// CHECK-NEXT: }

// CHECK-NOT: extension F where T : B<Int>

public protocol P2 {
associatedtype T : P1
}

extension P2 where T.T : A {
public func blah() {}
}

public class G<T : P1> {}
extension G : P2 {}

// CHECK: extension G where T.T : A {
// CHECK-NEXT: func blah()
// CHECK-NEXT: }

// CHECK: extension P1 where Self.T : print_synthesized_extensions_generics.B<Self.U> {
// CHECK-NEXT: func qux()
// CHECK-NEXT: }

// CHECK: extension P1 where Self.T : print_synthesized_extensions_generics.B<Int> {
// CHECK-NEXT: func flob()
// CHECK-NEXT: }

// CHECK: extension P2 where Self.T.T : print_synthesized_extensions_generics.A {
// CHECK-NEXT: func blah()
// CHECK-NEXT: }
25 changes: 25 additions & 0 deletions test/SourceKit/DocSupport/Inputs/module_with_class_extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

public class C<T> {}
public class D {}
public class E {}

extension C where T : D {
public func foo() {}
}

public protocol P8 {
associatedtype T
}

extension P8 where T : D {
public func bar() {}
}

extension P8 where T : E {
public func baz() {}
}

extension C : P8 {}

public class F<T : D> {}
extension F : P8 {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// RUN: %empty-directory(%t.mod)
// RUN: %swift -emit-module -o %t.mod/module_with_class_extension.swiftmodule %S/Inputs/module_with_class_extension.swift -parse-as-library
// RUN: %sourcekitd-test -req=doc-info -module module_with_class_extension -- -Xfrontend -disable-implicit-concurrency-module-import -I %t.mod > %t.response
// RUN: %diff -u %s.response %t.response

// rdar://76868074: Make sure we print the extensions for C.
Loading