Skip to content

Commit c6f00fa

Browse files
[Sema] Allow TreatArrayLiteralAsDictionary fix to handle literals with more than one element
1 parent 9a0978a commit c6f00fa

File tree

6 files changed

+110
-60
lines changed

6 files changed

+110
-60
lines changed

include/swift/Sema/CSFix.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -708,10 +708,13 @@ class TreatArrayLiteralAsDictionary final : public ContextualMismatch {
708708
}
709709

710710
bool diagnose(const Solution &solution, bool asNote = false) const override;
711+
bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
712+
return diagnose(*commonFixes.front().first);
713+
}
711714

712-
static TreatArrayLiteralAsDictionary *create(ConstraintSystem &cs,
713-
Type dictionaryTy, Type arrayTy,
714-
ConstraintLocator *loc);
715+
static TreatArrayLiteralAsDictionary *attempt(ConstraintSystem &cs,
716+
Type dictionaryTy, Type arrayTy,
717+
ConstraintLocator *loc);
715718

716719
static bool classof(ConstraintFix *fix) {
717720
return fix->getKind() == FixKind::TreatArrayLiteralAsDictionary;

lib/Sema/CSDiagnostics.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -920,8 +920,21 @@ bool ArrayLiteralToDictionaryConversionFailure::diagnoseAsError() {
920920
CTP == CTP_Initialization);
921921

922922
auto diagnostic = emitDiagnostic(diag::meant_dictionary_lit);
923-
if (AE->getNumElements() == 1)
923+
const auto numElements = AE->getNumElements();
924+
if (numElements == 1) {
924925
diagnostic.fixItInsertAfter(AE->getElement(0)->getEndLoc(), ": <#value#>");
926+
} else {
927+
// If there is an even number of elements in the array, let's produce
928+
// a fix-it which suggests to replace "," with ":" to form a dictionary
929+
// literal.
930+
if ((numElements & 1) == 0) {
931+
const auto commaLocs = AE->getCommaLocs();
932+
if (commaLocs.size() == numElements - 1) {
933+
for (unsigned i = 0, e = numElements / 2; i != e; ++i)
934+
diagnostic.fixItReplace(commaLocs[i * 2], ":");
935+
}
936+
}
937+
}
925938
return true;
926939
}
927940

lib/Sema/CSFix.cpp

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,37 @@ bool TreatArrayLiteralAsDictionary::diagnose(const Solution &solution,
173173
}
174174

