Skip to content

Commit e02fab9

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 d569448 commit e02fab9

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
@@ -8555,6 +8555,7 @@ static Optional<SolutionApplicationTarget> applySolutionToForEachStmt(
85558555
auto resultTarget = target;
85568556
auto &forEachStmtInfo = resultTarget.getForEachStmtInfo();
85578557
auto *stmt = target.getAsForEachStmt();
8558+
auto *parsedSequence = stmt->getSequence();
85588559
bool isAsync = stmt->getAwaitLoc().isValid();
85598560

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

8676+
auto type = forEachStmtInfo.sequenceType->getRValueType();
8677+
if (type->isExistentialType()) {
8678+
auto *contextualLoc = solution.getConstraintLocator(
8679+
parsedSequence, LocatorPathElt::ContextualType(CTP_ForEachSequence));
8680+
type = Type(solution.OpenedExistentialTypes[contextualLoc]);
8681+
}
86758682
auto sequenceConformance = TypeChecker::conformsToProtocol(
8676-
forEachStmtInfo.sequenceType->getRValueType(), sequenceProto,
8677-
dc->getParentModule());
8683+
type, sequenceProto, dc->getParentModule());
86788684
assert(!sequenceConformance.isInvalid() &&
86798685
"Couldn't find sequence conformance");
86808686
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
@@ -7139,6 +7139,16 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
71397139
} break;
71407140
case ConstraintKind::ConformsTo:
71417141
case ConstraintKind::LiteralConformsTo: {
7142+
// If existential type is used as a for-in sequence, let's open it
7143+
// and check whether underlying type conforms to `Sequence`.
7144+
if (type->isExistentialType()) {
7145+
if (auto elt = loc->getLastElementAs<LocatorPathElt::ContextualType>()) {
7146+
if (elt->getPurpose() == CTP_ForEachSequence) {
7147+
type = openExistentialType(type, loc).first;
7148+
}
7149+
}
7150+
}
7151+
71427152
// Check whether this type conforms to the protocol.
71437153
auto conformance = DC->getParentModule()->lookupConformance(
71447154
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)