Skip to content

Commit 1105531

Browse files
author
Nathan Hawes
authored
Merge pull request #34521 from nathawes/handle-dicts-parsed-as-arrays-for-completion
[CodeCompletion][Sema] Add fix to treat empty or single-element array literals as dictionaries when used as such
2 parents 891e115 + edbbefc commit 1105531

File tree

8 files changed

+306
-32
lines changed

8 files changed

+306
-32
lines changed

include/swift/Sema/CSFix.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ enum class FixKind : uint8_t {
285285
/// Allow expressions to reference invalid declarations by turning
286286
/// them into holes.
287287
AllowRefToInvalidDecl,
288+
289+
/// Treat empty and single-element array literals as if they were incomplete
290+
/// dictionary literals when used as such.
291+
TreatArrayLiteralAsDictionary,
288292
};
289293

290294
class ConstraintFix {
@@ -551,6 +555,25 @@ class ContextualMismatch : public ConstraintFix {
551555
ConstraintLocator *locator);
552556
};
553557

558+
class TreatArrayLiteralAsDictionary final : public ContextualMismatch {
559+
TreatArrayLiteralAsDictionary(ConstraintSystem &cs, Type dictionaryTy,
560+
Type arrayTy, ConstraintLocator *locator)
561+
: ContextualMismatch(cs, FixKind::TreatArrayLiteralAsDictionary,
562+
dictionaryTy, arrayTy, locator) {
563+
}
564+
565+
public:
566+
std::string getName() const override {
567+
return "treat array literal as dictionary";
568+
}
569+
570+
bool diagnose(const Solution &solution, bool asNote = false) const override;
571+
572+
static TreatArrayLiteralAsDictionary *create(ConstraintSystem &cs,
573+
Type dictionaryTy, Type arrayTy,
574+
ConstraintLocator *loc);
575+
};
576+
554577
/// Mark function type as explicitly '@escaping'.
555578
class MarkExplicitlyEscaping final : public ContextualMismatch {
556579
MarkExplicitlyEscaping(ConstraintSystem &cs, Type lhs, Type rhs,

lib/Sema/CSDiagnostics.cpp

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,27 @@ bool LabelingFailure::diagnoseAsNote() {
841841
return false;
842842
}
843843

844+
bool ArrayLiteralToDictionaryConversionFailure::diagnoseAsError() {
845+
ArrayExpr *AE = getAsExpr<ArrayExpr>(getAnchor());
846+
assert(AE);
847+
848+
if (AE->getNumElements() == 0) {
849+
emitDiagnostic(diag::should_use_empty_dictionary_literal)
850+
.fixItInsertAfter(getLoc(), ":");
851+
return true;
852+
}
853+
854+
auto CTP = getConstraintSystem().getContextualTypePurpose(AE);
855+
emitDiagnostic(diag::should_use_dictionary_literal,
856+
getToType()->lookThroughAllOptionalTypes(),
857+
CTP == CTP_Initialization);
858+
859+
auto diagnostic = emitDiagnostic(diag::meant_dictionary_lit);
860+
if (AE->getNumElements() == 1)
861+
diagnostic.fixItInsertAfter(AE->getElement(0)->getEndLoc(), ": <#value#>");
862+
return true;
863+
}
864+
844865
bool NoEscapeFuncToTypeConversionFailure::diagnoseAsError() {
845866
if (diagnoseParameterUse())
846867
return true;
@@ -5129,16 +5150,27 @@ bool CollectionElementContextualFailure::diagnoseAsError() {
51295150
auto eltType = getFromType();
51305151
auto contextualType = getToType();
51315152

5153+
auto isFixedToDictionary = [&](ArrayExpr *anchor) {
5154+
return llvm::any_of(getSolution().Fixes, [&](ConstraintFix *fix) {
5155+
auto *fixAnchor = getAsExpr<ArrayExpr>(fix->getAnchor());
5156+
return fixAnchor && fixAnchor == anchor &&
5157+
fix->getKind() == FixKind::TreatArrayLiteralAsDictionary;
5158+
});
5159+
};
5160+
5161+
bool treatAsDictionary = false;
51325162
Optional<InFlightDiagnostic> diagnostic;
5133-
if (isExpr<ArrayExpr>(anchor)) {
5134-
if (diagnoseMergedLiteralElements())
5135-
return true;
5163+
if (auto *AE = getAsExpr<ArrayExpr>(anchor)) {
5164+
if (!(treatAsDictionary = isFixedToDictionary(AE))) {
5165+
if (diagnoseMergedLiteralElements())
5166+
return true;
51365167

5137-
diagnostic.emplace(emitDiagnostic(diag::cannot_convert_array_element,
5138-
eltType, contextualType));
5168+
diagnostic.emplace(emitDiagnostic(diag::cannot_convert_array_element,
5169+
eltType, contextualType));
5170+
}
51395171
}
51405172

5141-
if (isExpr<DictionaryExpr>(anchor)) {
5173+
if (treatAsDictionary || isExpr<DictionaryExpr>(anchor)) {
51425174
auto eltLoc = locator->castLastElementTo<LocatorPathElt::TupleElement>();
51435175
switch (eltLoc.getIndex()) {
51445176
case 0: // key

lib/Sema/CSDiagnostics.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,18 @@ class ContextualFailure : public FailureDiagnostic {
690690
getDiagnosticFor(ContextualTypePurpose context, Type contextualType);
691691
};
692692

693+
/// Diagnose errors related to using an array literal where a
694+
/// dictionary is expected.
695+
class ArrayLiteralToDictionaryConversionFailure final : public ContextualFailure {
696+
public:
697+
ArrayLiteralToDictionaryConversionFailure(const Solution &solution,
698+
Type arrayTy, Type dictTy,
699+
ConstraintLocator *locator)
700+
: ContextualFailure(solution, arrayTy, dictTy, locator) {}
701+
702+
bool diagnoseAsError() override;
703+
};
704+
693705
/// Diagnose errors related to converting function type which
694706
/// isn't explicitly '@escaping' to some other type.
695707
class NoEscapeFuncToTypeConversionFailure final : public ContextualFailure {

lib/Sema/CSFix.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,23 @@ CoerceToCheckedCast *CoerceToCheckedCast::attempt(ConstraintSystem &cs,
162162
CoerceToCheckedCast(cs, fromType, toType, locator);
163163
}
164164

165+
bool TreatArrayLiteralAsDictionary::diagnose(const Solution &solution,
166+
bool asNote) const {
167+
ArrayLiteralToDictionaryConversionFailure failure(solution,
168+
getToType(), getFromType(),
169+
getLocator());
170+
return failure.diagnose(asNote);
171+
};
172+
173+
TreatArrayLiteralAsDictionary *
174+
TreatArrayLiteralAsDictionary::create(ConstraintSystem &cs,
175+
Type dictionaryTy, Type arrayTy,
176+
ConstraintLocator *locator) {
177+
assert(getAsExpr<ArrayExpr>(locator->getAnchor())->getNumElements() <= 1);
178+
return new (cs.getAllocator())
179+
TreatArrayLiteralAsDictionary(cs, dictionaryTy, arrayTy, locator);
180+
};
181+
165182
bool MarkExplicitlyEscaping::diagnose(const Solution &solution,
166183
bool asNote) const {
167184
NoEscapeFuncToTypeConversionFailure failure(solution, getFromType(),

lib/Sema/CSGen.cpp

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1744,33 +1744,31 @@ namespace {
17441744
auto &DE = CS.getASTContext().Diags;
17451745
auto numElements = expr->getNumElements();
17461746

1747-
if (numElements == 0) {
1748-
DE.diagnose(expr->getStartLoc(),
1749-
diag::should_use_empty_dictionary_literal)
1750-
.fixItInsert(expr->getEndLoc(), ":");
1751-
return nullptr;
1752-
}
1753-
1754-
bool isIniting =
1755-
CS.getContextualTypePurpose(expr) == CTP_Initialization;
1756-
DE.diagnose(expr->getStartLoc(), diag::should_use_dictionary_literal,
1757-
contextualType->lookThroughAllOptionalTypes(), isIniting);
1758-
1759-
auto diagnostic =
1760-
DE.diagnose(expr->getStartLoc(), diag::meant_dictionary_lit);
1761-
1762-
// If there is an even number of elements in the array, let's produce
1763-
// a fix-it which suggests to replace "," with ":" to form a dictionary
1764-
// literal.
1765-
if ((numElements & 1) == 0) {
1766-
const auto commaLocs = expr->getCommaLocs();
1767-
if (commaLocs.size() == numElements - 1) {
1768-
for (unsigned i = 0, e = numElements / 2; i != e; ++i)
1769-
diagnostic.fixItReplace(commaLocs[i * 2], ":");
1747+
// Empty and single element array literals with dictionary contextual
1748+
// types are fixed during solving, so continue as normal in those
1749+
// cases.
1750+
if (numElements > 1) {
1751+
bool isIniting =
1752+
CS.getContextualTypePurpose(expr) == CTP_Initialization;
1753+
DE.diagnose(expr->getStartLoc(), diag::should_use_dictionary_literal,
1754+
contextualType->lookThroughAllOptionalTypes(), isIniting);
1755+
1756+
auto diagnostic =
1757+
DE.diagnose(expr->getStartLoc(), diag::meant_dictionary_lit);
1758+
1759+
// If there is an even number of elements in the array, let's produce
1760+
// a fix-it which suggests to replace "," with ":" to form a dictionary
1761+
// literal.
1762+
if ((numElements & 1) == 0) {
1763+
const auto commaLocs = expr->getCommaLocs();
1764+
if (commaLocs.size() == numElements - 1) {
1765+
for (unsigned i = 0, e = numElements / 2; i != e; ++i)
1766+
diagnostic.fixItReplace(commaLocs[i * 2], ":");
1767+
}
17701768
}
1771-
}
17721769

1773-
return nullptr;
1770+
return nullptr;
1771+
}
17741772
}
17751773

17761774
auto arrayTy = CS.createTypeVariable(locator,

lib/Sema/CSSimplify.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3206,6 +3206,58 @@ repairViaOptionalUnwrap(ConstraintSystem &cs, Type fromType, Type toType,
32063206
return true;
32073207
}
32083208

3209+
static bool repairArrayLiteralUsedAsDictionary(
3210+
ConstraintSystem &cs, Type arrayType, Type dictType,
3211+
ConstraintKind matchKind,
3212+
SmallVectorImpl<RestrictionOrFix> &conversionsOrFixes,
3213+
ConstraintLocator *loc) {
3214+
3215+
if (!cs.isArrayType(arrayType))
3216+
return false;
3217+
3218+
// Determine the ArrayExpr from the locator.
3219+
auto *expr = getAsExpr(simplifyLocatorToAnchor(loc));
3220+
if (!expr)
3221+
return false;
3222+
3223+
if (auto *AE = dyn_cast<AssignExpr>(expr))
3224+
expr = AE->getSrc();
3225+
3226+
auto *arrayExpr = dyn_cast<ArrayExpr>(expr);
3227+
if (!arrayExpr)
3228+
return false;
3229+
3230+
// This fix currently only handles empty and single-element arrays:
3231+
// [] => [:] and [1] => [1:_]
3232+
if (arrayExpr->getNumElements() > 1)
3233+
return false;
3234+
3235+
// This fix only applies if the array is used as a dictionary.
3236+
auto unwrappedDict = dictType->lookThroughAllOptionalTypes();
3237+
if (unwrappedDict->isTypeVariableOrMember())
3238+
return false;
3239+
3240+
if (!conformsToKnownProtocol(
3241+
cs.DC, unwrappedDict,
3242+
KnownProtocolKind::ExpressibleByDictionaryLiteral))
3243+
return false;
3244+
3245+
// Ignore any attempts at promoting the value to an optional as even after
3246+
// stripping off all optionals above the underlying types don't match (array
3247+
// vs dictionary).
3248+
conversionsOrFixes.erase(llvm::remove_if(conversionsOrFixes,
3249+
[&](RestrictionOrFix &E) {
3250+
if (auto restriction = E.getRestriction())
3251+
return *restriction == ConversionRestrictionKind::ValueToOptional;
3252+
return false;
3253+
}), conversionsOrFixes.end());
3254+
3255+
auto argLoc = cs.getConstraintLocator(arrayExpr);
3256+
conversionsOrFixes.push_back(TreatArrayLiteralAsDictionary::create(
3257+
cs, dictType, arrayType, argLoc));
3258+
return true;
3259+
}
3260+
32093261
/// Let's check whether this is an out-of-order argument in binary
32103262
/// operator/function with concrete type parameters e.g.
32113263
/// `func ^^(x: Int, y: String)` called as `"" ^^ 42` instead of
@@ -3481,6 +3533,11 @@ bool ConstraintSystem::repairFailures(
34813533
});
34823534
};
34833535

3536+
if (repairArrayLiteralUsedAsDictionary(*this, lhs, rhs, matchKind,
3537+
conversionsOrFixes,
3538+
getConstraintLocator(locator)))
3539+
return true;
3540+
34843541
if (path.empty()) {
34853542
if (!anchor)
34863543
return false;
@@ -10185,6 +10242,44 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
1018510242
return recordFix(fix, impact) ? SolutionKind::Error : SolutionKind::Solved;
1018610243
}
1018710244

10245+
case FixKind::TreatArrayLiteralAsDictionary: {
10246+
ArrayExpr *AE = getAsExpr<ArrayExpr>(fix->getAnchor());
10247+
assert(AE);
10248+
10249+
// If the array was empty, there's nothing to do.
10250+
if (AE->getNumElements() == 0)
10251+
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
10252+
10253+
// For arrays with a single element, match the element type to the
10254+
// dictionary's key type.
10255+
SmallVector<Type, 2> optionals;
10256+
auto dictTy = type2->lookThroughAllOptionalTypes(optionals);
10257+
10258+
// If the fix is worse than the best solution, there's no point continuing.
10259+
if (recordFix(fix, optionals.size() + 1))
10260+
return SolutionKind::Error;
10261+
10262+
// Extract the dictionary key type.
10263+
ProtocolDecl *dictionaryProto =
10264+
Context.getProtocol(KnownProtocolKind::ExpressibleByDictionaryLiteral);
10265+
auto keyAssocTy = dictionaryProto->getAssociatedType(Context.Id_Key);
10266+
auto valueBaseTy = createTypeVariable(getConstraintLocator(locator),
10267+
TVO_CanBindToLValue |
10268+
TVO_CanBindToNoEscape |
10269+
TVO_CanBindToHole);
10270+
assignFixedType(valueBaseTy, dictTy);
10271+
auto dictionaryKeyTy = DependentMemberType::get(valueBaseTy, keyAssocTy);
10272+
10273+
// Extract the array element type.
10274+
auto elemTy = isArrayType(type1);
10275+
10276+
ConstraintLocator *elemLoc = getConstraintLocator(AE->getElement(0));
10277+
ConstraintKind kind = isDictionaryType(dictTy)
10278+
? ConstraintKind::Conversion
10279+
: ConstraintKind::Equal;
10280+
return matchTypes(*elemTy, dictionaryKeyTy, kind, subflags, elemLoc);
10281+
}
10282+
1018810283
case FixKind::ContextualMismatch: {
1018910284
auto impact = 1;
1019010285

test/Constraints/dictionary_literal.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,53 @@ var _: MyDictionary<String, Int>? = ["foo" : 1.0] // expected-error {{cannot co
7171

7272
// <rdar://problem/24058895> QoI: Should handle [] in dictionary contexts better
7373
var _: [Int: Int] = [] // expected-error {{use [:] to get an empty dictionary literal}} {{22-22=:}}
74+
var _ = useDictStringInt([]) // expected-error {{use [:] to get an empty dictionary literal}} {{27-27=:}}
75+
var _: [[Int: Int]] = [[]] // expected-error {{use [:] to get an empty dictionary literal}} {{25-25=:}}
76+
var _: [[Int: Int]?] = [[]] // expected-error {{use [:] to get an empty dictionary literal}} {{26-26=:}}
77+
var assignDict = [1: 2]
78+
assignDict = [] // expected-error {{use [:] to get an empty dictionary literal}} {{15-15=:}}
79+
80+
var _: [Int: Int] = [1] // expected-error {{dictionary of type '[Int : Int]' cannot be initialized with array literal}}
81+
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{23-23=: <#value#>}}
82+
83+
var _: [Float: Int] = [1] // expected-error {{dictionary of type '[Float : Int]' cannot be initialized with array literal}}
84+
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{25-25=: <#value#>}}
85+
86+
var _: [Int: Int] = ["foo"] // expected-error {{dictionary of type '[Int : Int]' cannot be initialized with array literal}}
87+
// 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'}}
89+
90+
var _ = useDictStringInt(["Key"]) // expected-error {{dictionary of type 'DictStringInt' cannot be used with array literal}}
91+
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{32-32=: <#value#>}}
92+
93+
var _ = useDictStringInt([4]) // expected-error {{dictionary of type 'DictStringInt' cannot be used with array literal}}
94+
// 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')}}
96+
97+
var _: [[Int: Int]] = [[5]] // expected-error {{dictionary of type '[Int : Int]' cannot be used with array literal}}
98+
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{26-26=: <#value#>}}
99+
100+
var _: [[Int: Int]] = [["bar"]] // expected-error {{dictionary of type '[Int : Int]' cannot be used with array literal}}
101+
// 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'}}
103+
104+
assignDict = [1] // expected-error {{dictionary of type '[Int : Int]' cannot be used with array literal}}
105+
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{16-16=: <#value#>}}
106+
107+
assignDict = [""] // expected-error {{dictionary of type '[Int : Int]' cannot be used with array literal}}
108+
// 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'}}
110+
111+
func arrayLiteralDictionaryMismatch<T>(a: inout T) where T: ExpressibleByDictionaryLiteral, T.Key == Int, T.Value == Int {
112+
a = [] // expected-error {{use [:] to get an empty dictionary literal}} {{8-8=:}}
113+
114+
a = [1] // expected-error {{dictionary of type 'T' cannot be used with array literal}}
115+
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{9-9=: <#value#>}}
116+
117+
a = [""] // expected-error {{dictionary of type 'T' cannot be used with array literal}}
118+
// 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'}}
120+
}
74121

75122

76123
class A { }

0 commit comments

Comments
 (0)