175175
TreatArrayLiteralAsDictionary *
176-
TreatArrayLiteralAsDictionary::create(ConstraintSystem &cs,
177-
Type dictionaryTy, Type arrayTy,
178-
ConstraintLocator *locator) {
179-
assert(getAsExpr<ArrayExpr>(locator->getAnchor())->getNumElements() <= 1);
176+
TreatArrayLiteralAsDictionary::attempt(ConstraintSystem &cs, Type dictionaryTy,
177+
Type arrayTy,
178+
ConstraintLocator *locator) {
179+
if (!cs.isArrayType(arrayTy))
180+
return nullptr;
181+
182+
// Determine the ArrayExpr from the locator.
183+
auto *expr = getAsExpr(simplifyLocatorToAnchor(locator));
184+
if (!expr)
185+
return nullptr;
186+
187+
if (auto *AE = dyn_cast<AssignExpr>(expr))
188+
expr = AE->getSrc();
189+
190+
auto *arrayExpr = dyn_cast<ArrayExpr>(expr);
191+
if (!arrayExpr)
192+
return nullptr;
193+
194+
// This fix only applies if the array is used as a dictionary.
195+
auto unwrappedDict = dictionaryTy->lookThroughAllOptionalTypes();
196+
if (unwrappedDict->isTypeVariableOrMember())
197+
return nullptr;
198+
199+
if (!TypeChecker::conformsToKnownProtocol(
200+
unwrappedDict, KnownProtocolKind::ExpressibleByDictionaryLiteral,
201+
cs.DC->getParentModule()))
202+
return nullptr;
203+
204+
auto arrayLoc = cs.getConstraintLocator(arrayExpr);
180205
return new (cs.getAllocator())
181-
TreatArrayLiteralAsDictionary(cs, dictionaryTy, arrayTy, locator);
206+
TreatArrayLiteralAsDictionary(cs, dictionaryTy, arrayTy, arrayLoc);
182207
}
183208

184209
bool MarkExplicitlyEscaping::diagnose(const Solution &solution,

lib/Sema/CSSimplify.cpp

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4225,52 +4225,25 @@ static bool repairArrayLiteralUsedAsDictionary(
42254225
ConstraintKind matchKind,
42264226
SmallVectorImpl<RestrictionOrFix> &conversionsOrFixes,
42274227
ConstraintLocator *loc) {
4228+
if (auto *fix = TreatArrayLiteralAsDictionary::attempt(cs, dictType,
4229+
arrayType, loc)) {
4230+
// Ignore any attempts at promoting the value to an optional as even after
4231+
// stripping off all optionals above the underlying types don't match (array
4232+
// vs dictionary).
4233+
conversionsOrFixes.erase(
4234+
llvm::remove_if(conversionsOrFixes,
4235+
[&](RestrictionOrFix &E) {
4236+
if (auto restriction = E.getRestriction())
4237+
return *restriction ==
4238+
ConversionRestrictionKind::ValueToOptional;
4239+
return false;
4240+
}),
4241+
conversionsOrFixes.end());
42284242

4229-
if (!cs.isArrayType(arrayType))
4230-
return false;
4231-
4232-
// Determine the ArrayExpr from the locator.
4233-
auto *expr = getAsExpr(simplifyLocatorToAnchor(loc));
4234-
if (!expr)
4235-
return false;
4236-
4237-
if (auto *AE = dyn_cast<AssignExpr>(expr))
4238-
expr = AE->getSrc();
4239-
4240-
auto *arrayExpr = dyn_cast<ArrayExpr>(expr);
4241-
if (!arrayExpr)
4242-
return false;
4243-
4244-
// This fix currently only handles empty and single-element arrays:
4245-
// [] => [:] and [1] => [1:_]
4246-
if (arrayExpr->getNumElements() > 1)
4247-
return false;
4248-
4249-
// This fix only applies if the array is used as a dictionary.
4250-
auto unwrappedDict = dictType->lookThroughAllOptionalTypes();
4251-
if (unwrappedDict->isTypeVariableOrMember())
4252-
return false;
4253-
4254-
if (!TypeChecker::conformsToKnownProtocol(
4255-
unwrappedDict,
4256-
KnownProtocolKind::ExpressibleByDictionaryLiteral,
4257-
cs.DC->getParentModule()))
4258-
return false;
4259-
4260-
// Ignore any attempts at promoting the value to an optional as even after
4261-
// stripping off all optionals above the underlying types don't match (array
4262-
// vs dictionary).
4263-
conversionsOrFixes.erase(llvm::remove_if(conversionsOrFixes,
4264-
[&](RestrictionOrFix &E) {
4265-
if (auto restriction = E.getRestriction())
4266-
return *restriction == ConversionRestrictionKind::ValueToOptional;
4267-
return false;
4268-
}), conversionsOrFixes.end());
4269-
4270-
auto argLoc = cs.getConstraintLocator(arrayExpr);
4271-
conversionsOrFixes.push_back(TreatArrayLiteralAsDictionary::create(
4272-
cs, dictType, arrayType, argLoc));
4273-
return true;
4243+
conversionsOrFixes.push_back(fix);
4244+
return true;
4245+
}
4246+
return false;
42744247
}
42754248

42764249
/// Let's check whether this is an out-of-order argument in binary
@@ -5563,6 +5536,17 @@ bool ConstraintSystem::repairFailures(
55635536
if (hasConversionOrRestriction(ConversionRestrictionKind::DeepEquality))
55645537
break;
55655538

5539+
// We already have a fix for trying to initialize/assign an array literal
5540+
// to a dictionary type. In this case elements mismatch only add extra
5541+
// verbosity to the diagnostic. So let's skip the fix and only increase
5542+
// the score to focus on suggesting using dictionary literal instead.
5543+
path.pop_back();
5544+
auto loc = getConstraintLocator(anchor, path);
5545+
if (hasFixFor(loc, FixKind::TreatArrayLiteralAsDictionary)) {
5546+
increaseScore(SK_Fix);
5547+
return true;
5548+
}
5549+
55665550
conversionsOrFixes.push_back(CollectionElementContextualMismatch::create(
55675551
*this, lhs, rhs, getConstraintLocator(locator)));
55685552
break;

test/Constraints/dictionary_literal.swift

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,28 +85,24 @@ var _: [Float: Int] = [1] // expected-error {{dictionary of type '[Float : Int]'
8585

8686
var _: [Int: Int] = ["foo"] // expected-error {{dictionary of type '[Int : Int]' cannot be initialized with array literal}}
8787
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{27-27=: <#value#>}}
88-
// expected-error@-2 {{cannot convert value of type 'String' to expected dictionary key type 'Int'}}
8988

9089
var _ = useDictStringInt(["Key"]) // expected-error {{dictionary of type 'DictStringInt' cannot be used with array literal}}
9190
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{32-32=: <#value#>}}
9291

9392
var _ = useDictStringInt([4]) // expected-error {{dictionary of type 'DictStringInt' cannot be used with array literal}}
9493
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{28-28=: <#value#>}}
95-
// expected-error@-2 {{cannot convert value of type 'Int' to expected dictionary key type 'DictStringInt.Key' (aka 'String')}}
9694

9795
var _: [[Int: Int]] = [[5]] // expected-error {{dictionary of type '[Int : Int]' cannot be used with array literal}}
9896
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{26-26=: <#value#>}}
9997

10098
var _: [[Int: Int]] = [["bar"]] // expected-error {{dictionary of type '[Int : Int]' cannot be used with array literal}}
10199
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{30-30=: <#value#>}}
102-
// expected-error@-2 {{cannot convert value of type 'String' to expected dictionary key type 'Int'}}
103100

104101
assignDict = [1] // expected-error {{dictionary of type '[Int : Int]' cannot be used with array literal}}
105102
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{16-16=: <#value#>}}
106103

107104
assignDict = [""] // expected-error {{dictionary of type '[Int : Int]' cannot be used with array literal}}
108105
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{17-17=: <#value#>}}
109-
// expected-error@-2 {{cannot convert value of type 'String' to expected dictionary key type 'Int'}}
110106

111107
func arrayLiteralDictionaryMismatch<T>(a: inout T) where T: ExpressibleByDictionaryLiteral, T.Key == Int, T.Value == Int {
112108
a = [] // expected-error {{use [:] to get an empty dictionary literal}} {{8-8=:}}
@@ -116,7 +112,6 @@ func arrayLiteralDictionaryMismatch<T>(a: inout T) where T: ExpressibleByDiction
116112

117113
a = [""] // expected-error {{dictionary of type 'T' cannot be used with array literal}}
118114
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{10-10=: <#value#>}}
119-
// expected-error@-2 {{cannot convert value of type 'String' to expected dictionary key type 'Int'}}
120115
}
121116

122117

@@ -167,3 +162,33 @@ func rdar32330004_2() -> [String: Any] {
167162
// expected-error@-1 {{dictionary of type '[String : Any]' cannot be used with array literal}}
168163
// expected-note@-2 {{did you mean to use a dictionary literal instead?}} {{14-15=:}} {{24-25=:}} {{34-35=:}} {{46-47=:}}
169164
}
165+
166+
// https://github.com/apple/swift/issues/59215
167+
class S59215 {
168+
var m: [String: [String: String]] = [:]
169+
init() {
170+
m["a"] = ["", 1] //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
171+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{17-18=:}}
172+
m["a"] = [1 , ""] //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
173+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{17-18=:}}
174+
m["a"] = ["", ""] //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
175+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{17-18=:}}
176+
m["a"] = [1 , 1] //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
177+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{17-18=:}}
178+
}
179+
}
180+
181+
func f59215(_ a: [String: String]) {}
182+
f59215(["", 1]) //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
183+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{11-12=:}}
184+
f59215([1 , ""]) //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
185+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{11-12=:}}
186+
f59215([1 , 1]) //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
187+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{11-12=:}}
188+
f59215(["", ""]) //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
189+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{11-12=:}}
190+
191+
f59215(["", "", "", ""]) //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
192+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{11-12=:}} {{19-20=:}}
193+
f59215(["", "", "", ""]) //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
194+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{11-12=:}} {{19-20=:}}

test/Constraints/optional.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ func rdar85166519() {
506506

507507
var _: [Int: AnyObject] = [ // expected-error {{dictionary of type '[Int : AnyObject]' cannot be initialized with array literal}}
508508
// expected-note@-1 {{did you mean to use a dictionary literal instead?}}
509-
v?.addingReportingOverflow(0) // expected-error {{cannot convert value of type '(partialValue: Int, overflow: Bool)?' to expected dictionary key type 'Int'}}
509+
v?.addingReportingOverflow(0)
510510
]
511511
}
512512

0 commit comments

Comments
 (0)