Skip to content

Commit a77722f

Browse files
committed
[ConstraintSystem] Implicitly open existential type of for-in sequence
This allows to use `for-in` statement to iterate over i.e. `any Collection` and other existentials that conform to `Sequence` protocol.
1 parent 1840724 commit a77722f

File tree

6 files changed

+39
-6
lines changed

6 files changed

+39
-6
lines changed

lib/Sema/CSApply.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8556,6 +8556,7 @@ static Optional<SolutionApplicationTarget> applySolutionToForEachStmt(
85568556
auto resultTarget = target;
85578557
auto &forEachStmtInfo = resultTarget.getForEachStmtInfo();
85588558
auto *stmt = target.getAsForEachStmt();
8559+
auto *parsedSequence = stmt->getSequence();
85598560
bool isAsync = stmt->getAwaitLoc().isValid();
85608561

85618562
// Simplify the various types.
@@ -8673,9 +8674,14 @@ static Optional<SolutionApplicationTarget> applySolutionToForEachStmt(
86738674
stmt->getAwaitLoc().isValid() ? KnownProtocolKind::AsyncSequence
86748675
: KnownProtocolKind::Sequence);
86758676

8677+
auto type = forEachStmtInfo.sequenceType->getRValueType();
8678+
if (type->isExistentialType()) {
8679+
auto *contextualLoc = solution.getConstraintLocator(
8680+
parsedSequence, LocatorPathElt::ContextualType(CTP_ForEachSequence));
8681+
type = Type(solution.OpenedExistentialTypes[contextualLoc]);
8682+
}
86768683
auto sequenceConformance = TypeChecker::conformsToProtocol(
8677-
forEachStmtInfo.sequenceType->getRValueType(), sequenceProto,
8678-
dc->getParentModule());
8684+
type, sequenceProto, dc->getParentModule());
86798685
assert(!sequenceConformance.isInvalid() &&
86808686
"Couldn't find sequence conformance");
86818687
stmt->setSequenceConformance(sequenceConformance);

lib/Sema/CSDiagnostics.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,7 @@ bool MemberAccessOnOptionalBaseFailure::diagnoseAsError() {
12751275
auto &solution = getSolution();
12761276
auto *baseLoc = solution.getConstraintLocator(
12771277
UDE->getBase(),
1278-
LocatorPathElt::ContextualType(CTP_ForEachStmt));
1278+
LocatorPathElt::ContextualType(CTP_ForEachSequence));
12791279

12801280
if (hasFixFor(solution, baseLoc))
12811281
return false;

lib/Sema/CSGen.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3889,7 +3889,7 @@ generateForEachStmtConstraints(
38893889
auto *sequenceExpr = stmt->getSequence();
38903890
auto *dc = target.getDeclContext();
38913891
auto contextualLocator = cs.getConstraintLocator(
3892-
sequenceExpr, LocatorPathElt::ContextualType(CTP_ForEachStmt));
3892+
sequenceExpr, LocatorPathElt::ContextualType(CTP_ForEachSequence));
38933893

38943894
// The expression type must conform to the Sequence protocol.
38953895
auto sequenceProto = TypeChecker::getProtocol(

lib/Sema/CSSimplify.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7153,6 +7153,16 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
71537153
} break;
71547154
case ConstraintKind::ConformsTo:
71557155
case ConstraintKind::LiteralConformsTo: {
7156+
// If existential type is used as a for-in sequence, let's open it
7157+
// and check whether underlying type conforms to `Sequence`.
7158+
if (type->isExistentialType()) {
7159+
if (auto elt = loc->getLastElementAs<LocatorPathElt::ContextualType>()) {
7160+
if (elt->getPurpose() == CTP_ForEachSequence) {
7161+
type = openExistentialType(type, loc).first;
7162+
}
7163+
}
7164+
}
7165+
71567166
// Check whether this type conforms to the protocol.
71577167
auto conformance = DC->getParentModule()->lookupConformance(
71587168
type, protocol, /*allowMissing=*/true);

test/Interpreter/statements.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,13 @@ print(guardvalue)
5151
print("done");
5252
// CHECK-LABEL: done
5353

54+
func testAnyCollection(_ c: any Collection) {
55+
for v in c {
56+
print(v)
57+
}
58+
}
59+
60+
testAnyCollection([1, 2.0, "Hello"])
61+
// CHECK-NEXT: 1
62+
// CHECK-NEXT: 2.0
63+
// CHECK-NEXT: Hello

test/stmt/foreach.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,19 @@ func testOptionalSequence() {
175175
}
176176
}
177177

178-
// FIXME: Should this be allowed?
179178
func testExistentialSequence(s: any Sequence) {
180-
for x in s { // expected-error {{type 'any Sequence' cannot conform to 'Sequence'}} expected-note {{only concrete types such as structs, enums and classes can conform to protocols}}
179+
for x in s {
181180
_ = x
182181
}
183182
}
184183

184+
// rdar://92177656 - implicit existential opening should work with for-in loops
185+
func testForEachWithAnyCollection(c: any Collection) {
186+
for v in c {
187+
print(v)
188+
}
189+
}
190+
185191
// Conditional conformance to Sequence and IteratorProtocol.
186192
protocol P { }
187193

@@ -238,3 +244,4 @@ func testForEachWhereWithClosure(_ x: [Int]) {
238244
for i in x where foo({ i.byteSwapped == 5 }) {}
239245
for i in x where x.contains(where: { $0.byteSwapped == i }) {}
240246
}
247+

0 commit comments

Comments
 (0)