Skip to content

[ConstraintSystem] Port tuple type mismatch diagnostics #28600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions docs/TypeChecker.rst
Original file line number Diff line number Diff line change
Expand Up @@ -981,9 +981,6 @@ to the new diagnostic framework, which is described in detail in this

The things in the queue yet to be ported are:

- ``visitTupleExpr``: Diagnostics related to label/type mismatches
associated with tuple conversions.

- Diagnostics related to member references: ``diagnoseMemberFailures``.
Most of the associated diagnostics have been ported and fixes are
located in ``ConstraintSystem::simplifyMemberConstraint``.
Expand Down
3 changes: 1 addition & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -955,9 +955,8 @@ ERROR(types_not_convertible_use_bool_value,none,
ERROR(tuple_types_not_convertible_nelts,none,
"%0 is not convertible to %1, "
"tuples have a different number of elements", (Type, Type))

ERROR(tuple_types_not_convertible,none,
"tuple type %0 is not convertible to tuple %1", (Type, Type))
"tuple type %0 is not convertible to tuple type %1", (Type, Type))

ERROR(invalid_force_unwrap,none,
"cannot force unwrap value of non-optional type %0", (Type))
Expand Down
55 changes: 1 addition & 54 deletions lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,7 @@ class FailureDiagnosis :public ASTVisitor<FailureDiagnosis, /*exprresult*/bool>{
bool visitExpr(Expr *E);
bool visitIdentityExpr(IdentityExpr *E);
bool visitTryExpr(TryExpr *E);
bool visitTupleExpr(TupleExpr *E);


bool visitUnresolvedMemberExpr(UnresolvedMemberExpr *E);
bool visitUnresolvedDotExpr(UnresolvedDotExpr *UDE);
bool visitArrayExpr(ArrayExpr *E);
Expand Down Expand Up @@ -2936,58 +2935,6 @@ bool FailureDiagnosis::visitUnresolvedDotExpr(UnresolvedDotExpr *UDE) {
locator);
}

/// A TupleExpr propagate contextual type information down to its children and
/// can be erroneous when there is a label mismatch etc.
bool FailureDiagnosis::visitTupleExpr(TupleExpr *TE) {
// If we know the requested argType to use, use computeTupleShuffle to produce
// the shuffle of input arguments to destination values. It requires a
// TupleType to compute the mapping from argExpr. Conveniently, it doesn't
// care about the actual types though, so we can just use 'void' for them.
if (!CS.getContextualType() || !CS.getContextualType()->is<TupleType>())
return visitExpr(TE);

auto contextualTT = CS.getContextualType()->castTo<TupleType>();

SmallVector<TupleTypeElt, 4> ArgElts;
auto voidTy = CS.getASTContext().TheEmptyTupleType;

for (unsigned i = 0, e = TE->getNumElements(); i != e; ++i)
ArgElts.push_back({ voidTy, TE->getElementName(i) });
auto TEType = TupleType::get(ArgElts, CS.getASTContext());

if (!TEType->is<TupleType>())
return visitExpr(TE);

SmallVector<unsigned, 4> sources;

// If the shuffle is invalid, then there is a type error. We could diagnose
// it specifically here, but the general logic does a fine job so we let it
// do it.
if (computeTupleShuffle(TEType->castTo<TupleType>()->getElements(),
contextualTT->getElements(), sources))
return visitExpr(TE);

// If we got a correct shuffle, we can perform the analysis of all of
// the input elements, with their expected types.
for (unsigned i = 0, e = sources.size(); i != e; ++i) {
// Otherwise, it must match the corresponding expected argument type.
unsigned inArgNo = sources[i];

TCCOptions options;
if (contextualTT->getElement(i).isInOut())
options |= TCC_AllowLValue;

auto actualType = contextualTT->getElementType(i);
auto exprResult =
typeCheckChildIndependently(TE->getElement(inArgNo), actualType,
CS.getContextualTypePurpose(), options);
// If there was an error type checking this argument, then we're done.
if (!exprResult) return true;
}

return false;
}

