[CodeCompletion][Sema] Add fix to treat empty or single-element array literals as dictionaries when used as such

In the single-element case, it is treated as the dictionary key.

func takesDict(_ x: [Int: String]) {}
takesDict([]) // diagnose with fixit to add missing ':'
takesDict([1]) // diagnose with fixit to add missing ': <#value#>'
takesDict([foo.<complete>]) // prioritise Int members in completion results -
                            // the user just hasn't written the value yet.

The above previously failed with a generic mismatch error in normal type
checking (due to the literal being parsed as an array literal) and code
completion could not pick up the expected type from the context.
This commit is contained in:
Nathan Hawes
2020-10-30 12:11:53 -07:00
parent cdfff19832
commit edbbefce91
8 changed files with 307 additions and 33 deletions

View File

@@ -3206,6 +3206,58 @@ repairViaOptionalUnwrap(ConstraintSystem &cs, Type fromType, Type toType,
return true;
}
static bool repairArrayLiteralUsedAsDictionary(
ConstraintSystem &cs, Type arrayType, Type dictType,
ConstraintKind matchKind,
SmallVectorImpl<RestrictionOrFix> &conversionsOrFixes,
ConstraintLocator *loc) {
if (!cs.isArrayType(arrayType))
return false;
// Determine the ArrayExpr from the locator.
auto *expr = getAsExpr(simplifyLocatorToAnchor(loc));
if (!expr)
return false;
if (auto *AE = dyn_cast<AssignExpr>(expr))
expr = AE->getSrc();
auto *arrayExpr = dyn_cast<ArrayExpr>(expr);
if (!arrayExpr)
return false;
// This fix currently only handles empty and single-element arrays:
// [] => [:] and [1] => [1:_]
if (arrayExpr->getNumElements() > 1)
return false;
// This fix only applies if the array is used as a dictionary.
auto unwrappedDict = dictType->lookThroughAllOptionalTypes();
if (unwrappedDict->isTypeVariableOrMember())
return false;
if (!conformsToKnownProtocol(
cs.DC, unwrappedDict,
KnownProtocolKind::ExpressibleByDictionaryLiteral))
return false;
// Ignore any attempts at promoting the value to an optional as even after
// stripping off all optionals above the underlying types don't match (array
// vs dictionary).
conversionsOrFixes.erase(llvm::remove_if(conversionsOrFixes,
[&](RestrictionOrFix &E) {
if (auto restriction = E.getRestriction())
return *restriction == ConversionRestrictionKind::ValueToOptional;
return false;
}), conversionsOrFixes.end());
auto argLoc = cs.getConstraintLocator(arrayExpr);
conversionsOrFixes.push_back(TreatArrayLiteralAsDictionary::create(
cs, dictType, arrayType, argLoc));
return true;
}
/// Let's check whether this is an out-of-order argument in binary
/// operator/function with concrete type parameters e.g.
/// `func ^^(x: Int, y: String)` called as `"" ^^ 42` instead of
@@ -3481,6 +3533,11 @@ bool ConstraintSystem::repairFailures(
});
};
if (repairArrayLiteralUsedAsDictionary(*this, lhs, rhs, matchKind,
conversionsOrFixes,
getConstraintLocator(locator)))
return true;
if (path.empty()) {
if (!anchor)
return false;
@@ -10185,6 +10242,44 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
return recordFix(fix, impact) ? SolutionKind::Error : SolutionKind::Solved;
}
case FixKind::TreatArrayLiteralAsDictionary: {
ArrayExpr *AE = getAsExpr<ArrayExpr>(fix->getAnchor());
assert(AE);
// If the array was empty, there's nothing to do.
if (AE->getNumElements() == 0)
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
// For arrays with a single element, match the element type to the
// dictionary's key type.
SmallVector<Type, 2> optionals;
auto dictTy = type2->lookThroughAllOptionalTypes(optionals);
// If the fix is worse than the best solution, there's no point continuing.
if (recordFix(fix, optionals.size() + 1))
return SolutionKind::Error;
// Extract the dictionary key type.
ProtocolDecl *dictionaryProto =
Context.getProtocol(KnownProtocolKind::ExpressibleByDictionaryLiteral);
auto keyAssocTy = dictionaryProto->getAssociatedType(Context.Id_Key);
auto valueBaseTy = createTypeVariable(getConstraintLocator(locator),
TVO_CanBindToLValue |
TVO_CanBindToNoEscape |
TVO_CanBindToHole);
assignFixedType(valueBaseTy, dictTy);
auto dictionaryKeyTy = DependentMemberType::get(valueBaseTy, keyAssocTy);
// Extract the array element type.
auto elemTy = isArrayType(type1);
ConstraintLocator *elemLoc = getConstraintLocator(AE->getElement(0));
ConstraintKind kind = isDictionaryType(dictTy)
? ConstraintKind::Conversion
: ConstraintKind::Equal;
return matchTypes(*elemTy, dictionaryKeyTy, kind, subflags, elemLoc);
}
case FixKind::ContextualMismatch: {
auto impact = 1;