Skip to content

Commit 60fb2bf

Browse files
committed
[CSFix] Try to fix missing member by defining it based on use
If lookup couldn't find anything matching given name, let's try to fake its presence based on how member is being used. This is going to help (potentially) type-check whole expression and diagnose the problem precisely.
1 parent 62c4036 commit 60fb2bf

File tree

5 files changed

+156
-8
lines changed

5 files changed

+156
-8
lines changed

lib/Sema/CSFix.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,14 @@ UseSubscriptOperator *UseSubscriptOperator::create(ConstraintSystem &cs,
245245
ConstraintLocator *locator) {
246246
return new (cs.getAllocator()) UseSubscriptOperator(cs, locator);
247247
}
248+
249+
bool DefineMemberBasedOnUse::diagnose(Expr *root, bool asNote) const {
250+
return false;
251+
}
252+
253+
DefineMemberBasedOnUse *
254+
DefineMemberBasedOnUse::create(ConstraintSystem &cs, Type baseType,
255+
DeclName member, ConstraintLocator *locator) {
256+
return new (cs.getAllocator())
257+
DefineMemberBasedOnUse(cs, baseType, member, locator);
258+
}

lib/Sema/CSFix.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ enum class FixKind : uint8_t {
9999

100100
/// Instead of spelling out `subscript` directly, use subscript operator.
101101
UseSubscriptOperator,
102+
103+
/// Requested name is not associated with a give base type,
104+
/// fix this issue by pretending that member exists and matches
105+
/// given arguments/result types exactly.
106+
DefineMemberBasedOnUse,
102107
};
103108

104109
class ConstraintFix {
@@ -465,6 +470,30 @@ class UseSubscriptOperator final : public ConstraintFix {
465470
ConstraintLocator *locator);
466471
};
467472

473+
class DefineMemberBasedOnUse final : public ConstraintFix {
474+
Type BaseType;
475+
DeclName Name;
476+
477+
public:
478+
DefineMemberBasedOnUse(ConstraintSystem &cs, Type baseType, DeclName member,
479+
ConstraintLocator *locator)
480+
: ConstraintFix(cs, FixKind::DefineMemberBasedOnUse, locator),
481+
BaseType(baseType), Name(member) {}
482+
483+
std::string getName() const override {
484+
llvm::SmallVector<char, 16> scratch;
485+
auto memberName = Name.getString(scratch);
486+
return "define missing member named '" + memberName.str() +
487+
"' based on its use";
488+
}
489+
490+
bool diagnose(Expr *root, bool asNote = false) const override;
491+
492+
static DefineMemberBasedOnUse *create(ConstraintSystem &cs, Type baseType,
493+
DeclName member,
494+
ConstraintLocator *locator);
495+
};
496+
468497
} // end namespace constraints
469498
} // end namespace swift
470499

