Skip to content

Commit 2875aa8

Browse files
committed
[Diagnostics] Diagnose an attempt to init/use dictionary with array literal in CSGen
It's much easier to detect that contextual type is a dictionary but expression is an array literal while generating constraints.
1 parent 96441c1 commit 2875aa8

File tree

4 files changed

+52
-88
lines changed

4 files changed

+52
-88
lines changed

lib/Sema/CSDiagnostics.cpp

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,9 +1891,6 @@ bool ContextualFailure::diagnoseAsError() {
18911891
if (diagnoseMissingFunctionCall())
18921892
return true;
18931893

1894-
if (diagnoseConversionToDictionary())
1895-
return true;
1896-
18971894
// Special case of some common conversions involving Swift.String
18981895
// indexes, catching cases where people attempt to index them with an integer.
18991896
if (isIntegerToStringIndexConversion()) {
@@ -2403,61 +2400,6 @@ bool ContextualFailure::diagnoseConversionToBool() const {
24032400
return false;
24042401
}
24052402

2406-
bool ContextualFailure::isInvalidDictionaryConversion(
2407-
ConstraintSystem &cs, Expr *anchor, Type contextualType) {
2408-
auto *arrayExpr = dyn_cast<ArrayExpr>(anchor);
2409-
if (!arrayExpr)
2410-
return false;
2411-
2412-
auto type = contextualType->lookThroughAllOptionalTypes();
2413-
if (!conformsToKnownProtocol(
2414-
cs, type, KnownProtocolKind::ExpressibleByDictionaryLiteral))
2415-
return false;
2416-
2417-
return (arrayExpr->getNumElements() & 1) == 0;
2418-
}
2419-
2420-
bool ContextualFailure::diagnoseConversionToDictionary() const {
2421-
auto &cs = getConstraintSystem();
2422-
auto toType = getToType()->lookThroughAllOptionalTypes();
2423-
2424-
if (!isInvalidDictionaryConversion(cs, getAnchor(), toType))
2425-
return false;
2426-
2427-
auto *arrayExpr = cast<ArrayExpr>(getAnchor());
2428-
2429-
// If the contextual type conforms to ExpressibleByDictionaryLiteral and
2430-
// this is an empty array, then they meant "[:]".
2431-
auto numElements = arrayExpr->getNumElements();
2432-
if (numElements == 0) {
2433-
emitDiagnostic(arrayExpr->getStartLoc(),
2434-
diag::should_use_empty_dictionary_literal)
2435-
.fixItInsert(arrayExpr->getEndLoc(), ":");
2436-
return true;
2437-
}
2438-
2439-
// If the contextual type conforms to ExpressibleByDictionaryLiteral, then
2440-
// they wrote "x = [1,2]" but probably meant "x = [1:2]".
2441-
bool isIniting = getContextualTypePurpose() == CTP_Initialization;
2442-
emitDiagnostic(arrayExpr->getStartLoc(), diag::should_use_dictionary_literal,
2443-
toType, isIniting);
2444-
2445-
auto diagnostic =
2446-
emitDiagnostic(arrayExpr->getStartLoc(), diag::meant_dictionary_lit);
2447-
2448-
// Change every other comma into a colon, only if the number
2449-
// of commas present matches the number of elements, because
2450-
// otherwise it might a structural problem with the expression
2451-
// e.g. ["a""b": 1].
2452-
const auto commaLocs = arrayExpr->getCommaLocs();
2453-
if (commaLocs.size() == numElements - 1) {
2454-
for (unsigned i = 0, e = numElements / 2; i != e; ++i)
2455-
diagnostic.fixItReplace(commaLocs[i * 2], ":");
2456-
}
2457-
2458-
return true;
2459-
}
2460-
24612403
bool ContextualFailure::diagnoseThrowsTypeMismatch() const {
24622404
// If this is conversion failure due to a return statement with an argument
24632405
// that cannot be coerced to the result type of the function, emit a
@@ -4857,24 +4799,6 @@ bool CollectionElementContextualFailure::diagnoseAsError() {
48574799
auto *anchor = getAnchor();
48584800
auto *locator = getLocator();
48594801

4860-
// Check whether this is situation like `let _: [String: Int] = ["A", 0]`
4861-
// which attempts to convert an array into a dictionary. We have a tailored
4862-
// contextual diagnostic for that, so no need to diagnose element mismatches
4863-
// as well.
4864-
auto &cs = getConstraintSystem();
4865-
auto *rawAnchor = getRawAnchor();
4866-
if (llvm::any_of(cs.getFixes(), [&](const ConstraintFix *fix) -> bool {
4867-
auto *locator = fix->getLocator();
4868-
if (!(fix->getKind() == FixKind::ContextualMismatch &&
4869-
locator->getAnchor() == rawAnchor))
4870-
return false;
4871-
4872-
auto *mismatch = static_cast<const ContextualMismatch *>(fix);
4873-
return isInvalidDictionaryConversion(cs, rawAnchor,
4874-
mismatch->getToType());
4875-
}))
4876-
return false;
4877-
48784802
auto eltType = getFromType();
48794803
auto contextualType = getToType();
48804804

lib/Sema/CSDiagnostics.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -566,10 +566,6 @@ class ContextualFailure : public FailureDiagnostic {
566566
/// Produce a specialized diagnostic if this is an invalid conversion to Bool.
567567
bool diagnoseConversionToBool() const;
568568

569-
/// Produce a specialized diagnostic if this is an attempt to initialize
570-
/// or convert an array literal to a dictionary e.g. `let _: [String: Int] = ["A", 0]`
571-
bool diagnoseConversionToDictionary() const;
572-
573569
/// Produce a specialized diagnostic if this is an attempt to throw
574570
/// something with doesn't conform to `Error`.
575571
bool diagnoseThrowsTypeMismatch() const;
@@ -625,11 +621,6 @@ class ContextualFailure : public FailureDiagnostic {
625621
/// protocol
626622
bool tryProtocolConformanceFixIt(InFlightDiagnostic &diagnostic) const;
627623

628-
/// Check whether this contextual failure represents an invalid
629-
/// conversion from array literal to dictionary.
630-
static bool isInvalidDictionaryConversion(ConstraintSystem &cs, Expr *anchor,
631-
Type contextualType);
632-
633624
private:
634625
Type resolve(Type rawType) const {
635626
return resolveType(rawType)->getWithoutSpecifierType();

lib/Sema/CSGen.cpp

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1852,7 +1852,56 @@ namespace {
18521852

18531853
return contextualArrayType;
18541854
}
1855-
1855+
1856+
// Produce a specialized diagnostic if this is an attempt to initialize
1857+
// or convert an array literal to a dictionary e.g.
1858+
// `let _: [String: Int] = ["A", 0]`
1859+
auto isDictionaryContextualType = [&](Type contextualType) -> bool {
1860+
if (!contextualType)
1861+
return false;
1862+
1863+
auto type = contextualType->lookThroughAllOptionalTypes();
1864+
if (conformsToKnownProtocol(
1865+
CS, type, KnownProtocolKind::ExpressibleByArrayLiteral))
1866+
return false;
1867+
1868+
return conformsToKnownProtocol(
1869+
CS, type, KnownProtocolKind::ExpressibleByDictionaryLiteral);
1870+
};
1871+
1872+
if (isDictionaryContextualType(contextualType)) {
1873+
auto &DE = CS.getASTContext().Diags;
1874+
auto numElements = expr->getNumElements();
1875+
1876+
if (numElements == 0) {
1877+
DE.diagnose(expr->getStartLoc(),
1878+
diag::should_use_empty_dictionary_literal)
1879+
.fixItInsert(expr->getEndLoc(), ":");
1880+
return nullptr;
1881+
}
1882+
1883+
bool isIniting =
1884+
CS.getContextualTypePurpose(expr) == CTP_Initialization;
1885+
DE.diagnose(expr->getStartLoc(), diag::should_use_dictionary_literal,
1886+
contextualType->lookThroughAllOptionalTypes(), isIniting);
1887+
1888+
auto diagnostic =
1889+
DE.diagnose(expr->getStartLoc(), diag::meant_dictionary_lit);
1890+
1891+
// If there is an even number of elements in the array, let's produce
1892+
// a fix-it which suggests to replace "," with ":" to form a dictionary
1893+
// literal.
1894+
if ((numElements & 1) == 0) {
1895+
const auto commaLocs = expr->getCommaLocs();
1896+
if (commaLocs.size() == numElements - 1) {
1897+
for (unsigned i = 0, e = numElements / 2; i != e; ++i)
1898+
diagnostic.fixItReplace(commaLocs[i * 2], ":");
1899+
}
1900+
}
1901+
1902+
return nullptr;
1903+
}
1904+
18561905
auto arrayTy = CS.createTypeVariable(locator,
18571906
TVO_PrefersSubtypeBinding |
18581907
TVO_CanBindToNoEscape);

test/Constraints/dictionary_literal.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ var _: MyDictionary<String, Int>? = ["foo", 1] // expected-error {{dictionary o
6363
var _: MyDictionary<String, Int>? = ["foo", 1, "bar", 42] // expected-error {{dictionary of type 'MyDictionary<String, Int>' cannot be initialized with array literal}}
6464
// expected-note @-1 {{did you mean to use a dictionary literal instead?}} {{43-44=:}} {{53-54=:}}
6565

66-
var _: MyDictionary<String, Int>? = ["foo", 1.0, 2] // expected-error {{cannot convert value of type '[Double]' to specified type 'MyDictionary<String, Int>?'}}
67-
// expected-error@-1 {{cannot convert value of type 'String' to expected element type 'Double'}}
66+
var _: MyDictionary<String, Int>? = ["foo", 1.0, 2] // expected-error {{dictionary of type 'MyDictionary<String, Int>' cannot be initialized with array literal}}
67+
// expected-note@-1 {{did you mean to use a dictionary literal instead?}} {{none}}
6868

6969
var _: MyDictionary<String, Int>? = ["foo" : 1.0] // expected-error {{cannot convert value of type 'Double' to expected dictionary value type 'MyDictionary<String, Int>.Value' (aka 'Int')}}
7070

0 commit comments

Comments
 (0)