Skip to content

Commit c6dc44d

Browse files
authored
Merge pull request #7841 from jckarter/leading-dot-pattern-fallback
Sema: Let `.foo` patterns fall back to being ExprPatterns if they don't match an enum case.
2 parents af33162 + fc16cb5 commit c6dc44d

File tree

10 files changed

+123
-25
lines changed

10 files changed

+123
-25
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ ERROR(value_type_comparison_with_nil_illegal,none,
164164
ERROR(cannot_match_expr_pattern_with_value,none,
165165
"expression pattern of type %0 cannot match values of type %1",
166166
(Type, Type))
167+
ERROR(cannot_match_unresolved_expr_pattern_with_value,none,
168+
"pattern cannot match values of type %0",
169+
(Type))
167170

168171
ERROR(cannot_reference_compare_types,none,
169172
"cannot check reference equality of functions; operands here have types "

include/swift/AST/Pattern.h

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ class EnumElementPattern : public Pattern {
492492
SourceLoc DotLoc;
493493
SourceLoc NameLoc;
494494
Identifier Name;
495-
EnumElementDecl *ElementDecl;
495+
PointerUnion<EnumElementDecl *, Expr*> ElementDeclOrUnresolvedOriginalExpr;
496496
Pattern /*nullable*/ *SubPattern;
497497

498498
public:
@@ -501,10 +501,25 @@ class EnumElementPattern : public Pattern {
501501
Pattern *SubPattern, Optional<bool> Implicit = None)
502502
: Pattern(PatternKind::EnumElement),
503503
ParentType(ParentType), DotLoc(DotLoc), NameLoc(NameLoc), Name(Name),
504-
ElementDecl(Element), SubPattern(SubPattern) {
504+
ElementDeclOrUnresolvedOriginalExpr(Element),
505+
SubPattern(SubPattern) {
505506
if (Implicit.hasValue() && *Implicit)
506507
setImplicit();
507508
}
509+
510+
/// Create an unresolved EnumElementPattern for a `.foo` pattern relying on
511+
/// contextual type.
512+
EnumElementPattern(SourceLoc DotLoc,
513+
SourceLoc NameLoc,
514+
Identifier Name,
515+
Pattern *SubPattern,
516+
Expr *UnresolvedOriginalExpr)
517+
: Pattern(PatternKind::EnumElement),
518+
ParentType(), DotLoc(DotLoc), NameLoc(NameLoc), Name(Name),
519+
ElementDeclOrUnresolvedOriginalExpr(UnresolvedOriginalExpr),
520+
SubPattern(SubPattern) {
521+
522+
}
508523

509524
bool hasSubPattern() const { return SubPattern; }
510525

@@ -524,8 +539,19 @@ class EnumElementPattern : public Pattern {
524539

525540
Identifier getName() const { return Name; }
526541

527-
EnumElementDecl *getElementDecl() const { return ElementDecl; }
528-
void setElementDecl(EnumElementDecl *d) { ElementDecl = d; }
542+
EnumElementDecl *getElementDecl() const {
543+
return ElementDeclOrUnresolvedOriginalExpr.dyn_cast<EnumElementDecl*>();
544+
}
545+
void setElementDecl(EnumElementDecl *d) {
546+
ElementDeclOrUnresolvedOriginalExpr = d;
547+
}
548+
549+
Expr *getUnresolvedOriginalExpr() const {
550+
return ElementDeclOrUnresolvedOriginalExpr.get<Expr*>();
551+
}
552+
bool hasUnresolvedOriginalExpr() const {
553+
return ElementDeclOrUnresolvedOriginalExpr.is<Expr*>();
554+
}
529555

530556
SourceLoc getNameLoc() const { return NameLoc; }
531557
SourceLoc getLoc() const { return NameLoc; }

lib/Sema/CSApply.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3495,11 +3495,13 @@ namespace {
34953495
Expr *visitUnresolvedPatternExpr(UnresolvedPatternExpr *expr) {
34963496
// If we end up here, we should have diagnosed somewhere else
34973497
// already.
3498-
if (!SuppressDiagnostics) {
3499-
cs.TC.diagnose(expr->getLoc(), diag::pattern_in_expr,
3498+
Expr *simplified = simplifyExprType(expr);
3499+
if (!SuppressDiagnostics
3500+
&& !simplified->getType()->is<UnresolvedType>()) {
3501+
cs.TC.diagnose(simplified->getLoc(), diag::pattern_in_expr,
35003502
expr->getSubPattern()->getKind());
35013503
}
3502-
return expr;
3504+
return simplified;
35033505
}
35043506

35053507
Expr *visitBindOptionalExpr(BindOptionalExpr *expr) {

lib/Sema/CSDiag.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6077,9 +6077,13 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
60776077

60786078
if (callExpr->isImplicit() && overloadName == "~=") {
60796079
// This binop was synthesized when typechecking an expression pattern.
6080-
auto diag = diagnose(lhsExpr->getLoc(),
6081-
diag::cannot_match_expr_pattern_with_value,
6082-
lhsType, rhsType);
6080+
auto diag = lhsType->is<UnresolvedType>()
6081+
? diagnose(lhsExpr->getLoc(),
6082+
diag::cannot_match_unresolved_expr_pattern_with_value,
6083+
rhsType)
6084+
: diagnose(lhsExpr->getLoc(),
6085+
diag::cannot_match_expr_pattern_with_value,
6086+
lhsType, rhsType);
60836087
diag.highlight(lhsExpr->getSourceRange());
60846088
diag.highlight(rhsExpr->getSourceRange());
60856089
if (auto optUnwrappedType = rhsType->getOptionalObjectType()) {

lib/Sema/CSGen.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2670,8 +2670,13 @@ namespace {
26702670

26712671
Type visitUnresolvedPatternExpr(UnresolvedPatternExpr *expr) {
26722672
// If there are UnresolvedPatterns floating around after name binding,
2673-
// they are pattern productions in invalid positions.
2674-
return ErrorType::get(CS.getASTContext());
2673+
// they are pattern productions in invalid positions. However, we will
2674+
// diagnose that condition elsewhere; to avoid unnecessary noise errors,
2675+
// just plop an open type variable here.
2676+
2677+
auto locator = CS.getConstraintLocator(expr);
2678+
auto typeVar = CS.createTypeVariable(locator, TVO_CanBindToLValue);
2679+
return typeVar;
26752680
}
26762681

26772682
/// Get the type T?

lib/Sema/TypeCheckPattern.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,19 +394,19 @@ class ResolvePattern : public ASTVisitor<ResolvePattern,
394394
// Unresolved member syntax '.Element' forms an EnumElement pattern. The
395395
// element will be resolved when we type-check the pattern.
396396
Pattern *visitUnresolvedMemberExpr(UnresolvedMemberExpr *ume) {
397-
// We the unresolved member has an argument, turn it into a subpattern.
397+
// If the unresolved member has an argument, turn it into a subpattern.
398398
Pattern *subPattern = nullptr;
399399
if (auto arg = ume->getArgument()) {
400400
subPattern = getSubExprPattern(arg);
401401
}
402402

403403
// FIXME: Compound names.
404404
return new (TC.Context) EnumElementPattern(
405-
TypeLoc(), ume->getDotLoc(),
405+
ume->getDotLoc(),
406406
ume->getNameLoc().getBaseNameLoc(),
407407
ume->getName().getBaseName(),
408-
nullptr,
409-
subPattern);
408+
subPattern,
409+
ume);
410410
}
411411

412412
// Member syntax 'T.Element' forms a pattern if 'T' is an enum and the
@@ -1004,6 +1004,7 @@ bool TypeChecker::coercePatternToType(Pattern *&P, DeclContext *dc, Type type,
10041004
TypeResolutionOptions options,
10051005
GenericTypeResolver *resolver,
10061006
TypeLoc tyLoc) {
1007+
recur:
10071008
if (tyLoc.isNull()) {
10081009
tyLoc = TypeLoc::withoutLoc(type);
10091010
}
@@ -1377,6 +1378,14 @@ bool TypeChecker::coercePatternToType(Pattern *&P, DeclContext *dc, Type type,
13771378

13781379
return true;
13791380
}
1381+
1382+
// If we have the original expression parse tree, try reinterpreting
1383+
// it as an expr-pattern if enum element lookup failed, since `.foo`
1384+
// could also refer to a static member of the context type.
1385+
} else if (EEP->hasUnresolvedOriginalExpr()) {
1386+
P = new (Context) ExprPattern(EEP->getUnresolvedOriginalExpr(),
1387+
nullptr, nullptr);
1388+
goto recur;
13801389
}
13811390

13821391
diagnose(EEP->getLoc(), diag::enum_element_pattern_member_not_found,

test/Constraints/patterns.swift

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ case iPadHair.HairForceOne: // expected-error{{generic enum type 'iPadHair' is a
114114
()
115115
case Watch.Edition: // TODO: should warn that cast can't succeed with currently known conformances
116116
()
117-
case .HairForceOne: // expected-error{{enum case 'HairForceOne' not found in type 'HairType'}}
117+
// TODO: Bad error message
118+
case .HairForceOne: // expected-error{{cannot convert}}
118119
()
119120
default:
120121
break
@@ -254,3 +255,53 @@ class SomeClass {}
254255
if case let doesNotExist as SomeClass:AlsoDoesNotExist {}
255256
// expected-error@-1 {{use of undeclared type 'AlsoDoesNotExist'}}
256257
// expected-error@-2 {{variable binding in a condition requires an initializer}}
258+
259+
// `.foo` and `.bar(...)` pattern syntax should also be able to match
260+
// static members as expr patterns
261+
262+
struct StaticMembers: Equatable {
263+
init() {}
264+
init(_: Int) {}
265+
init?(opt: Int) {}
266+
static var prop = StaticMembers()
267+
static var optProp: Optional = StaticMembers()
268+
269+
static func method(_: Int) -> StaticMembers { return prop }
270+
static func method(withLabel: Int) -> StaticMembers { return prop }
271+
static func optMethod(_: Int) -> StaticMembers? { return optProp }
272+
273+
static func ==(x: StaticMembers, y: StaticMembers) -> Bool { return true }
274+
}
275+
276+
let staticMembers = StaticMembers()
277+
let optStaticMembers: Optional = StaticMembers()
278+
279+
switch staticMembers {
280+
case .init: break // expected-error{{cannot match values of type 'StaticMembers'}}
281+
case .init(opt:): break // expected-error{{cannot match values of type 'StaticMembers'}}
282+
case .init(): break
283+
284+
case .init(0): break
285+
case .init(_): break // expected-error{{'_' can only appear in a pattern}}
286+
case .init(let x): break // expected-error{{cannot appear in an expression}}
287+
case .init(opt: 0): break // expected-error{{not unwrapped}}
288+
289+
case .prop: break
290+
// TODO: repeated error message
291+
case .optProp: break // expected-error* {{not unwrapped}}
292+
293+
case .method: break // expected-error{{cannot match}}
294+
case .method(0): break
295+
case .method(_): break // expected-error{{'_' can only appear in a pattern}}
296+
case .method(let x): break // expected-error{{cannot appear in an expression}}
297+
298+
case .method(withLabel:): break // expected-error{{cannot match}}
299+
case .method(withLabel: 0): break
300+
case .method(withLabel: _): break // expected-error{{'_' can only appear in a pattern}}
301+
case .method(withLabel: let x): break // expected-error{{cannot appear in an expression}}
302+
303+
case .optMethod: break // expected-error{{cannot match}}
304+
case .optMethod(0): break // expected-error{{not unwrapped}}
305+
}
306+
307+
_ = 0

test/Parse/matching_patterns.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ enum Voluntary<T> : Equatable {
100100
}
101101

102102
switch foo {
103-
case .Naught: // expected-error{{enum case 'Naught' not found in type 'Foo'}}
103+
case .Naught: // expected-error{{pattern cannot match values of type 'Foo'}}
104104
()
105105
case .A, .B, .C:
106106
()
@@ -137,7 +137,7 @@ case .Twain,
137137
var notAnEnum = 0
138138

139139
switch notAnEnum {
140-
case .Foo: // expected-error{{enum case 'Foo' not found in type 'Int'}}
140+
case .Foo: // expected-error{{pattern cannot match values of type 'Int'}}
141141
()
142142
}
143143

@@ -264,10 +264,8 @@ case +++(_, var d, 3):
264264
// expected-error@-1{{'+++' is not a prefix unary operator}}
265265
()
266266
case (_, var e, 3) +++ (1, 2, 3):
267-
// expected-error@-1{{binary operator '+++' cannot be applied to operands of type '(_, <<error type>>, Int)' and '(Int, Int, Int)'}}
268-
// expected-note@-2{{expected an argument list of type '((Int, Int, Int), (Int, Int, Int))'}}
269-
// expected-error@-3{{'var' binding pattern cannot appear in an expression}}
270-
// expected-error@-4{{'var' binding pattern cannot appear in an expression}}
267+
// expected-error@-1{{'_' can only appear in a pattern}}
268+
// expected-error@-2{{'var' binding pattern cannot appear in an expression}}
271269
()
272270
}
273271

test/Parse/switch.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ Gronk:
264264

265265
func enumElementSyntaxOnTuple() {
266266
switch (1, 1) {
267-
case .Bar: // expected-error {{enum case 'Bar' not found in type '(Int, Int)'}}
267+
case .Bar: // expected-error {{pattern cannot match values of type '(Int, Int)'}}
268268
break
269269
default:
270270
break

test/stmt/statements.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ func RepeatWhileStmt4() {
247247

248248
func brokenSwitch(_ x: Int) -> Int {
249249
switch x {
250-
case .Blah(var rep): // expected-error{{enum case 'Blah' not found in type 'Int'}}
250+
case .Blah(var rep): // expected-error{{pattern cannot match values of type 'Int'}}
251251
return rep
252252
}
253253
}

0 commit comments

Comments
 (0)