Skip to content

[Diagnostics] Nullify contextual type if it's generic with type variables #6806

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 1 commit into from
Jan 17, 2017
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
93 changes: 72 additions & 21 deletions lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2075,6 +2075,20 @@ class FailureDiagnosis :public ASTVisitor<FailureDiagnosis, /*exprresult*/bool>{
bool diagnoseCalleeResultContextualConversionError();

private:
/// Validate potential contextual type for type-checking one of the
/// sub-expressions, usually correct/valid types are the ones which
/// either don't have type variables or are not generic, because
/// generic types with left-over type variables or unresolved types
/// degrade quality of diagnostics if allowed to be used as contextual.
///
/// \param contextualType The candidate contextual type.
/// \param CTP The contextual purpose attached to the given candidate.
///
/// \returns Pair of validated type and it's purpose, potentially nullified
/// if it wasn't an appropriate type to be used.
std::pair<Type, ContextualTypePurpose>
validateContextualType(Type contextualType, ContextualTypePurpose CTP);

/// Check the specified closure to see if it is a multi-statement closure with
/// an uninferred type. If so, diagnose the problem with an error and return
/// true.
Expand Down Expand Up @@ -3433,29 +3447,11 @@ Expr *FailureDiagnosis::typeCheckChildIndependently(

CS->TC.addExprForDiagnosis(subExpr, subExpr);
}

// If we have a conversion type, but it has type variables (from the current
// ConstraintSystem), then we can't use it.
if (convertType) {
// If we're asked to convert to an autoclosure, then we really want to
// convert to the result of it.
if (auto *FT = convertType->getAs<AnyFunctionType>())
if (FT->isAutoClosure())
convertType = FT->getResult();

// Replace archetypes and type parameters with UnresolvedType.
convertType = replaceTypeParametersWithUnresolved(convertType);
convertType = replaceTypeVariablesWithUnresolved(convertType);

// If the conversion type contains no info, drop it.
if (convertType->is<UnresolvedType>() ||
(convertType->is<MetatypeType>() && convertType->hasUnresolvedType())) {
convertType = Type();
convertTypePurpose = CTP_Unused;
}
}
// Validate contextual type before trying to use it.
std::tie(convertType, convertTypePurpose) =
validateContextualType(convertType, convertTypePurpose);


// If we have no contextual type information and the subexpr is obviously a
// overload set, don't recursively simplify this. The recursive solver will
// sometimes pick one based on arbitrary ranking behavior (e.g. like
Expand Down Expand Up @@ -7376,6 +7372,61 @@ static void noteArchetypeSource(const TypeLoc &loc, ArchetypeType *archetype,
}
}

std::pair<Type, ContextualTypePurpose>
FailureDiagnosis::validateContextualType(Type contextualType,
ContextualTypePurpose CTP) {
if (!contextualType)
return {contextualType, CTP};

// If we're asked to convert to an autoclosure, then we really want to
// convert to the result of it.
if (auto *FT = contextualType->getAs<AnyFunctionType>())
if (FT->isAutoClosure())
contextualType = FT->getResult();

bool shouldNullify = false;
if (auto objectType = contextualType->getLValueOrInOutObjectType()) {
// Note that simply checking for `objectType->hasUnresolvedType()` is not
// appropriate in this case standalone, because if it's in a function,
// for example, or inout type, we still want to preserve it's skeleton
/// because that helps to diagnose inout argument issues. Complete
// nullification is only appropriate for generic types with unresolved
// types or standalone archetypes because that's going to give
// sub-expression solver a chance to try and compute type as it sees fit
// and higher level code would have a chance to check it, which avoids
// diagnostic messages like `cannot convert (_) -> _ to (Int) -> Void`.
switch (objectType->getDesugaredType()->getKind()) {
case TypeKind::Archetype:
case TypeKind::Unresolved:
shouldNullify = true;
break;

case TypeKind::BoundGenericEnum:
case TypeKind::BoundGenericClass:
case TypeKind::BoundGenericStruct:
case TypeKind::UnboundGeneric:
case TypeKind::GenericFunction:
case TypeKind::Metatype:
shouldNullify = objectType->hasUnresolvedType();
break;

default:
shouldNullify = false;
break;
}
}

// If the conversion type contains no info, drop it.
if (shouldNullify)
return {Type(), CTP_Unused};

// Remove all of the potentially leftover type variables or type parameters
// from the contextual type to be used by new solver.
contextualType = replaceTypeParametersWithUnresolved(contextualType);
contextualType = replaceTypeVariablesWithUnresolved(contextualType);

return {contextualType, CTP};
}

/// Check the specified closure to see if it is a multi-statement closure with
/// an uninferred type. If so, diagnose the problem with an error and return
Expand Down
13 changes: 13 additions & 0 deletions test/Constraints/generics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -426,3 +426,16 @@ func genericFunc<T>(t: T) {
// expected-note@-1 {{explicitly specify the generic arguments to fix this issue}}
// expected-error@-2 2 {{type 'T' does not conform to protocol 'Hashable'}}
}

struct SR_3525<T> {}
func sr3525_arg_int(_: inout SR_3525<Int>) {}
func sr3525_arg_gen<T>(_: inout SR_3525<T>) {}
func sr3525_1(t: SR_3525<Int>) {
let _ = sr3525_arg_int(&t) // expected-error {{cannot pass immutable value as inout argument: 't' is a 'let' constant}}
}
func sr3525_2(t: SR_3525<Int>) {
let _ = sr3525_arg_gen(&t) // expected-error {{cannot pass immutable value as inout argument: 't' is a 'let' constant}}
}
func sr3525_3<T>(t: SR_3525<T>) {
let _ = sr3525_arg_gen(&t) // expected-error {{cannot pass immutable value as inout argument: 't' is a 'let' constant}}
}