Skip to content

Commit d726c90

Browse files
committed
[CSBindings] Limit optionality hack only to direct protocol requirements
In situations like: $T0 subtype $T1 $T1 literal conforms to ExpressibleByArrayLiteral $T0 conv [<Type>]? We have to ensure that $T0 gets to maintain its optionality because $T1 then could strip optionality later based on a typing rule where `T <: T?`
1 parent 74600db commit d726c90

File tree

2 files changed

+40
-8
lines changed

2 files changed

+40
-8
lines changed

lib/Sema/CSBindings.cpp

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,17 +120,33 @@ isUnviableDefaultType(Type defaultType,
120120

121121
void ConstraintSystem::PotentialBindings::inferDefaultTypes(
122122
ConstraintSystem &cs, llvm::SmallPtrSetImpl<CanType> &existingTypes) {
123+
auto isDirectRequirement = [&](Constraint *constraint) -> bool {
124+
if (auto *typeVar = constraint->getFirstType()->getAs<TypeVariableType>()) {
125+
auto *repr = cs.getRepresentative(typeVar);
126+
return repr == TypeVar;
127+
}
128+
129+
return false;
130+
};
131+
123132
// If we have any literal constraints, check whether there is already a
124133
// binding that provides a type that conforms to that literal protocol. In
125134
// such cases, don't add the default binding suggestion because the existing
126135
// suggestion is better.
127136
//
128137
// Note that ordering is important when it comes to bindings, we'd like to
129-
// add any "direct" default types first to attempt them before transitive ones.
130-
llvm::SmallMapVector<ProtocolDecl *, bool, 4> literalProtocols;
138+
// add any "direct" default types first to attempt them before transitive
139+
// ones.
140+
//
141+
// Key is a literal protocol requirement, Value indicates whether (first)
142+
// given protocol is a direct requirement, and (second) whether it has been
143+
// covered by an existing binding.
144+
llvm::SmallMapVector<ProtocolDecl *, std::pair<bool, bool>, 4>
145+
literalProtocols;
131146
for (auto *constraint : Protocols) {
132147
if (constraint->getKind() == ConstraintKind::LiteralConformsTo)
133-
literalProtocols.insert({constraint->getProtocol(), false});
148+
literalProtocols.insert({constraint->getProtocol(),
149+
{isDirectRequirement(constraint), false}});
134150
}
135151

136152
for (auto &binding : Bindings) {
@@ -153,7 +169,8 @@ void ConstraintSystem::PotentialBindings::inferDefaultTypes(
153169
bool requiresUnwrap = false;
154170
for (auto &entry : literalProtocols) {
155171
auto *protocol = entry.first;
156-
bool &isCovered = entry.second;
172+
bool isDirectRequirement = entry.second.first;
173+
bool &isCovered = entry.second.second;
157174

158175
if (isCovered)
159176
continue;
@@ -169,6 +186,12 @@ void ConstraintSystem::PotentialBindings::inferDefaultTypes(
169186
break;
170187
}
171188

189+
// If this literal protocol is not a direct requirement it
190+
// would be possible to change optionality while inferring
191+
// bindings for a supertype, so this hack doesn't apply.
192+
if (!isDirectRequirement)
193+
break;
194+
172195
// If we're allowed to bind to subtypes, look through optionals.
173196
// FIXME: This is really crappy special case of computing a reasonable
174197
// result based on the given constraints.
@@ -193,7 +216,7 @@ void ConstraintSystem::PotentialBindings::inferDefaultTypes(
193216
// binding it can't provide a default type.
194217
auto isUnviableForDefaulting = [&literalProtocols](ProtocolDecl *protocol) {
195218
auto literal = literalProtocols.find(protocol);
196-
return literal == literalProtocols.end() || literal->second;
219+
return literal == literalProtocols.end() || literal->second.second;
197220
};
198221

199222
for (auto *constraint : Protocols) {
@@ -225,10 +248,8 @@ void ConstraintSystem::PotentialBindings::inferDefaultTypes(
225248
// We need to figure out whether this is a direct conformance
226249
// requirement or inferred transitive one to identify binding
227250
// kind correctly.
228-
auto *conformingVar = cs.getRepresentative(
229-
constraint->getFirstType()->castTo<TypeVariableType>());
230251
addPotentialBinding({defaultType,
231-
TypeVar == conformingVar
252+
isDirectRequirement(constraint)
232253
? AllowedBindingKind::Subtypes
233254
: AllowedBindingKind::Supertypes,
234255
constraint});

test/Constraints/construction.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,14 @@ func sr_10837() {
254254
}
255255
}
256256
}
257+
258+
// To make sure that hack related to type variable bindings works as expected we need to test
259+
// that in the following case result of a call to `reduce` maintains optionality.
260+
func test_that_optionality_of_closure_result_is_preserved() {
261+
struct S {}
262+
263+
let arr: [S?] = []
264+
let _: [S]? = arr.reduce([], { (a: [S]?, s: S?) -> [S]? in
265+
a.flatMap { (group: [S]) -> [S]? in s.map { group + [$0] } } // Ok
266+
})
267+
}

0 commit comments

Comments
 (0)