Skip to content

Commit 7ba07ae

Browse files
authored
Merge pull request #59210 from xedin/conflicting-patterns-in-case
[Diagnostics] Diagnose conflicting pattern variables
2 parents 4606c9f + 9c8bebe commit 7ba07ae

File tree

7 files changed

+219
-7
lines changed

7 files changed

+219
-7
lines changed

include/swift/Sema/CSFix.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,10 @@ enum class FixKind : uint8_t {
398398
/// Coerce a result type of a call to a particular existential type
399399
/// by adding `as any <#Type#>`.
400400
AddExplicitExistentialCoercion,
401+
402+
/// For example `.a(let x), .b(let x)` where `x` gets bound to different
403+
/// types.
404+
RenameConflictingPatternVariables,
401405
};
402406

403407
class ConstraintFix {
@@ -3019,6 +3023,50 @@ class AddExplicitExistentialCoercion final : public ConstraintFix {
30193023
ConstraintLocator *locator);
30203024
};
30213025

3026+
class RenameConflictingPatternVariables final
3027+
: public ConstraintFix,
3028+
private llvm::TrailingObjects<RenameConflictingPatternVariables,
3029+
VarDecl *> {
3030+
friend TrailingObjects;
3031+
3032+
Type ExpectedType;
3033+
unsigned NumConflicts;
3034+
3035+
RenameConflictingPatternVariables(ConstraintSystem &cs, Type expectedTy,
3036+
ArrayRef<VarDecl *> conflicts,
3037+
ConstraintLocator *locator)
3038+
: ConstraintFix(cs, FixKind::RenameConflictingPatternVariables, locator),
3039+
ExpectedType(expectedTy), NumConflicts(conflicts.size()) {
3040+
std::uninitialized_copy(conflicts.begin(), conflicts.end(),
3041+
getConflictingBuffer().begin());
3042+
}
3043+
3044+
MutableArrayRef<VarDecl *> getConflictingBuffer() {
3045+
return {getTrailingObjects<VarDecl *>(), NumConflicts};
3046+
}
3047+
3048+
public:
3049+
std::string getName() const override { return "rename pattern variables"; }
3050+
3051+
ArrayRef<VarDecl *> getConflictingVars() const {
3052+
return {getTrailingObjects<VarDecl *>(), NumConflicts};
3053+
}
3054+
3055+
bool diagnose(const Solution &solution, bool asNote = false) const override;
3056+
3057+
bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
3058+
return diagnose(*commonFixes.front().first);
3059+
}
3060+
3061+
static RenameConflictingPatternVariables *
3062+
create(ConstraintSystem &cs, Type expectedTy, ArrayRef<VarDecl *> conflicts,
3063+
ConstraintLocator *locator);
3064+
3065+
static bool classof(ConstraintFix *fix) {
3066+
return fix->getKind() == FixKind::RenameConflictingPatternVariables;
3067+
}
3068+
};
3069+
30223070
} // end namespace constraints
30233071
} // end namespace swift
30243072

lib/Sema/CSClosure.cpp

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ class SyntacticElementConstraintGenerator
338338
hadError = true;
339339
return;
340340
}
341+
342+
caseItem->setPattern(pattern, /*resolved=*/true);
341343
}
342344

343345
// Let's generate constraints for pattern + where clause.
@@ -780,8 +782,6 @@ class SyntacticElementConstraintGenerator
780782
}
781783
}
782784

783-
bindSwitchCasePatternVars(context.getAsDeclContext(), caseStmt);
784-
785785
auto *caseLoc = cs.getConstraintLocator(
786786
locator, LocatorPathElt::SyntacticElement(caseStmt));
787787

@@ -805,10 +805,8 @@ class SyntacticElementConstraintGenerator
805805
locator->castLastElementTo<LocatorPathElt::SyntacticElement>()
806806
.asStmt());
807807

808-
for (auto caseBodyVar : caseStmt->getCaseBodyVariablesOrEmptyArray()) {
809-
auto parentVar = caseBodyVar->getParentVarDecl();
810-
assert(parentVar && "Case body variables always have parents");
811-
cs.setType(caseBodyVar, cs.getType(parentVar));
808+
if (recordInferredSwitchCasePatternVars(caseStmt)) {
809+
hadError = true;
812810
}
813811
}
814812

