Skip to content

Commit b9f4efc

Browse files
authored
Merge pull request #59437 from xedin/conflicting-patterns-in-case-5.7
[5.7][Diagnostics] Diagnose conflicting pattern variables
2 parents 4b716eb + 9ccf61e commit b9f4efc

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
@@ -390,6 +390,10 @@ enum class FixKind : uint8_t {
390390
/// Coerce a result type of a call to a particular existential type
391391
/// by adding `as any <#Type#>`.
392392
AddExplicitExistentialCoercion,
393+
394+
/// For example `.a(let x), .b(let x)` where `x` gets bound to different
395+
/// types.
396+
RenameConflictingPatternVariables,
393397
};
394398

395399
class ConstraintFix {
@@ -2960,6 +2964,50 @@ class AddExplicitExistentialCoercion final : public ConstraintFix {
29602964
ConstraintLocator *locator);
29612965
};
29622966

2967+
class RenameConflictingPatternVariables final
2968+
: public ConstraintFix,
2969+
private llvm::TrailingObjects<RenameConflictingPatternVariables,
2970+
VarDecl *> {
2971+
friend TrailingObjects;
2972+
2973+
Type ExpectedType;
2974+
unsigned NumConflicts;
2975+
2976+
RenameConflictingPatternVariables(ConstraintSystem &cs, Type expectedTy,
2977+
ArrayRef<VarDecl *> conflicts,
2978+
ConstraintLocator *locator)
2979+
: ConstraintFix(cs, FixKind::RenameConflictingPatternVariables, locator),
2980+
ExpectedType(expectedTy), NumConflicts(conflicts.size()) {
2981+
std::uninitialized_copy(conflicts.begin(), conflicts.end(),
2982+
getConflictingBuffer().begin());
2983+
}
2984+
2985+
MutableArrayRef<VarDecl *> getConflictingBuffer() {
2986+
return {getTrailingObjects<VarDecl *>(), NumConflicts};
2987+
}
2988+
2989+
public:
2990+
std::string getName() const override { return "rename pattern variables"; }
2991+
2992+
ArrayRef<VarDecl *> getConflictingVars() const {
2993+
return {getTrailingObjects<VarDecl *>(), NumConflicts};
2994+
}
2995+
2996+
bool diagnose(const Solution &solution, bool asNote = false) const override;
2997+
2998+
bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
2999+
return diagnose(*commonFixes.front().first);
3000+
}
3001+
3002+
static RenameConflictingPatternVariables *
3003+
create(ConstraintSystem &cs, Type expectedTy, ArrayRef<VarDecl *> conflicts,
3004+
ConstraintLocator *locator);
3005+
3006+
static bool classof(ConstraintFix *fix) {
3007+
return fix->getKind() == FixKind::RenameConflictingPatternVariables;
3008+
}
3009+
};
3010+
29633011
} // end namespace constraints
29643012
} // end namespace swift
29653013

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
@@ -8173,3 +8173,12 @@ void MissingExplicitExistentialCoercion::fixIt(
81738173
"as " + ErasedResultType->getString(printOpts) +
81748174
(requiresParens ? ")" : ""));
81758175
}
8176+
8177+
bool ConflictingPatternVariables::diagnoseAsError() {
8178+
for (auto *var : Vars) {
8179+
emitDiagnosticAt(var->getStartLoc(),
8180+
diag::type_mismatch_multiple_pattern_list, getType(var),
8181+
ExpectedType);
8182+
}
8183+
return true;
8184+
}

lib/Sema/CSDiagnostics.h

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

2743+
/// Diagnose situations where pattern variables with the same name
2744+
/// have conflicting types:
2745+
///
2746+
/// \code
2747+
/// enum E {
2748+
/// case a(Int)
2749+
/// case b(String)
2750+
/// }
2751+
///
2752+
/// func test(e: E) {
2753+
/// switch e {
2754+
/// case .a(let x), .b(let x): ...
2755+
/// }
2756+
/// }
2757+
/// \endcode
2758+
///
2759+
/// In this example `x` is bound to `Int` and `String` at the same
2760+
/// time which is incorrect.
2761+
class ConflictingPatternVariables final : public FailureDiagnostic {
2762+
Type ExpectedType;
2763+
SmallVector<VarDecl *, 4> Vars;
2764+
2765+
public:
2766+
ConflictingPatternVariables(const Solution &solution, Type expectedTy,
2767+
ArrayRef<VarDecl *> conflicts,
2768+
ConstraintLocator *locator)
2769+
: FailureDiagnostic(solution, locator),
2770+
ExpectedType(resolveType(expectedTy)),
2771+
Vars(conflicts.begin(), conflicts.end()) {
2772+
assert(!Vars.empty());
2773+
}
2774+
2775+
bool diagnoseAsError() override;
2776+
};
2777+
27432778
} // end namespace constraints
27442779
} // end namespace swift
27452780

lib/Sema/CSFix.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2378,3 +2378,21 @@ AddExplicitExistentialCoercion::create(ConstraintSystem &cs, Type resultTy,
23782378
return new (cs.getAllocator())
23792379
AddExplicitExistentialCoercion(cs, resultTy, locator);
23802380
}
2381+
2382+
bool RenameConflictingPatternVariables::diagnose(const Solution &solution,
2383+
bool asNote) const {
2384+
ConflictingPatternVariables failure(solution, ExpectedType,
2385+
getConflictingVars(), getLocator());
2386+
return failure.diagnose(asNote);
2387+
}
2388+
2389+
RenameConflictingPatternVariables *
2390+
RenameConflictingPatternVariables::create(ConstraintSystem &cs, Type expectedTy,
2391+
ArrayRef<VarDecl *> conflicts,
2392+
ConstraintLocator *locator) {
2393+
unsigned size = totalSizeToAlloc<VarDecl *>(conflicts.size());
2394+
void *mem = cs.getAllocator().Allocate(
2395+
size, alignof(RenameConflictingPatternVariables));
2396+
return new (mem)
2397+
RenameConflictingPatternVariables(cs, expectedTy, conflicts, locator);
2398+
}

lib/Sema/CSSimplify.cpp

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

test/expr/closure/multi_statement.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,3 +483,35 @@ func test_missing_conformance_diagnostics_in_for_sequence() {
483483
}
484484
}
485485
}
486+
487+
func test_conflicting_pattern_vars() {
488+
enum E {
489+
case a(Int, String)
490+
case b(String, Int)
491+
}
492+
493+
func fn(_: (E) -> Void) {}
494+
func fn<T>(_: (E) -> T) {}
495+
496+
func test(e: E) {
497+
fn {
498+
switch $0 {
499+
case .a(let x, let y),
500+
.b(let x, let y):
501+
// expected-error@-1 {{pattern variable bound to type 'String', expected type 'Int'}}
502+
// expected-error@-2 {{pattern variable bound to type 'Int', expected type 'String'}}
503+
_ = x
504+
_ = y
505+
}
506+
}
507+
508+
fn {
509+
switch $0 {
510+
case .a(let x, let y),
511+
.b(let y, let x): // Ok
512+
_ = x
513+
_ = y
514+
}
515+
}
516+
}
517+
}

0 commit comments

Comments
 (0)