Skip to content

Commit 773ea6b

Browse files
committed
[TypeChecker] Require a coercion if result of protocol member access would loose information
Accessing members on the protocol could result in existential opening and subsequence result erasure, which requires explicit coercion if there is any loss of generic requirements.
1 parent 73b16ba commit 773ea6b

File tree

5 files changed

+132
-44
lines changed

5 files changed

+132
-44
lines changed

include/swift/Sema/CSFix.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3000,11 +3000,16 @@ class AddExplicitExistentialCoercion final : public ConstraintFix {
30003000

30013001
bool diagnose(const Solution &solution, bool asNote = false) const override;
30023002

3003-
static bool isRequired(
3004-
ConstraintSystem &cs, Type resultTy,
3005-
SmallVectorImpl<std::pair<TypeVariableType *, OpenedArchetypeType *>>
3006-
&openedExistentials,
3007-
ConstraintLocatorBuilder locator);
3003+
static bool
3004+
isRequired(ConstraintSystem &cs, Type resultTy,
3005+
ArrayRef<std::pair<TypeVariableType *, OpenedArchetypeType *>>
3006+
openedExistentials,
3007+
ConstraintLocatorBuilder locator);
3008+
3009+
static bool isRequired(ConstraintSystem &cs, Type resultTy,
3010+
llvm::function_ref<Optional<Type>(TypeVariableType *)>
3011+
findExistentialType,
3012+
ConstraintLocatorBuilder locator);
30083013

30093014
static AddExplicitExistentialCoercion *create(ConstraintSystem &cs,
30103015
Type resultTy,

lib/Sema/CSFix.cpp

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "swift/AST/ParameterList.h"
2222
#include "swift/AST/Type.h"
2323
#include "swift/AST/Types.h"
24+
#include "swift/AST/ExistentialLayout.h"
2425
#include "swift/Basic/SourceManager.h"
2526
#include "swift/Sema/ConstraintLocator.h"
2627
#include "swift/Sema/ConstraintSystem.h"
@@ -2193,35 +2194,26 @@ bool AddExplicitExistentialCoercion::diagnose(const Solution &solution,
21932194

21942195
bool AddExplicitExistentialCoercion::isRequired(
21952196
ConstraintSystem &cs, Type resultTy,
2196-
SmallVectorImpl<std::pair<TypeVariableType *, OpenedArchetypeType *>>
2197-
&openedExistentials,
2197+
llvm::function_ref<Optional<Type>(TypeVariableType *)> findExistentialType,
21982198
ConstraintLocatorBuilder locator) {
2199-
using ExistentialList =
2200-
SmallVectorImpl<std::pair<TypeVariableType *, OpenedArchetypeType *>> &;
2199+
using ExistentialTypeFinder =
2200+
llvm::function_ref<Optional<Type>(TypeVariableType *)>;
22012201

22022202
struct CoercionChecker : public TypeWalker {
22032203
bool RequiresCoercion = false;
22042204

22052205
ConstraintSystem &cs;
2206-
ExistentialList OpenedExistentials;
2206+
ExistentialTypeFinder GetExistentialType;
22072207

2208-
CoercionChecker(ConstraintSystem &cs, ExistentialList openedExistentials)
2209-
: cs(cs), OpenedExistentials(openedExistentials) {}
2208+
CoercionChecker(ConstraintSystem &cs,
2209+
ExistentialTypeFinder getExistentialType)
2210+
: cs(cs), GetExistentialType(getExistentialType) {}
22102211

22112212
Action walkToTypePre(Type componentTy) override {
22122213
// In cases where result references a member type, we need to check
22132214
// whether such type would is resolved to concrete or not.
22142215
if (auto *member = componentTy->getAs<DependentMemberType>()) {
2215-
Type memberBaseTy = member->getBase();
2216-
2217-
// Find the base of this member chain.
2218-
while (true) {
2219-
if (auto *memberTy = memberBaseTy->getAs<DependentMemberType>()) {
2220-
memberBaseTy = memberTy->getBase();
2221-
} else {
2222-
break;
2223-
}
2224-
}
2216+
auto memberBaseTy = getBaseTypeOfDependentMemberChain(member);
22252217

22262218
auto typeVar = memberBaseTy->getAs<TypeVariableType>();
22272219
if (!typeVar)
@@ -2232,17 +2224,12 @@ bool AddExplicitExistentialCoercion::isRequired(
22322224
// we need to check whether any requirements are going to be lost
22332225
// in process.
22342226

2235-
auto opened =
2236-
llvm::find_if(OpenedExistentials, [&typeVar](const auto &entry) {
2237-
return typeVar == entry.first;
2238-
});
2239-
2240-
if (opened == OpenedExistentials.end())
2227+
auto existentialType = GetExistentialType(typeVar);
2228+
if (!existentialType)
22412229
return Action::SkipChildren;
22422230

22432231
auto erasedMemberTy = typeEraseOpenedExistentialReference(
2244-
Type(member), opened->second->getExistentialType(), opened->first,
2245-
TypePosition::Covariant);
2232+
Type(member), *existentialType, typeVar, TypePosition::Covariant);
22462233

22472234
// If result is an existential type and the base has `where` clauses
22482235
// associated with its associated types, the call needs a coercion.
@@ -2258,13 +2245,9 @@ bool AddExplicitExistentialCoercion::isRequired(
22582245
// The case where there is a direct access to opened existential type
22592246
// e.g. `$T` or `[$T]`.
22602247
if (auto *typeVar = componentTy->getAs<TypeVariableType>()) {
2261-
auto opened =
2262-
llvm::find_if(OpenedExistentials, [&typeVar](const auto &entry) {
2263-
return typeVar == entry.first;
2264-
});
2265-
2266-
if (opened != OpenedExistentials.end()) {
2267-
RequiresCoercion |= hasConstrainedAssociatedTypes(opened->second);
2248+
if (auto existentialType = GetExistentialType(typeVar)) {
2249+
RequiresCoercion |= hasConstrainedAssociatedTypes(
2250+
(*existentialType)->getExistentialLayout());
22682251
return RequiresCoercion ? Action::Stop : Action::SkipChildren;
22692252
}
22702253
}
@@ -2286,9 +2269,20 @@ bool AddExplicitExistentialCoercion::isRequired(
22862269
return false;
22872270
}
22882271

2289-
bool hasConstrainedAssociatedTypes(ArchetypeType *archetypeTy) {
2290-
assert(archetypeTy);
2291-
for (auto *protocol : archetypeTy->getConformsTo()) {
2272+
static Type getBaseTypeOfDependentMemberChain(DependentMemberType *member) {
2273+
if (!member->getBase())
2274+
return member;
2275+
2276+
auto base = member->getBase();
2277+
2278+
if (auto *DMT = base->getAs<DependentMemberType>())
2279+
return getBaseTypeOfDependentMemberChain(DMT);
2280+
2281+
return base;
2282+
}
2283+
2284+
static bool hasConstrainedAssociatedTypes(ExistentialLayout layout) {
2285+
for (auto *protocol : layout.getProtocols()) {
22922286
if (llvm::any_of(protocol->getAssociatedTypeMembers(),
22932287
[](const auto *assocTypeDecl) {
22942288
return bool(assocTypeDecl->getTrailingWhereClause());
@@ -2301,19 +2295,48 @@ bool AddExplicitExistentialCoercion::isRequired(
23012295

23022296
// First, let's check whether coercion is already there.
23032297
if (auto *anchor = getAsExpr(locator.getAnchor())) {
2298+
// If this is erasure related to `Self`, let's look through
2299+
// the call, if any.
2300+
if (isa<UnresolvedDotExpr>(anchor)) {
2301+
auto parentExpr = cs.getParentExpr(anchor);
2302+
if (parentExpr && isa<CallExpr>(parentExpr))
2303+
anchor = parentExpr;
2304+
}
2305+
23042306
auto *parent = cs.getParentExpr(anchor);
23052307
// Support both `as` and `as!` coercions.
23062308
if (parent &&
23072309
(isa<CoerceExpr>(parent) || isa<ForcedCheckedCastExpr>(parent)))
23082310
return false;
23092311
}
23102312

2311-
CoercionChecker check(cs, openedExistentials);
2313+
CoercionChecker check(cs, findExistentialType);
23122314
resultTy.walk(check);
23132315

23142316
return check.RequiresCoercion;
23152317
}
23162318

2319+
bool AddExplicitExistentialCoercion::isRequired(
2320+
ConstraintSystem &cs, Type resultTy,
2321+
ArrayRef<std::pair<TypeVariableType *, OpenedArchetypeType *>>
2322+
openedExistentials,
2323+
ConstraintLocatorBuilder locator) {
2324+
return isRequired(
2325+
cs, resultTy,
2326+
[&](TypeVariableType *typeVar) -> Optional<Type> {
2327+
auto opened =
2328+
llvm::find_if(openedExistentials, [&typeVar](const auto &entry) {
2329+
return typeVar == entry.first;
2330+
});
2331+
2332+
if (opened == openedExistentials.end())
2333+
return None;
2334+
2335+
return opened->second->getExistentialType();
2336+
},
2337+
locator);
2338+
}
2339+
23172340
AddExplicitExistentialCoercion *
23182341
AddExplicitExistentialCoercion::create(ConstraintSystem &cs, Type resultTy,
23192342
ConstraintLocator *locator) {

lib/Sema/ConstraintSystem.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,11 +2296,30 @@ ConstraintSystem::getTypeOfMemberReference(
22962296
baseObjTy->isExistentialType() && outerDC->getSelfProtocolDecl() &&
22972297
// If there are no type variables, there were no references to 'Self'.
22982298
type->hasTypeVariable()) {
2299+
auto getResultType = [](Type type) {
2300+
if (auto *funcTy = type->getAs<FunctionType>())
2301+
return funcTy->getResult();
2302+
return type;
2303+
};
2304+
2305+
auto nonErasedResultTy = getResultType(type);
2306+
22992307
const auto selfGP = cast<GenericTypeParamType>(
23002308
outerDC->getSelfInterfaceType()->getCanonicalType());
23012309
auto openedTypeVar = replacements.lookup(selfGP);
23022310
type = typeEraseOpenedExistentialReference(type, baseObjTy, openedTypeVar,
23032311
TypePosition::Covariant);
2312+
2313+
if (!hasFixFor(locator) &&
2314+
AddExplicitExistentialCoercion::isRequired(
2315+
*this, nonErasedResultTy,
2316+
[&](TypeVariableType *typeVar) {
2317+
return openedTypeVar == typeVar ? baseObjTy : Optional<Type>();
2318+
},
2319+
locator)) {
2320+
recordFix(AddExplicitExistentialCoercion::create(
2321+
*this, getResultType(type), locator));
2322+
}
23042323
}
23052324

23062325
// Construct an idealized parameter type of the initializer associated

test/Constraints/opened_existentials.swift

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,24 @@ protocol B {
216216
associatedtype D: P
217217
}
218218

219-
func testExplicitCoercionRequirement(v: any B) {
219+
protocol D {
220+
associatedtype E
221+
}
222+
223+
extension B {
224+
var testVar: (Int, [C]) { get { fatalError() } }
225+
226+
func getC() -> C { fatalError() }
227+
}
228+
229+
func testExplicitCoercionRequirement(v: any B, otherV: any B & D) {
220230
func getC<T: B>(_: T) -> T.C { fatalError() }
221231
func getTuple<T: B>(_: T) -> (T, T.C) { fatalError() }
222232
func getNoError<T: B>(_: T) -> T.C.A { fatalError() }
223233
func getComplex<T: B>(_: T) -> ([(x: (a: T.C, b: Int), y: Int)], [Int: T.C]) { fatalError() }
224234

225235
func overloaded<T: B>(_: T) -> (x: Int, y: T.C) { fatalError() }
226-
// expected-note@-1 {{inferred result type '(x: Int, y: any P)' requires explicit coercion due to loss of generic requirements}} {{241:20-20=as (x: Int, y: any P)}}
236+
// expected-note@-1 {{inferred result type '(x: Int, y: any P)' requires explicit coercion due to loss of generic requirements}} {{251:20-20=as (x: Int, y: any P)}}
227237
func overloaded<T: P>(_: T) -> Int { 42 }
228238
// expected-note@-1 {{candidate requires that 'any B' conform to 'P' (requirement specified as 'T' : 'P')}}
229239

@@ -240,7 +250,37 @@ func testExplicitCoercionRequirement(v: any B) {
240250

241251
_ = overloaded(v) // expected-error {{no exact matches in call to local function 'overloaded'}}
242252

253+
func acceptsAny<T>(_: T) {}
254+
255+
acceptsAny(getC(v)) // expected-error {{inferred result type 'any P' requires explicit coercion due to loss of generic requirements}} {{21-21=as any P}}
256+
acceptsAny(getC(v) as any P) // Ok
257+
258+
acceptsAny(getComplex(v)) // expected-error {{inferred result type '([(x: (a: any P, b: Int), y: Int)], [Int : any P])' requires explicit coercion due to loss of generic requirements}} {{27-27=as ([(x: (a: any P, b: Int), y: Int)], [Int : any P])}}
259+
acceptsAny(getComplex(v) as ([(x: (a: any P, b: Int), y: Int)], [Int : any P]))
260+
243261
func getAssocNoRequirements<T: B>(_: T) -> (Int, [T.D]) { fatalError() }
244262

245263
_ = getAssocNoRequirements(v) // Ok, `D` doesn't have any requirements
264+
265+
// Test existential opening from protocol extension access
266+
_ = v.getC() // expected-error {{inferred result type 'any P' requires explicit coercion due to loss of generic requirements}} {{13-13=as any P}}
267+
_ = v.getC() as any P // Ok
268+
269+
_ = v.testVar // expected-error {{inferred result type '(Int, [any P])' requires explicit coercion due to loss of generic requirements}} {{16-16=as (Int, [any P])}}
270+
_ = v.testVar as (Int, [any P])
271+
272+
func getE<T: D>(_: T) -> T.E { fatalError() }
273+
274+
_ = getE(otherV) // Ok `E` doesn't have a `where` clause
275+
276+
func getSelf<T: B>(_: T) -> T { fatalError() } // expected-note {{found this candidate}}
277+
func getSelf<T: D>(_: T) -> T { fatalError() } // expected-note {{found this candidate}}
278+
279+
_ = getSelf(v) // expected-error {{inferred result type 'any B' requires explicit coercion due to loss of generic requirements}} {{17-17=as any B}}
280+
_ = getSelf(v) as any B // Ok
281+
_ = getSelf(otherV) as any B & D // expected-error {{ambiguous use of 'getSelf'}}
282+
283+
func getBDSelf<T: D>(_: T) -> T { fatalError() }
284+
_ = getBDSelf(otherV) // expected-error {{inferred result type 'any B & D' requires explicit coercion due to loss of generic requirements}} {{24-24=as any B & D}}
285+
_ = getBDSelf(otherV) as any B & D // Ok
246286
}

test/decl/protocol/existential_member_accesses_self_assoctype.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,8 @@ do {
786786
_ = arg.method3 // expected-error {{member 'method3' cannot be used on value of type 'any ConcreteAssocTypes'; consider using a generic constraint instead}}
787787
_ = arg.property1 // expected-error {{member 'property1' cannot be used on value of type 'any ConcreteAssocTypes'; consider using a generic constraint instead}}
788788
// Covariant 'Self' erasure works in conjunction with concrete associated types.
789-
let _: (Bool, any ConcreteAssocTypes) = arg.property2 // ok
789+
let _: (Bool, any ConcreteAssocTypes) = arg.property2 // expected-error {{inferred result type '(Bool, any ConcreteAssocTypes)' requires explicit coercion due to loss of generic requirements}} {{58-58=as (Bool, any ConcreteAssocTypes)}}
790+
let _: (Bool, any ConcreteAssocTypes) = arg.property2 as (Bool, any ConcreteAssocTypes) // Ok
790791
_ = arg.property3 // expected-error {{member 'property3' cannot be used on value of type 'any ConcreteAssocTypes'; consider using a generic constraint instead}}
791792
_ = arg[subscript1: false] // expected-error {{member 'subscript' cannot be used on value of type 'any ConcreteAssocTypes'; consider using a generic constraint instead}}
792793
_ = arg[subscript2: false] // expected-error {{member 'subscript' cannot be used on value of type 'any ConcreteAssocTypes'; consider using a generic constraint instead}}

0 commit comments

Comments
 (0)