Skip to content

Commit c138e4c

Browse files
committed
[Sema] Swift3 compatibility: Fix ambiguous protocol composition production
Fixes: https://bugs.swift.org/browse/SR-2843 'P1 & P2.Type' is mistakingly accepted and parsed as (meatatype (composition P1, P2)) in swift3. Now, we parse it as (composition P1, (metatype P2)) For source compatibility, reconstruct it as Swift3. Also, this solves inconsistent behavior between type and type-expression in Swift3. typealias T1 = P1 & P2? // was accepted as '(P1 & P2)?' let T2 = (P1 & P2?).self // was silently accepted as 'P1' in Swift3.0
1 parent 4302a74 commit c138e4c

File tree

5 files changed

+156
-5
lines changed

5 files changed

+156
-5
lines changed

include/swift/AST/DiagnosticsSema.def

+3
Original file line numberDiff line numberDiff line change
@@ -1506,6 +1506,9 @@ ERROR(protocol_composition_not_protocol,none,
15061506
"non-protocol type %0 cannot be used within a protocol composition", (Type))
15071507
ERROR(objc_protocol_inherits_non_objc_protocol,none,
15081508
"@objc protocol %0 cannot refine non-@objc protocol %1", (Type, Type))
1509+
WARNING(protocol_composition_with_postfix,none,
1510+
"protocol composition with postfix '%0' is ambiguous "
1511+
"and will be rejected in future version of Swift", (StringRef))
15091512

15101513
ERROR(requires_conformance_nonprotocol,none,
15111514
"type %0 constrained to non-protocol type %1", (TypeLoc, TypeLoc))

lib/Sema/TypeCheckType.cpp

+101
Original file line numberDiff line numberDiff line change
@@ -2614,8 +2614,109 @@ Type TypeResolver::resolveTupleType(TupleTypeRepr *repr,
26142614
return TupleType::get(elements, Context);
26152615
}
26162616

