Skip to content

Commit fc16cb5

Browse files
committed
Sema: Let .foo patterns fall back to being ExprPatterns if they don't match an enum case.
This lets you match `case .foo` when `foo` resolves to any static member, instead of only a `case`, albeit without the exhaustiveness checking and subpattern capabilities of proper cases. While we're here, adjust the type system we set up for unresolved patterns embedded in expressions so that we give better signal in the error messages too.
1 parent 17da6b3 commit fc16cb5

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
@@ -6086,9 +6086,13 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
60866086

60876087
if (callExpr->isImplicit() && overloadName == "~=") {
60886088
// This binop was synthesized when typechecking an expression pattern.
6089-
auto diag = diagnose(lhsExpr->getLoc(),
6090-
diag::cannot_match_expr_pattern_with_value,
6091-
lhsType, rhsType);
6089+
auto diag = lhsType->is<UnresolvedType>()
6090+
? diagnose(lhsExpr->getLoc(),
6091+
diag::cannot_match_unresolved_expr_pattern_with_value,
6092+
rhsType)
6093+
: diagnose(lhsExpr->getLoc(),
6094+
diag::cannot_match_expr_pattern_with_value,
6095+
lhsType, rhsType);
60926096
diag.highlight(lhsExpr->getSourceRange());
60936097
diag.highlight(rhsExpr->getSourceRange());
60946098
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)