lib/Sema/CSSimplify.cpp

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3668,6 +3668,9 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
36683668
DeclContext *useDC, FunctionRefKind functionRefKind,
36693669
ArrayRef<OverloadChoice> outerAlternatives, TypeMatchOptions flags,
36703670
ConstraintLocatorBuilder locatorB) {
3671+
// We'd need to record original base type because it might be a type
3672+
// variable representing another missing member.
3673+
auto origBaseTy = baseTy;
36713674
// Resolve the base type, if we can. If we can't resolve the base type,
36723675
// then we can't solve this constraint.
36733676
baseTy = simplifyType(baseTy, flags);
@@ -3715,6 +3718,13 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
37153718
// If the lookup found no hits at all (either viable or unviable), diagnose it
37163719
// as such and try to recover in various ways.
37173720
if (shouldAttemptFixes()) {
3721+
// Let's record missing member in constraint system, this helps to prevent
3722+
// stacking up fixes for the same member, because e.g. if its base was of
3723+
// optional type, we'd re-introduce member constraint with optional stripped
3724+
// off to see if the problem is related to base not being explicitly unwrapped.
3725+
if (!MissingMembers.insert(locator))
3726+
return SolutionKind::Error;
3727+
37183728
if (baseObjTy->getOptionalObjectType()) {
37193729
// If the base type was an optional, look through it.
37203730

@@ -3760,15 +3770,22 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
37603770
return SolutionKind::Solved;
37613771
}
37623772

3773+
auto solveWithNewBaseOrName = [&](Type baseType,
3774+
DeclName memberName) -> SolutionKind {
3775+
// Let's re-enable fixes for this member, because
3776+
// the base or member name has been changed.
3777+
MissingMembers.remove(locator);
3778+
return simplifyMemberConstraint(kind, baseType, memberName, memberTy,
3779+
useDC, functionRefKind, outerAlternatives,
3780+
flags, locatorB);
3781+
};
3782+
37633783
if (auto *funcType = baseTy->getAs<FunctionType>()) {
37643784
// We can't really suggest anything useful unless
37653785
// function takes no arguments, otherwise it
37663786
// would make sense to report this a missing member.
37673787
if (funcType->getNumParams() == 0) {
3768-
auto result = simplifyMemberConstraint(
3769-
kind, funcType->getResult(), member, memberTy, useDC,
3770-
functionRefKind, outerAlternatives, flags, locatorB);
3771-
3788+
auto result = solveWithNewBaseOrName(funcType->getResult(), member);
37723789
// If there is indeed a member with given name in result type
37733790
// let's return, otherwise let's fall-through and report
37743791
// this problem as a missing member.
@@ -3781,16 +3798,59 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
37813798

37823799
// Instead of using subscript operator spelled out `subscript` directly.
37833800
if (member.getBaseName() == getTokenText(tok::kw_subscript)) {
3784-
auto result = simplifyMemberConstraint(
3785-
kind, baseTy, DeclBaseName::createSubscript(), memberTy, useDC,
3786-
functionRefKind, {}, flags, locatorB);
3787-
3801+
auto result =
3802+
solveWithNewBaseOrName(baseTy, DeclBaseName::createSubscript());
37883803
// Looks like it was indeed meant to be a subscript operator.
37893804
if (result == SolutionKind::Solved)
37903805
return recordFix(UseSubscriptOperator::create(*this, locator))
37913806
? SolutionKind::Error
37923807
: SolutionKind::Solved;
37933808
}
3809+
3810+
// FIXME(diagnostics): This is more of a hack than anything.
3811+
// Let's not try to suggest that there is no member related to an
3812+
// obscure underscored type, the real problem would be somewhere
3813+
// else. This helps to diagnose pattern matching cases.
3814+
{
3815+
if (auto *metatype = baseTy->getAs<MetatypeType>()) {
3816+
auto instanceTy = metatype->getInstanceType();
3817+
if (auto *NTD = instanceTy->getAnyNominal()) {
3818+
if (NTD->getName() == getASTContext().Id_OptionalNilComparisonType)
3819+
return SolutionKind::Error;
3820+
}
3821+
}
3822+
}
3823+
3824+
// FIXME(diagnostics): Errors related to `AnyObject` could be diagnosed
3825+
// better in the future, relevant failure information has to be extracted
3826+
// from `performMemberLookup` result, in order to figure out if it was a
3827+
// simple labeling or # of arguments mismatch, or member with requested name
3828+
// really doesn't exist.
3829+
if (baseTy->isAnyObject())
3830+
return SolutionKind::Error;
3831+
3832+
result = performMemberLookup(kind, member, baseTy, functionRefKind, locator,
3833+
/*includeInaccessibleMembers*/ true);
3834+
3835+
// FIXME(diagnostics): If there were no viable results, but there are
3836+
// unviable ones, we'd have to introduce fix for each specific problem.
3837+
if (!result.UnviableCandidates.empty())
3838+
return SolutionKind::Error;
3839+
3840+
// Since member with given base and name doesn't exist, let's try to
3841+
// fake its presence based on use, that makes it possible to diagnose
3842+
// problems related to member lookup more precisely.
3843+
auto *fix =
3844+
DefineMemberBasedOnUse::create(*this, origBaseTy, member, locator);
3845+
if (recordFix(fix))
3846+
return SolutionKind::Error;
3847+
3848+
// Allow member type to default to `Any` to make it possible to form
3849+
// solutions when contextual type of the result cannot be deduced e.g.
3850+
// `let _ = x.foo`.
3851+
addConstraint(ConstraintKind::Defaultable, memberTy,
3852+
getASTContext().TheAnyType, locator);
3853+
return SolutionKind::Solved;
37943854
}
37953855
return SolutionKind::Error;
37963856
}
@@ -4508,6 +4568,25 @@ ConstraintSystem::simplifyApplicableFnConstraint(
45084568
// following: $T1 -> $T2.
45094569
assert(type1->is<FunctionType>());
45104570

4571+
// Let's check if this member couldn't be found and is fixed
4572+
// to exist based on its usage.
4573+
if (auto *memberTy = type2->getAs<TypeVariableType>()) {
4574+
auto *locator = memberTy->getImpl().getLocator();
4575+
if (MissingMembers.count(locator)) {
4576+
auto *funcTy = type1->castTo<FunctionType>();
4577+
// Bind type variable associated with member to a type of argument
4578+
// application, which makes it seem like member exists with the
4579+
// types of the parameters matching argument types exactly.
4580+
addConstraint(ConstraintKind::Bind, memberTy, funcTy, locator);
4581+
// There might be no contextual type for result of the application,
4582+
// in cases like `let _ = x.foo()`, so let's default result to `Any`
4583+
// to make expressions like that type-check.
4584+
addConstraint(ConstraintKind::Defaultable, funcTy->getResult(),
4585+
getASTContext().TheAnyType, locator);
4586+
return SolutionKind::Solved;
4587+
}
4588+
}
4589+
45114590
// Drill down to the concrete type on the right hand side.
45124591
type2 = getFixedTypeRecursive(type2, flags, /*wantRValue=*/true);
45134592
auto desugar2 = type2->getDesugaredType();
@@ -5461,6 +5540,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
54615540
case FixKind::AddConformance:
54625541
case FixKind::AutoClosureForwarding:
54635542
case FixKind::RemoveUnwrap:
5543+
case FixKind::DefineMemberBasedOnUse:
54645544
llvm_unreachable("handled elsewhere");
54655545
}
54665546

lib/Sema/CSSolver.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ Solution ConstraintSystem::finalize() {
125125
}
126126
solution.Fixes.append(Fixes.begin() + firstFixIndex, Fixes.end());
127127

128+
// Remember all of the missing member references encountered,
129+
// that helps diagnostics to avoid emitting error for each
130+
// member in the chain.
131+
for (auto *member : MissingMembers)
132+
solution.MissingMembers.push_back(member);
133+
128134
// Remember all the disjunction choices we made.
129135
for (auto &choice : DisjunctionChoices) {
130136
// We shouldn't ever register disjunction choices multiple times,
@@ -232,6 +238,10 @@ void ConstraintSystem::applySolution(const Solution &solution) {
232238

233239
// Register any fixes produced along this path.
234240
Fixes.append(solution.Fixes.begin(), solution.Fixes.end());
241+
242+
// Register any missing members encountered along this path.
243+
MissingMembers.insert(solution.MissingMembers.begin(),
244+
solution.MissingMembers.end());
235245
}
236246

237247
/// Restore the type variable bindings to what they were before
@@ -307,6 +317,13 @@ void truncate(SmallVectorImpl<T> &vec, unsigned newSize) {
307317
vec.erase(vec.begin() + newSize, vec.end());
308318
}
309319

320+
template<typename T, unsigned N>
321+
void truncate(llvm::SmallSetVector<T, N> &vec, unsigned newSize) {
322+
assert(newSize <= vec.size() && "Not a truncation!");
323+
for (unsigned i = 0, n = vec.size() - newSize; i != n; ++i)
324+
vec.pop_back();
325+
}
326+
310327
} // end anonymous namespace
311328

312329
ConstraintSystem::SolverState::SolverState(
@@ -409,6 +426,7 @@ ConstraintSystem::SolverScope::SolverScope(ConstraintSystem &cs)
409426
numOpenedExistentialTypes = cs.OpenedExistentialTypes.size();
410427
numDefaultedConstraints = cs.DefaultedConstraints.size();
411428
numCheckedConformances = cs.CheckedConformances.size();
429+
numMissingMembers = cs.MissingMembers.size();
412430
PreviousScore = cs.CurrentScore;
413431

414432
cs.solverState->registerScope(this);
@@ -459,6 +477,9 @@ ConstraintSystem::SolverScope::~SolverScope() {
459477
// Remove any conformances checked along the current path.
460478
truncate(cs.CheckedConformances, numCheckedConformances);
461479

480+
// Remove any missing members found along the current path.
481+
truncate(cs.MissingMembers, numMissingMembers);
482+
462483
// Reset the previous score.
463484
cs.CurrentScore = PreviousScore;
464485

lib/Sema/ConstraintSystem.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,9 @@ class Solution {
593593
/// The list of fixes that need to be applied to the initial expression
594594
/// to make the solution work.
595595
llvm::SmallVector<ConstraintFix *, 4> Fixes;
596+
/// The list of member references which couldn't be resolved,
597+
/// and had to be assumed based on their use.
598+
llvm::SmallVector<ConstraintLocator *, 4> MissingMembers;
596599

597600
/// The set of disjunction choices used to arrive at this solution,
598601
/// which informs constraint application.
@@ -1038,6 +1041,8 @@ class ConstraintSystem {
10381041

10391042
/// The set of fixes applied to make the solution work.
10401043
llvm::SmallVector<ConstraintFix *, 4> Fixes;
1044+
/// The set of type variables representing types of missing members.
1045+
llvm::SmallSetVector<ConstraintLocator *, 4> MissingMembers;
10411046

10421047
/// The set of remembered disjunction choices used to reach
10431048
/// the current constraint system.
@@ -1486,6 +1491,8 @@ class ConstraintSystem {
14861491

14871492
unsigned numCheckedConformances;
14881493

1494+
unsigned numMissingMembers;
1495+
14891496
/// The previous score.
14901497
Score PreviousScore;
14911498

0 commit comments

Comments
 (0)