2617+
/// Restore Swift3 behavior of ambiguous compostion for source compatibility.
2618+
///
2619+
/// Currently, 'P1 & P2.Type' is parsed as (composition P1, (metatype P2))
2620+
/// In Swift3, that was (metatype (composition P1, P2)).
2621+
/// For source compatibility, before resolving Type of that, reconstruct
2622+
/// TypeRepr as so, and emit a warning with fix-it to enclose it with
2623+
/// parenthesis; '(P1 & P2).Type'
2624+
//
2625+
/// \param Comp The type composition to be checked and fixed.
2626+
///
2627+
/// \returns Fixed TypeRepr, or nullptr that indicates no need to fix.
2628+
static TypeRepr *fixCompositionWithPostfix(TypeChecker &TC,
2629+
CompositionTypeRepr *Comp) {
2630+
// Only for Swift3
2631+
if (!TC.Context.isSwiftVersion3())
2632+
return nullptr;
2633+
2634+
auto Types = Comp->getTypes();
2635+
TypeRepr *LastType = nullptr;
2636+
for (auto i = Types.begin(), e = Types.end(); i != e; ++i) {
2637+
if (!isa<IdentTypeRepr>(*i)) {
2638+
// Found non-IdentType not at the last, can't help.
2639+
if (i + 1 != e)
2640+
return nullptr;
2641+
LastType = *i;
2642+
}
2643+
}
2644+
// Only IdentType(s) it's OK.
2645+
if (!LastType)
2646+
return nullptr;
2647+
2648+
// Strip off the postfix type repr.
2649+
SmallVector<TypeRepr *, 2> Postfixes;
2650+
while (true) {
2651+
if (auto T = dyn_cast<ProtocolTypeRepr>(LastType)) {
2652+
Postfixes.push_back(LastType);
2653+
LastType = T->getBase();
2654+
} else if (auto T = dyn_cast<MetatypeTypeRepr>(LastType)) {
2655+
Postfixes.push_back(LastType);
2656+
LastType = T->getBase();
2657+
} else if (auto T = dyn_cast<OptionalTypeRepr>(LastType)) {
2658+
Postfixes.push_back(LastType);
2659+
LastType = T->getBase();
2660+
} else if (auto T =
2661+
dyn_cast<ImplicitlyUnwrappedOptionalTypeRepr>(LastType)) {
2662+
Postfixes.push_back(LastType);
2663+
LastType = T->getBase();
2664+
} else if (!isa<IdentTypeRepr>(LastType)) {
2665+
// Found non-IdentTypeRepr, can't help;
2666+
return nullptr;
2667+
} else {
2668+
break;
2669+
}
2670+
}
2671+
assert(!Postfixes.empty() && isa<IdentTypeRepr>(LastType));
2672+
2673+
// Now, we know we can fix-it. do it.
2674+
SmallVector<TypeRepr *, 4> Protocols(Types.begin(), Types.end() - 1);
2675+
Protocols.push_back(LastType);
2676+
2677+
// Emit fix-it to enclose compostion part into parentheses.
2678+
TypeRepr *InnerMost = Postfixes.back();
2679+
TC.diagnose(InnerMost->getLoc(), diag::protocol_composition_with_postfix,
2680+
isa<ProtocolTypeRepr>(InnerMost) ? ".Protocol" :
2681+
isa<MetatypeTypeRepr>(InnerMost) ? ".Type" :
2682+
isa<OptionalTypeRepr>(InnerMost) ? "?" :
2683+
isa<ImplicitlyUnwrappedOptionalTypeRepr>(InnerMost) ? "!" :
2684+
/* unreachable */"")
2685+
.highlight({Comp->getStartLoc(), LastType->getEndLoc()})
2686+
.fixItInsert(Comp->getStartLoc(), "(")
2687+
.fixItInsertAfter(LastType->getEndLoc(), ")");
2688+
2689+
// Reconstruct postfix type repr with collected protocols.
2690+
TypeRepr *Fixed = CompositionTypeRepr::create(
2691+
TC.Context, Protocols, Comp->getStartLoc(),
2692+
{Comp->getCompositionRange().Start, LastType->getEndLoc()});
2693+
2694+
// Add back postix TypeRepr(s) to the composition.
2695+
while (Postfixes.size()) {
2696+
auto Postfix = Postfixes.pop_back_val();
2697+
if (auto T = dyn_cast<ProtocolTypeRepr>(Postfix))
2698+
Fixed = new (TC.Context) ProtocolTypeRepr(Fixed, T->getProtocolLoc());
2699+
else if (auto T = dyn_cast<MetatypeTypeRepr>(Postfix))
2700+
Fixed = new (TC.Context) MetatypeTypeRepr(Fixed, T->getMetaLoc());
2701+
else if (auto T = dyn_cast<OptionalTypeRepr>(Postfix))
2702+
Fixed = new (TC.Context) OptionalTypeRepr(Fixed, T->getQuestionLoc());
2703+
else if (auto T = dyn_cast<ImplicitlyUnwrappedOptionalTypeRepr>(Postfix))
2704+
Fixed = new (TC.Context)
2705+
ImplicitlyUnwrappedOptionalTypeRepr(Fixed, T->getExclamationLoc());
2706+
else
2707+
llvm_unreachable("unexpected type repr");
2708+
}
2709+
2710+
return Fixed;
2711+
}
2712+
26172713
Type TypeResolver::resolveCompositionType(CompositionTypeRepr *repr,
26182714
TypeResolutionOptions options) {
2715+
2716+
// Fix 'P1 & P2.Type' to '(P1 & P2).Type' for Swift3
2717+
if (auto fixed = fixCompositionWithPostfix(TC, repr))
2718+
return resolveType(fixed, options);
2719+
26192720
SmallVector<Type, 4> ProtocolTypes;
26202721
for (auto tyR : repr->getTypes()) {
26212722
Type ty = TC.resolveType(tyR, DC, withoutContext(options), Resolver);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: rm -rf %t && mkdir -p %t
2+
// RUN: %utils/split_file.py -o %t %s
3+
// RUN: %target-swift-frontend -parse -primary-file %t/swift3.swift %t/common.swift -verify
4+
// RUN: %target-swift-frontend -parse -primary-file %t/swift4.swift %t/common.swift -verify -swift-version 4
5+
6+
// BEGIN common.swift
7+
protocol P1 {
8+
static func p1() -> Int
9+
}
10+
protocol P2 {
11+
static var p2: Int { get }
12+
}
13+
14+
// BEGIN swift3.swift
15+
16+
// Warning for mitakingly accepted protocol composition production.
17+
func foo(x: P1 & Any & P2.Type?) {
18+
// expected-warning @-1 {{protocol composition with postfix '.Type' is ambiguous and will be rejected in future version of Swift}} {{13-13=(}} {{26-26=)}}
19+
let _: (P1 & P2).Type? = x
20+
let _: (P1 & P2).Type = x!
21+
let _: Int = x!.p1()
22+
let _: Int? = x?.p2
23+
}
24+
25+
// Type expression
26+
func bar() -> ((P1 & P2)?).Type {
27+
let x = (P1 & P2?).self
28+
// expected-warning @-1 {{protocol composition with postfix '?' is ambiguous and will be rejected in future version of Swift}} {{12-12=(}} {{19-19=)}}
29+
return x
30+
}
31+
32+
// Non-ident type at non-last position are rejected anyway.
33+
typealias A1 = P1.Type & P2 // expected-error {{type 'P1.Type' cannot be used within a protocol composition}}
34+
35+
// BEGIN swift4.swift
36+
37+
func foo(x: P1 & Any & P2.Type?) { // expected-error {{non-protocol type 'P2.Type?' cannot be used within a protocol composition}}
38+
let _: (P1 & P2).Type? = x // expected-error {{cannot convert value of type 'P1' to specified type '(P1 & P2).Type?'}}
39+
let _: (P1 & P2).Type = x! // expected-error {{cannot force unwrap value of non-optional type 'P1'}}
40+
let _: Int = x!.p1() // expected-error {{cannot force unwrap value of non-optional type 'P1'}}
41+
let _: Int? = x?.p2 // expected-error {{cannot use optional chaining on non-optional value of type 'P1'}}
42+
}
43+
44+
func bar() -> ((P1 & P2)?).Type {
45+
let x = (P1 & P2?).self // expected-error {{non-protocol type 'P2?' cannot be used within a protocol composition}}
46+
return x // expected-error {{cannot convert return expression}}
47+
}
48+
49+
typealias A1 = P1.Type & P2 // expected-error {{type 'P1.Type' cannot be used within a protocol composition}}

test/Parse/type_expr.swift

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %target-parse-verify-swift
2-
// RUN: %target-parse-verify-swift -enable-astscope-lookup
1+
// RUN: %target-parse-verify-swift -swift-version 4
2+
// RUN: %target-parse-verify-swift -enable-astscope-lookup -swift-version 4
33

44
// Types in expression contexts must be followed by a member access or
55
// constructor call.
@@ -234,7 +234,5 @@ func compositionType() {
234234
_ = (P1 & Int).self // expected-error {{non-protocol type 'Int' cannot be used within a protocol composition}}
235235
_ = (P1? & P2).self // expected-error {{non-protocol type 'P1?' cannot be used within a protocol composition}}
236236

237-
// FIXME: Inconsistency. as per https://bugs.swift.org/browse/SR-2843,
238-
// In Swift3 `typealias P = P1 & P2.Type` is parsed as (metatype (type_composite P1, P2))
239237
_ = (P1 & P2.Type).self // expected-error {{non-protocol type 'P2.Type' cannot be used within a protocol composition}}
240238
}

test/type/protocol_composition.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-parse-verify-swift
1+
// RUN: %target-parse-verify-swift -swift-version 4
22

33
func canonical_empty_protocol() -> Any {
44
return 1

0 commit comments

Comments
 (0)