@@ -935,6 +933,75 @@ class SyntacticElementConstraintGenerator
935933
locator->getLastElementAs<LocatorPathElt::SyntacticElement>();
936934
return parentElt ? parentElt->getElement().isStmt(kind) : false;
937935
}
936+
937+
bool recordInferredSwitchCasePatternVars(CaseStmt *caseStmt) {
938+
llvm::SmallDenseMap<Identifier, SmallVector<VarDecl *, 2>, 4> patternVars;
939+
940+
auto recordVar = [&](VarDecl *var) {
941+
if (!var->hasName())
942+
return;
943+
patternVars[var->getName()].push_back(var);
944+
};
945+
946+
for (auto &caseItem : caseStmt->getMutableCaseLabelItems()) {
947+
assert(caseItem.isPatternResolved());
948+
949+
auto *pattern = caseItem.getPattern();
950+
pattern->forEachVariable([&](VarDecl *var) { recordVar(var); });
951+
}
952+
953+
for (auto bodyVar : caseStmt->getCaseBodyVariablesOrEmptyArray()) {
954+
if (!bodyVar->hasName())
955+
continue;
956+
957+
const auto &variants = patternVars[bodyVar->getName()];
958+
959+
auto getType = [&](VarDecl *var) {
960+
auto type = cs.simplifyType(cs.getType(var));
961+
assert(!type->hasTypeVariable());
962+
return type;
963+
};
964+
965+
switch (variants.size()) {
966+
case 0:
967+
break;
968+
969+
case 1:
970+
// If there is only one choice here, let's use it directly.
971+
cs.setType(bodyVar, getType(variants.front()));
972+
break;
973+
974+
default: {
975+
// If there are multiple choices it could only mean multiple
976+
// patterns e.g. `.a(let x), .b(let x), ...:`. Let's join them.
977+
Type joinType = getType(variants.front());
978+
979+
SmallVector<VarDecl *, 2> conflicts;
980+
for (auto *var : llvm::drop_begin(variants)) {
981+
auto varType = getType(var);
982+
// Type mismatch between different patterns.
983+
if (!joinType->isEqual(varType))
984+
conflicts.push_back(var);
985+
}
986+
987+
if (!conflicts.empty()) {
988+
if (!cs.shouldAttemptFixes())
989+
return true;
990+
991+
// dfdf
992+
auto *locator = cs.getConstraintLocator(bodyVar);
993+
if (cs.recordFix(RenameConflictingPatternVariables::create(
994+
cs, joinType, conflicts, locator)))
995+
return true;
996+
}
997+
998+
cs.setType(bodyVar, joinType);
999+
}
1000+
}
1001+
}
1002+
1003+
return false;
1004+
}
9381005
};
9391006
}
9401007

@@ -1342,6 +1409,8 @@ class SyntacticElementSolutionApplication
13421409
}
13431410
}
13441411

1412+
bindSwitchCasePatternVars(context.getAsDeclContext(), caseStmt);
1413+
13451414
for (auto *expected : caseStmt->getCaseBodyVariablesOrEmptyArray()) {
13461415
assert(expected->hasName());
13471416
auto prev = expected->getParentVarDecl();

lib/Sema/CSDiagnostics.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8225,3 +8225,12 @@ void MissingExplicitExistentialCoercion::fixIt(
82258225
"as " + ErasedResultType->getString(printOpts) +
82268226
(requiresParens ? ")" : ""));
82278227
}
8228+
8229+
bool ConflictingPatternVariables::diagnoseAsError() {
8230+
for (auto *var : Vars) {
8231+
emitDiagnosticAt(var->getStartLoc(),
8232+
diag::type_mismatch_multiple_pattern_list, getType(var),
8233+
ExpectedType);
8234+
}
8235+
return true;
8236+
}

lib/Sema/CSDiagnostics.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2764,6 +2764,41 @@ class MissingExplicitExistentialCoercion final : public FailureDiagnostic {
27642764
bool fixItRequiresParens() const;
27652765
};
27662766

