Skip to content

Commit c9f1004

Browse files
Merge pull request #59277 from LucianoPAlmeida/dictionary-array-literals
[Sema] Allow TreatArrayLiteralAsDictionary fix to handle literals with more than one element
2 parents 1e8f306 + 42cf1b7 commit c9f1004

File tree

7 files changed

+121
-66
lines changed

7 files changed

+121
-66
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: 37 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4226,52 +4226,27 @@ static bool repairArrayLiteralUsedAsDictionary(
42264226
ConstraintKind matchKind,
42274227
SmallVectorImpl<RestrictionOrFix> &conversionsOrFixes,
42284228
ConstraintLocator *loc) {
4229+
if (auto *fix = TreatArrayLiteralAsDictionary::attempt(cs, dictType,
4230+
arrayType, loc)) {
4231+
// Ignore any attempts at promoting the value to an optional as even after
4232+
// stripping off all optionals above the underlying types won't match (array
4233+
// vs dictionary).
4234+
conversionsOrFixes.erase(
4235+
llvm::remove_if(conversionsOrFixes,
4236+
[&](RestrictionOrFix &E) {
4237+
if (auto restriction = E.getRestriction())
4238+
return *restriction == ConversionRestrictionKind::
4239+
ValueToOptional ||
4240+
*restriction == ConversionRestrictionKind::
4241+
OptionalToOptional;
4242+
return false;
4243+
}),
4244+
conversionsOrFixes.end());
42294245

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

42774252
/// Let's check whether this is an out-of-order argument in binary
@@ -4730,6 +4705,12 @@ bool ConstraintSystem::repairFailures(
47304705
return true;
47314706
}
47324707

4708+
// If we are trying to assign e.g. `Array<Int>` to `Array<Float>` let's
4709+
// give solver a chance to determine which generic parameters are
4710+
// mismatched and produce a fix for that.
4711+
if (hasConversionOrRestriction(ConversionRestrictionKind::DeepEquality))
4712+
return false;
4713+
47334714
// An attempt to assign `Int?` to `String?`.
47344715
if (hasConversionOrRestriction(
47354716
ConversionRestrictionKind::OptionalToOptional)) {
@@ -4738,12 +4719,6 @@ bool ConstraintSystem::repairFailures(
47384719
return true;
47394720
}
47404721

4741-
// If we are trying to assign e.g. `Array<Int>` to `Array<Float>` let's
4742-
// give solver a chance to determine which generic parameters are
4743-
// mismatched and produce a fix for that.
4744-
if (hasConversionOrRestriction(ConversionRestrictionKind::DeepEquality))
4745-
return false;
4746-
47474722
// If the situation has to do with protocol composition types and
47484723
// destination doesn't have one of the conformances e.g. source is
47494724
// `X & Y` but destination is only `Y` or vice versa, there is a
@@ -5577,6 +5552,17 @@ bool ConstraintSystem::repairFailures(
55775552
if (hasConversionOrRestriction(ConversionRestrictionKind::DeepEquality))
55785553
break;
55795554

5555+
// We already have a fix for trying to initialize/assign an array literal
5556+
// to a dictionary type. In this case elements mismatch only add extra
5557+
// verbosity to the diagnostic. So let's skip the fix and only increase
5558+
// the score to focus on suggesting using dictionary literal instead.
5559+
path.pop_back();
5560+
auto loc = getConstraintLocator(anchor, path);
5561+
if (hasFixFor(loc, FixKind::TreatArrayLiteralAsDictionary)) {
5562+
increaseScore(SK_Fix);
5563+
return true;
5564+
}
5565+
55805566
conversionsOrFixes.push_back(CollectionElementContextualMismatch::create(
55815567
*this, lhs, rhs, getConstraintLocator(locator)));
55825568
break;

test/Constraints/closures.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ func rdar21078316() {
371371
var foo : [String : String]?
372372
var bar : [(String, String)]?
373373
bar = foo.map { ($0, $1) } // expected-error {{contextual closure type '([String : String]) throws -> [(String, String)]' expects 1 argument, but 2 were used in closure body}}
374+
// expected-error@-1{{cannot convert value of type '(Dictionary<String, String>, _)' to closure result type '[(String, String)]'}}
374375
}
375376

376377

test/Constraints/dictionary_literal.swift

Lines changed: 32 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,35 @@ 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+
m["a"] = Optional(["", ""]) //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
179+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{26-27=:}}
180+
}
181+
}
182+
183+
func f59215(_ a: [String: String]) {}
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 , ""]) //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([1 , 1]) //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+
f59215(["", ""]) //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
191+
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{11-12=:}}
192+
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=:}}
195+
f59215(["", "", "", ""]) //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
196+
// 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)