Skip to content

[ConstraintSystem] Introduce a notion of a "hole" #26798

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 3 commits into from
Aug 27, 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
29 changes: 27 additions & 2 deletions lib/Sema/CSBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -978,8 +978,33 @@ bool TypeVariableBinding::attempt(ConstraintSystem &cs) const {
cs.addConstraint(ConstraintKind::Bind, TypeVar, type, locator);

// If this was from a defaultable binding note that.
if (Binding.isDefaultableBinding())
cs.DefaultedConstraints.push_back(Binding.DefaultableBinding);
if (Binding.isDefaultableBinding()) {
auto *locator = Binding.DefaultableBinding;
// If this default binding comes from a "hole"
// in the constraint system, we have to propagate
// this information and mark this type variable
// as well as mark everything adjacent to it as
// a potential "hole".
//
// Consider this example:
//
// func foo<T: BinaryInteger>(_: T) {}
// foo(.bar) <- Since `.bar` can't be inferred due to
// luck of information about its base type,
// it's member type is going to get defaulted
// to `Any` which has to be propaged to type
// variable associated with `T` and vice versa.
if (cs.shouldAttemptFixes() && cs.isHoleAt(locator)) {
auto &CG = cs.getConstraintGraph();
for (auto *constraint : CG.gatherConstraints(
TypeVar, ConstraintGraph::GatheringKind::EquivalenceClass)) {
for (auto *typeVar : constraint->getTypeVariables())
cs.recordHole(typeVar);
}
}

cs.DefaultedConstraints.push_back(locator);
}

return !cs.failedConstraint && !cs.simplify();
}
33 changes: 21 additions & 12 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2834,18 +2834,6 @@ bool MissingMemberFailure::diagnoseAsError() {
if (!anchor || !baseExpr)
return false;

if (auto *typeVar = getBaseType()->getAs<TypeVariableType>()) {
auto &CS = getConstraintSystem();
auto *memberLoc = typeVar->getImpl().getLocator();
// Don't try to diagnose anything besides first missing
// member in the chain. e.g. `x.foo().bar()` let's make
// sure to diagnose only `foo()` as missing because we
// don't really know much about what `bar()` is supposed
// to be.
if (CS.MissingMembers.count(memberLoc))
return false;
}

auto baseType = resolveType(getBaseType())->getWithoutSpecifierType();

DeclNameLoc nameLoc(anchor->getStartLoc());
Expand Down Expand Up @@ -2911,6 +2899,27 @@ bool MissingMemberFailure::diagnoseAsError() {
diagnostic.highlight(baseExpr->getSourceRange())
.highlight(nameLoc.getSourceRange());
correction->addFixits(diagnostic);
} else if (instanceTy->getAnyNominal() &&
getName().getBaseName() == DeclBaseName::createConstructor()) {
auto &cs = getConstraintSystem();

auto memberName = getName().getBaseName();
auto result = cs.performMemberLookup(
ConstraintKind::ValueMember, memberName, metatypeTy,
FunctionRefKind::DoubleApply, getLocator(),
/*includeInaccessibleMembers=*/true);

// If there are no `init` members at all produce a tailored
// diagnostic for that, otherwise fallback to generic
// "no such member" one.
if (result.ViableCandidates.empty() &&
result.UnviableCandidates.empty()) {
emitDiagnostic(anchor->getLoc(), diag::no_accessible_initializers,
instanceTy)
.highlight(baseExpr->getSourceRange());
} else {
emitBasicError(baseType);
}
} else {
emitBasicError(baseType);
}
Expand Down
135 changes: 95 additions & 40 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2177,6 +2177,9 @@ static ConstraintFix *fixPropertyWrapperFailure(
if (!decl->hasValidSignature() || !type)
return nullptr;

if (baseTy->isEqual(type))
return nullptr;

if (!attemptFix(resolvedOverload, decl, type))
return nullptr;

Expand Down Expand Up @@ -3715,6 +3718,23 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
ConstraintKind kind,
ConstraintLocatorBuilder locator,
TypeMatchOptions flags) {
if (shouldAttemptFixes()) {
auto *typeVar = type->getAs<TypeVariableType>();
// If type variable, associated with this conformance check,
// has been determined to be a "hole" in constraint system,
// let's consider this check a success without recording
// a fix, because it's just a consequence of other failure
// e.g.
//
// func foo<T: BinaryInteger>(_: T) {}
// foo(Foo.bar) <- if `Foo` doesn't have `bar` there is
// no reason to complain about missing conformance.
if (typeVar && isHole(typeVar)) {
increaseScore(SK_Fix);
return SolutionKind::Solved;
}
}

// Dig out the fixed type to which this type refers.
type = getFixedTypeRecursive(type, flags, /*wantRValue=*/true);

Expand Down Expand Up @@ -5126,12 +5146,33 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
// If the lookup found no hits at all (either viable or unviable), diagnose it
// as such and try to recover in various ways.
if (shouldAttemptFixes()) {
// Let's record missing member in constraint system, this helps to prevent
// stacking up fixes for the same member, because e.g. if its base was of
// optional type, we'd re-introduce member constraint with optional stripped
// off to see if the problem is related to base not being explicitly unwrapped.
if (!MissingMembers.insert(locator))
return SolutionKind::Error;
auto fixMissingMember = [&](Type baseTy, Type memberTy,
ConstraintLocator *locator) -> SolutionKind {
// Let's check whether there are any generic parameters
// associated with base type, we'd have to default them
// to `Any` and record as potential holes if so.
baseTy.transform([&](Type type) -> Type {
if (auto *typeVar = type->getAs<TypeVariableType>()) {
if (typeVar->getImpl().hasRepresentativeOrFixed())
return type;
recordHole(typeVar);
}
return type;
});

auto *fix =
DefineMemberBasedOnUse::create(*this, baseTy, member, locator);
if (recordFix(fix))
return SolutionKind::Error;

// Allow member type to default to `Any` to make it possible to form
// solutions when contextual type of the result cannot be deduced e.g.
// `let _ = x.foo`.
if (auto *memberTypeVar = memberTy->getAs<TypeVariableType>())
recordHole(memberTypeVar);

return SolutionKind::Solved;
};

if (baseObjTy->getOptionalObjectType()) {
// If the base type was an optional, look through it.
Expand All @@ -5153,6 +5194,18 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
}
}

// Let's check whether the problem is related to optionality of base
// type, or there is no member with a given name.
result =
performMemberLookup(kind, member, baseObjTy->getOptionalObjectType(),
functionRefKind, locator,
/*includeInaccessibleMembers*/ true);

// If uwrapped type still couldn't find anything for a given name,
// let's fallback to a "not such member" fix.
if (result.ViableCandidates.empty() && result.UnviableCandidates.empty())
return fixMissingMember(origBaseTy, memberTy, locator);

// The result of the member access can either be the expected member type
// (for '!' or optional members with '?'), or the original member type
// with one extra level of optionality ('?' with non-optional members).
Expand Down Expand Up @@ -5180,26 +5233,27 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
return SolutionKind::Solved;
}

auto solveWithNewBaseOrName = [&](Type baseType, DeclName memberName,
bool allowFixes = true) -> SolutionKind {
// Let's re-enable fixes for this member, because
// the base or member name has been changed.
if (allowFixes)
MissingMembers.remove(locator);
auto solveWithNewBaseOrName = [&](Type baseType,
DeclName memberName) -> SolutionKind {
return simplifyMemberConstraint(kind, baseType, memberName, memberTy,
useDC, functionRefKind, outerAlternatives,
flags, locatorB);
flags | TMF_ApplyingFix, locatorB);
};

// If this member reference is a result of a previous fix, let's not allow
// any more fixes expect when base is optional, because it could also be
// an IUO which requires a separate fix.
if (flags.contains(TMF_ApplyingFix))
return SolutionKind::Error;

// Check if any property wrappers on the base of the member lookup have
// matching members that we can fall back to, or if the type wraps any
// properties that have matching members.
if (auto *fix = fixPropertyWrapperFailure(
*this, baseTy, locator,
[&](ResolvedOverloadSetListItem *overload, VarDecl *decl,
Type newBase) {
return solveWithNewBaseOrName(newBase, member,
/*allowFixes=*/false) ==
return solveWithNewBaseOrName(newBase, member) ==
SolutionKind::Solved;
})) {
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
Expand Down Expand Up @@ -5266,29 +5320,20 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
// fake its presence based on use, that makes it possible to diagnose
// problems related to member lookup more precisely.

origBaseTy.transform([&](Type type) -> Type {
if (auto *typeVar = type->getAs<TypeVariableType>()) {
if (typeVar->getImpl().hasRepresentativeOrFixed())
return type;
// Default all of the generic parameters found in base to `Any`.
addConstraint(ConstraintKind::Defaultable, typeVar,
getASTContext().TheAnyType,
typeVar->getImpl().getLocator());
// If base type is a "hole" there is no reason to record any
// more "member not found" fixes for chained member references.
if (auto *baseType = origBaseTy->getMetatypeInstanceType()
->getRValueType()
->getAs<TypeVariableType>()) {
if (isHole(baseType)) {
increaseScore(SK_Fix);
if (auto *memberTypeVar = memberTy->getAs<TypeVariableType>())
recordHole(memberTypeVar);
return SolutionKind::Solved;
}
return type;
});

auto *fix =
DefineMemberBasedOnUse::create(*this, origBaseTy, member, locator);
if (recordFix(fix))
return SolutionKind::Error;
}

// Allow member type to default to `Any` to make it possible to form
// solutions when contextual type of the result cannot be deduced e.g.
// `let _ = x.foo`.
addConstraint(ConstraintKind::Defaultable, memberTy,
getASTContext().TheAnyType, locator);
return SolutionKind::Solved;
return fixMissingMember(origBaseTy, memberTy, locator);
}
return SolutionKind::Error;
}
Expand Down Expand Up @@ -6234,18 +6279,19 @@ ConstraintSystem::simplifyApplicableFnConstraint(
// Let's check if this member couldn't be found and is fixed
// to exist based on its usage.
if (auto *memberTy = type2->getAs<TypeVariableType>()) {
auto *locator = memberTy->getImpl().getLocator();
if (MissingMembers.count(locator)) {
if (isHole(memberTy)) {
auto *funcTy = type1->castTo<FunctionType>();
auto *locator = memberTy->getImpl().getLocator();
// Bind type variable associated with member to a type of argument
// application, which makes it seem like member exists with the
// types of the parameters matching argument types exactly.
addConstraint(ConstraintKind::Bind, memberTy, funcTy, locator);
// There might be no contextual type for result of the application,
// in cases like `let _ = x.foo()`, so let's default result to `Any`
// to make expressions like that type-check.
addConstraint(ConstraintKind::Defaultable, funcTy->getResult(),
getASTContext().TheAnyType, locator);
auto resultTy = funcTy->getResult();
if (auto *typeVar = resultTy->getAs<TypeVariableType>())
recordHole(typeVar);
return SolutionKind::Solved;
}
}
Expand Down Expand Up @@ -7148,6 +7194,15 @@ bool ConstraintSystem::recordFix(ConstraintFix *fix) {
return false;
}

void ConstraintSystem::recordHole(TypeVariableType *typeVar) {
assert(typeVar);
auto *locator = typeVar->getImpl().getLocator();
if (Holes.insert(locator)) {
addConstraint(ConstraintKind::Defaultable, typeVar,
getASTContext().TheAnyType, locator);
}
}

ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
ConstraintFix *fix, Type type1, Type type2, ConstraintKind matchKind,
TypeMatchOptions flags, ConstraintLocatorBuilder locator) {
Expand Down
18 changes: 4 additions & 14 deletions lib/Sema/CSSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,6 @@ Solution ConstraintSystem::finalize() {
}
solution.Fixes.append(Fixes.begin() + firstFixIndex, Fixes.end());

// Remember all of the missing member references encountered,
// that helps diagnostics to avoid emitting error for each
// member in the chain.
for (auto *member : MissingMembers)
solution.MissingMembers.push_back(member);

// Remember all the disjunction choices we made.
for (auto &choice : DisjunctionChoices) {
// We shouldn't ever register disjunction choices multiple times,
Expand Down Expand Up @@ -267,10 +261,6 @@ void ConstraintSystem::applySolution(const Solution &solution) {

// Register any fixes produced along this path.
Fixes.append(solution.Fixes.begin(), solution.Fixes.end());

// Register any missing members encountered along this path.
MissingMembers.insert(solution.MissingMembers.begin(),
solution.MissingMembers.end());
}

/// Restore the type variable bindings to what they were before
Expand Down Expand Up @@ -452,14 +442,14 @@ ConstraintSystem::SolverScope::SolverScope(ConstraintSystem &cs)
numSavedBindings = cs.solverState->savedBindings.size();
numConstraintRestrictions = cs.ConstraintRestrictions.size();
numFixes = cs.Fixes.size();
numHoles = cs.Holes.size();
numFixedRequirements = cs.FixedRequirements.size();
numDisjunctionChoices = cs.DisjunctionChoices.size();
numOpenedTypes = cs.OpenedTypes.size();
numOpenedExistentialTypes = cs.OpenedExistentialTypes.size();
numDefaultedConstraints = cs.DefaultedConstraints.size();
numAddedNodeTypes = cs.addedNodeTypes.size();
numCheckedConformances = cs.CheckedConformances.size();
numMissingMembers = cs.MissingMembers.size();
numDisabledConstraints = cs.solverState->getNumDisabledConstraints();
numFavoredConstraints = cs.solverState->getNumFavoredConstraints();
numBuilderTransformedClosures = cs.builderTransformedClosures.size();
Expand Down Expand Up @@ -500,6 +490,9 @@ ConstraintSystem::SolverScope::~SolverScope() {
// Remove any fixes.
truncate(cs.Fixes, numFixes);

// Remove any holes encountered along the current path.
truncate(cs.Holes, numHoles);

// Remove any disjunction choices.
truncate(cs.DisjunctionChoices, numDisjunctionChoices);

Expand All @@ -525,9 +518,6 @@ ConstraintSystem::SolverScope::~SolverScope() {
// Remove any conformances checked along the current path.
truncate(cs.CheckedConformances, numCheckedConformances);

// Remove any missing members found along the current path.
truncate(cs.MissingMembers, numMissingMembers);

/// Remove any builder transformed closures.
truncate(cs.builderTransformedClosures, numBuilderTransformedClosures);

Expand Down
Loading