2767+
/// Diagnose situations where pattern variables with the same name
2768+
/// have conflicting types:
2769+
///
2770+
/// \code
2771+
/// enum E {
2772+
/// case a(Int)
2773+
/// case b(String)
2774+
/// }
2775+
///
2776+
/// func test(e: E) {
2777+
/// switch e {
2778+
/// case .a(let x), .b(let x): ...
2779+
/// }
2780+
/// }
2781+
/// \endcode
2782+
///
2783+
/// In this example `x` is bound to `Int` and `String` at the same
2784+
/// time which is incorrect.
2785+
class ConflictingPatternVariables final : public FailureDiagnostic {
2786+
Type ExpectedType;
2787+
SmallVector<VarDecl *, 4> Vars;
2788+
2789+
public:
2790+
ConflictingPatternVariables(const Solution &solution, Type expectedTy,
2791+
ArrayRef<VarDecl *> conflicts,
2792+
ConstraintLocator *locator)
2793+
: FailureDiagnostic(solution, locator),
2794+
ExpectedType(resolveType(expectedTy)),
2795+
Vars(conflicts.begin(), conflicts.end()) {
2796+
assert(!Vars.empty());
2797+
}
2798+
2799+
bool diagnoseAsError() override;
2800+
};
2801+
27672802
} // end namespace constraints
27682803
} // end namespace swift
27692804

lib/Sema/CSFix.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2449,3 +2449,21 @@ AddExplicitExistentialCoercion::create(ConstraintSystem &cs, Type resultTy,
24492449
return new (cs.getAllocator())
24502450
AddExplicitExistentialCoercion(cs, resultTy, locator);
24512451
}
2452+
2453+
bool RenameConflictingPatternVariables::diagnose(const Solution &solution,
2454+
bool asNote) const {
2455+
ConflictingPatternVariables failure(solution, ExpectedType,
2456+
getConflictingVars(), getLocator());
2457+
return failure.diagnose(asNote);
2458+
}
2459+
2460+
RenameConflictingPatternVariables *
2461+
RenameConflictingPatternVariables::create(ConstraintSystem &cs, Type expectedTy,
2462+
ArrayRef<VarDecl *> conflicts,
2463+
ConstraintLocator *locator) {
2464+
unsigned size = totalSizeToAlloc<VarDecl *>(conflicts.size());
2465+
void *mem = cs.getAllocator().Allocate(
2466+
size, alignof(RenameConflictingPatternVariables));
2467+
return new (mem)
2468+
RenameConflictingPatternVariables(cs, expectedTy, conflicts, locator);
2469+
}

lib/Sema/CSSimplify.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12894,7 +12894,8 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
1289412894
case FixKind::SpecifyTypeForPlaceholder:
1289512895
case FixKind::AllowAutoClosurePointerConversion:
1289612896
case FixKind::IgnoreKeyPathContextualMismatch:
12897-
case FixKind::NotCompileTimeConst: {
12897+
case FixKind::NotCompileTimeConst:
12898+
case FixKind::RenameConflictingPatternVariables: {
1289812899
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
1289912900
}
1290012901

test/expr/closure/multi_statement.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,3 +515,35 @@ func test_missing_conformance_diagnostics_in_for_sequence() {
515515
}
516516
}
517517
}
518+
519+
func test_conflicting_pattern_vars() {
520+
enum E {
521+
case a(Int, String)
522+
case b(String, Int)
523+
}
524+
525+
func fn(_: (E) -> Void) {}
526+
func fn<T>(_: (E) -> T) {}
527+
528+
func test(e: E) {
529+
fn {
530+
switch $0 {
531+
case .a(let x, let y),
532+
.b(let x, let y):
533+
// expected-error@-1 {{pattern variable bound to type 'String', expected type 'Int'}}
534+
// expected-error@-2 {{pattern variable bound to type 'Int', expected type 'String'}}
535+
_ = x
536+
_ = y
537+
}
538+
}
539+
540+
fn {
541+
switch $0 {
542+
case .a(let x, let y),
543+
.b(let y, let x): // Ok
544+
_ = x
545+
_ = y
546+
}
547+
}
548+
}
549+
}

0 commit comments

Comments
 (0)