/// An IdentityExpr doesn't change its argument, but it *can* propagate its
/// contextual type information down.
bool FailureDiagnosis::visitIdentityExpr(IdentityExpr *E) {
Expand Down
192 changes: 23 additions & 169 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,165 +131,6 @@ FailureDiagnostic::getChoiceFor(ConstraintLocator *locator) const {
return getOverloadChoiceIfAvailable(cs.getCalleeLocator(locator));
}

Type FailureDiagnostic::resolveInterfaceType(Type type,
bool reconstituteSugar) const {
auto &cs = getConstraintSystem();
auto resolvedType = type.transform([&](Type type) -> Type {
if (auto *tvt = type->getAs<TypeVariableType>()) {
// If this type variable is for a generic parameter, return that.
if (auto *gp = tvt->getImpl().getGenericParameter())
return gp;

// Otherwise resolve its fixed type, mapped out of context.
if (auto fixed = cs.getFixedType(tvt))
return resolveInterfaceType(fixed->mapTypeOutOfContext());

return cs.getRepresentative(tvt);
}
if (auto *dmt = type->getAs<DependentMemberType>()) {
// For a dependent member, first resolve the base.
auto newBase = resolveInterfaceType(dmt->getBase());

// Then reconstruct using its associated type.
assert(dmt->getAssocType());
return DependentMemberType::get(newBase, dmt->getAssocType());
}
return type;
});

assert(!resolvedType->hasArchetype());
return reconstituteSugar ? resolvedType->reconstituteSugar(/*recursive*/ true)
: resolvedType;
}

/// Given an apply expr, returns true if it is expected to have a direct callee
/// overload, resolvable using `getChoiceFor`. Otherwise, returns false.
static bool shouldHaveDirectCalleeOverload(const CallExpr *callExpr) {
auto *fnExpr = callExpr->getDirectCallee();

// An apply of an apply/subscript doesn't have a direct callee.
if (isa<ApplyExpr>(fnExpr) || isa<SubscriptExpr>(fnExpr))
return false;

// Applies of closures don't have callee overloads.
if (isa<ClosureExpr>(fnExpr))
return false;

// No direct callee for a try!/try?.
if (isa<ForceTryExpr>(fnExpr) || isa<OptionalTryExpr>(fnExpr))
return false;

// If we have an intermediate cast, there's no direct callee.
if (isa<ExplicitCastExpr>(fnExpr))
return false;

// No direct callee for an if expr.
if (isa<IfExpr>(fnExpr))
return false;

// Assume that anything else would have a direct callee.
return true;
}

Optional<FunctionArgApplyInfo>
FailureDiagnostic::getFunctionArgApplyInfo(ConstraintLocator *locator) const {
auto &cs = getConstraintSystem();
auto *anchor = locator->getAnchor();
auto path = locator->getPath();

// Look for the apply-arg-to-param element in the locator's path. We may
// have to look through other elements that are generated from an argument
// conversion such as GenericArgument for an optional-to-optional conversion,
// and OptionalPayload for a value-to-optional conversion.
auto iter = path.rbegin();
auto applyArgElt = locator->findLast<LocatorPathElt::ApplyArgToParam>(iter);
if (!applyArgElt)
return None;

auto nextIter = iter + 1;
assert(!locator->findLast<LocatorPathElt::ApplyArgToParam>(nextIter) &&
"Multiple ApplyArgToParam components?");

// Form a new locator that ends at the apply-arg-to-param element, and
// simplify it to get the full argument expression.
auto argPath = path.drop_back(iter - path.rbegin());
auto *argLocator = cs.getConstraintLocator(
anchor, argPath, ConstraintLocator::getSummaryFlagsForPath(argPath));

auto *argExpr = simplifyLocatorToAnchor(argLocator);

// If we were unable to simplify down to the argument expression, we don't
// know what this is.
if (!argExpr)
return None;

Optional<OverloadChoice> choice;
Type rawFnType;
if (auto overload = getChoiceFor(argLocator)) {
// If we have resolved an overload for the callee, then use that to get the
// function type and callee.
choice = overload->choice;
rawFnType = overload->openedType;
} else {
// If we didn't resolve an overload for the callee, we should be dealing
// with a call of an arbitrary function expr.
if (auto *call = dyn_cast<CallExpr>(anchor)) {
assert(!shouldHaveDirectCalleeOverload(call) &&
"Should we have resolved a callee for this?");
rawFnType = cs.getType(call->getFn());
} else {
// FIXME: ArgumentMismatchFailure is currently used from CSDiag, meaning
// we can end up a BinaryExpr here with an unresolved callee. It should be
// possible to remove this once we've gotten rid of the old CSDiag logic
// and just assert that we have a CallExpr.
auto *apply = cast<ApplyExpr>(anchor);
rawFnType = cs.getType(apply->getFn());
}
}

// Try to resolve the function type by loading lvalues and looking through
// optional types, which can occur for expressions like `fn?(5)`.
auto *fnType = resolveType(rawFnType)
->lookThroughAllOptionalTypes()
->getAs<FunctionType>();
if (!fnType)
return None;

// Resolve the interface type for the function. Note that this may not be a
// function type, for example it could be a generic parameter.
Type fnInterfaceType;
auto *callee = choice ? choice->getDeclOrNull() : nullptr;
if (callee && callee->hasInterfaceType()) {
// If we have a callee with an interface type, we can use it. This is
// preferable to resolveInterfaceType, as this will allow us to get a
// GenericFunctionType for generic decls.
//
// Note that it's possible to find a callee without an interface type. This
// can happen for example with closure parameters, where the interface type
// isn't set until the solution is applied. In that case, use
// resolveInterfaceType.
fnInterfaceType = callee->getInterfaceType();

// Strip off the curried self parameter if necessary.
if (hasAppliedSelf(cs, *choice))
fnInterfaceType = fnInterfaceType->castTo<AnyFunctionType>()->getResult();

if (auto *fn = fnInterfaceType->getAs<AnyFunctionType>()) {
assert(fn->getNumParams() == fnType->getNumParams() &&
"Parameter mismatch?");
(void)fn;
}
} else {
fnInterfaceType = resolveInterfaceType(rawFnType);
}

auto argIdx = applyArgElt->getArgIdx();
auto paramIdx = applyArgElt->getParamIdx();

return FunctionArgApplyInfo(cs, argExpr, argIdx, getType(argExpr),
paramIdx, fnInterfaceType, fnType, callee);
}

Type FailureDiagnostic::restoreGenericParameters(
Type type,
llvm::function_ref<void(GenericTypeParamType *, Type)> substitution) {
Expand Down Expand Up @@ -929,7 +770,8 @@ bool NoEscapeFuncToTypeConversionFailure::diagnoseParameterUse() const {
// Let's check whether this is a function parameter passed
// as an argument to another function which accepts @escaping
// function at that position.
if (auto argApplyInfo = getFunctionArgApplyInfo(getLocator())) {
auto &cs = getConstraintSystem();
if (auto argApplyInfo = cs.getFunctionArgApplyInfo(getLocator())) {
auto paramInterfaceTy = argApplyInfo->getParamInterfaceType();
if (paramInterfaceTy->isTypeParameter()) {
auto diagnoseGenericParamFailure = [&](GenericTypeParamDecl *decl) {
Expand Down Expand Up @@ -1137,7 +979,8 @@ void MissingOptionalUnwrapFailure::offerDefaultValueUnwrapFixIt(
if (isa<InOutExpr>(anchor))
return;

if (auto argApplyInfo = getFunctionArgApplyInfo(getLocator()))
auto &cs = getConstraintSystem();
if (auto argApplyInfo = cs.getFunctionArgApplyInfo(getLocator()))
if (argApplyInfo->getParameterFlags().isInOut())
return;

Expand Down Expand Up @@ -2961,9 +2804,18 @@ ContextualFailure::getDiagnosticFor(ContextualTypePurpose context,
}

bool TupleContextualFailure::diagnoseAsError() {
auto diagnostic = isNumElementsMismatch()
? diag::tuple_types_not_convertible_nelts
: diag::tuple_types_not_convertible;
Diag<Type, Type> diagnostic;
auto purpose = getContextualTypePurpose();
auto &cs = getConstraintSystem();
if (isNumElementsMismatch())
diagnostic = diag::tuple_types_not_convertible_nelts;
else if ((purpose == CTP_Initialization) && !cs.getContextualType())
diagnostic = diag::tuple_types_not_convertible;
else if (auto diag = getDiagnosticFor(purpose, /*forProtocol=*/false))
diagnostic = *diag;
else
return false;

emitDiagnostic(getAnchor()->getLoc(), diagnostic, getFromType(), getToType());
return true;
}
Expand Down Expand Up @@ -3796,7 +3648,7 @@ bool MissingArgumentsFailure::diagnoseAsError() {
// foo(bar) // `() -> Void` vs. `(Int) -> Void`
// ```
if (locator->isLastElement<LocatorPathElt::ApplyArgToParam>()) {
auto info = *getFunctionArgApplyInfo(locator);
auto info = *(cs.getFunctionArgApplyInfo(locator));

auto *argExpr = info.getArgExpr();
emitDiagnostic(argExpr->getLoc(), diag::cannot_convert_argument_value,
Expand Down Expand Up @@ -4012,7 +3864,7 @@ bool MissingArgumentsFailure::diagnoseClosure(ClosureExpr *closure) {
auto *locator = getLocator();
if (locator->isForContextualType()) {
funcType = cs.getContextualType()->getAs<FunctionType>();
} else if (auto info = getFunctionArgApplyInfo(locator)) {
} else if (auto info = cs.getFunctionArgApplyInfo(locator)) {
funcType = info->getParamType()->getAs<FunctionType>();
} else if (locator->isLastElement<LocatorPathElt::ClosureResult>()) {
// Based on the locator we know this this is something like this:
Expand Down Expand Up @@ -4725,7 +4577,8 @@ SourceLoc InvalidUseOfAddressOf::getLoc() const {
}

bool InvalidUseOfAddressOf::diagnoseAsError() {
if (auto argApplyInfo = getFunctionArgApplyInfo(getLocator())) {
auto &cs = getConstraintSystem();
if (auto argApplyInfo = cs.getFunctionArgApplyInfo(getLocator())) {
if (!argApplyInfo->getParameterFlags().isInOut()) {
auto anchor = getAnchor();
emitDiagnostic(anchor->getLoc(), diag::extra_address_of, getToType())
Expand Down Expand Up @@ -5245,18 +5098,19 @@ bool ThrowingFunctionConversionFailure::diagnoseAsError() {
}

bool InOutConversionFailure::diagnoseAsError() {
auto &cs = getConstraintSystem();
auto *anchor = getAnchor();
auto *locator = getLocator();
auto path = locator->getPath();

if (!path.empty() &&
path.back().getKind() == ConstraintLocator::FunctionArgument) {
if (auto argApplyInfo = getFunctionArgApplyInfo(locator)) {
if (auto argApplyInfo = cs.getFunctionArgApplyInfo(locator)) {
emitDiagnostic(anchor->getLoc(), diag::cannot_convert_argument_value,
argApplyInfo->getArgType(), argApplyInfo->getParamType());
} else {
assert(locator->findLast<LocatorPathElt::ContextualType>());
auto contextualType = getConstraintSystem().getContextualType();
auto contextualType = cs.getContextualType();
auto purpose = getContextualTypePurpose();
auto diagnostic = getDiagnosticFor(purpose, /*forProtocol=*/false);

Expand Down
Loading