Skip to content

Commit d8719ab

Browse files
committed
[CS] Handle holes in mergeEquivalenceClasses
Make sure we preserve `TVO_CanBindToHole`. This unfortunately requires introducing the concept of a "non-representative" hole for representatives where the actual hole is given by another type variable in the equivalence class. This avoids disturbing binding order and regressing diagnostics. Once binding is less sensitive to ordering we ought to be able to always choose the hole as the representative.
1 parent 10e1cdf commit d8719ab

File tree

7 files changed

+95
-22
lines changed

7 files changed

+95
-22
lines changed

include/swift/AST/Types.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -454,11 +454,11 @@ class alignas(1 << TypeAlignInBits) TypeBase
454454
NumProtocols : 16
455455
);
456456

457-
SWIFT_INLINE_BITFIELD_FULL(TypeVariableType, TypeBase, 7+28,
457+
SWIFT_INLINE_BITFIELD_FULL(TypeVariableType, TypeBase, 8+26,
458458
/// Type variable options.
459-
Options : 7,
459+
Options : 8,
460460
/// The unique number assigned to this type variable.
461-
ID : 27
461+
ID : 26
462462
);
463463

464464
SWIFT_INLINE_BITFIELD_FULL(ErrorUnionType, TypeBase, 32,

include/swift/Sema/ConstraintSystem.h

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,14 @@ enum TypeVariableOptions {
318318

319319
/// Whether the type variable can be bound only to a pack expansion type.
320320
TVO_PackExpansion = 0x40,
321+
322+
/// Whether the hole for this type variable origates from a different
323+
/// type variable in its equivalence class.
324+
/// FIXME: This is an unfortunate hack due to the fact that choosing a
325+
/// different representative changes binding order and regresses a bunch of
326+
/// test cases. Once binding order is made more resilient we ought to be able
327+
/// to remove this.
328+
TVO_NonRepresentativeHole = 0x80,
321329
};
322330

323331
enum class KeyPathMutability : uint8_t {
@@ -397,6 +405,12 @@ class TypeVariableType::Implementation {
397405
/// Whether this type variable can bind only to PackExpansionType.
398406
bool isPackExpansion() const { return getRawOptions() & TVO_PackExpansion; }
399407

408+
/// Whether the hole for this type variable origates from a different
409+
/// type variable in its equivalence class.
410+
bool isNonRepresentativeHole() const {
411+
return getRawOptions() & TVO_NonRepresentativeHole;
412+
}
413+
400414
/// Whether this type variable prefers a subtype binding over a supertype
401415
/// binding.
402416
bool prefersSubtypeBinding() const {
@@ -565,28 +579,40 @@ class TypeVariableType::Implementation {
565579
otherRep->getImpl().recordBinding(*trail);
566580
otherRep->getImpl().ParentOrFixed = getTypeVariable();
567581

582+
auto &bits = getTypeVariable()->Bits.TypeVariableType;
583+
568584
if (canBindToLValue() && !otherRep->getImpl().canBindToLValue()) {
569585
if (trail)
570586
recordBinding(*trail);
571-
getTypeVariable()->Bits.TypeVariableType.Options &= ~TVO_CanBindToLValue;
587+
bits.Options &= ~TVO_CanBindToLValue;
572588
}
573589

574590
if (canBindToInOut() && !otherRep->getImpl().canBindToInOut()) {
575591
if (trail)
576592
recordBinding(*trail);
577-
getTypeVariable()->Bits.TypeVariableType.Options &= ~TVO_CanBindToInOut;
593+
bits.Options &= ~TVO_CanBindToInOut;
578594
}
579595

580596
if (canBindToNoEscape() && !otherRep->getImpl().canBindToNoEscape()) {
581597
if (trail)
582598
recordBinding(*trail);
583-
getTypeVariable()->Bits.TypeVariableType.Options &= ~TVO_CanBindToNoEscape;
599+
bits.Options &= ~TVO_CanBindToNoEscape;
584600
}
585601

586602
if (canBindToPack() && !otherRep->getImpl().canBindToPack()) {
587603
if (trail)
588604
recordBinding(*trail);
589-
getTypeVariable()->Bits.TypeVariableType.Options &= ~TVO_CanBindToPack;
605+
bits.Options &= ~TVO_CanBindToPack;
606+
}
607+
608+
// If we're merging a non-hole with a hole, add the flag to allow binding
609+
// to a hole. This type variable then becomes a "non-representative" hole,
610+
// which is an unfortunate hack necessary to preserve binding order. See
611+
// the comment on `TVO_NonRepresentativeHole` for more info.
612+
if (!canBindToHole() && otherRep->getImpl().canBindToHole()) {
613+
if (trail)
614+
recordBinding(*trail);
615+
bits.Options |= (TVO_CanBindToHole | TVO_NonRepresentativeHole);
590616
}
591617
}
592618

@@ -701,6 +727,7 @@ class TypeVariableType::Implementation {
701727
ENTRY(TVO_PrefersSubtypeBinding, "");
702728
ENTRY(TVO_CanBindToPack, "pack");
703729
ENTRY(TVO_PackExpansion, "pack expansion");
730+
ENTRY(TVO_NonRepresentativeHole, "non-rep hole");
704731
}
705732
#undef ENTRY
706733
}
@@ -3419,6 +3446,14 @@ class ConstraintSystem {
34193446
getImplicitValueConversionLocator(ConstraintLocatorBuilder root,
34203447
ConversionRestrictionKind restriction);
34213448

3449+
/// Retrieve the type variable that represents the originating hole in the
3450+
/// equivalence class for a given type variable.
3451+
TypeVariableType *getHoleTypeVar(TypeVariableType *tv);
3452+
3453+
/// Retrieve the locator for the hole that represents the originating hole in
3454+
/// the equivalence class for a given type variable.
3455+
ConstraintLocator *getHoleLocator(TypeVariableType *tv);
3456+
34223457
/// Lookup and return parent associated with given expression.
34233458
Expr *getParentExpr(Expr *expr) {
34243459
if (auto result = getExprDepthAndParent(expr))

lib/Sema/CSBindings.cpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ bool BindingSet::isDelayed() const {
169169
return true;
170170

171171
if (isHole()) {
172-
auto *locator = TypeVar->getImpl().getLocator();
172+
auto *locator = CS.getHoleLocator(TypeVar);
173173
assert(locator && "a hole without locator?");
174174

175175
// Delay resolution of the code completion expression until
@@ -2745,7 +2745,7 @@ bool TypeVarBindingProducer::computeNext() {
27452745

27462746
std::optional<std::pair<ConstraintFix *, unsigned>>
27472747
TypeVariableBinding::fixForHole(ConstraintSystem &cs) const {
2748-
auto *dstLocator = TypeVar->getImpl().getLocator();
2748+
auto *dstLocator = cs.getHoleLocator(TypeVar);
27492749
auto *srcLocator = Binding.getLocator();
27502750

27512751
// FIXME: This check could be turned into an assert once
@@ -2766,7 +2766,7 @@ TypeVariableBinding::fixForHole(ConstraintSystem &cs) const {
27662766

27672767
unsigned defaultImpact = 1;
27682768

2769-
if (auto *GP = TypeVar->getImpl().getGenericParameter()) {
2769+
if (auto *GP = dstLocator->getGenericParameter()) {
27702770
// If it is representative for a key path root, let's emit a more
27712771
// specific diagnostic.
27722772
auto *keyPathRoot =
@@ -2786,12 +2786,12 @@ TypeVariableBinding::fixForHole(ConstraintSystem &cs) const {
27862786
}
27872787
}
27882788

2789-
if (TypeVar->getImpl().isClosureParameterType()) {
2789+
if (dstLocator->isClosureParameterType()) {
27902790
ConstraintFix *fix = SpecifyClosureParameterType::create(cs, dstLocator);
27912791
return std::make_pair(fix, defaultImpact);
27922792
}
27932793

2794-
if (TypeVar->getImpl().isClosureResultType()) {
2794+
if (dstLocator->isClosureResultType()) {
27952795
auto *closure = castToExpr<ClosureExpr>(dstLocator->getAnchor());
27962796
// If the whole body is being ignored due to a pre-check failure,
27972797
// let's not record a fix about result type since there is
@@ -2910,15 +2910,17 @@ static bool shouldIgnoreHoleForCodeCompletion(ConstraintSystem &cs,
29102910
ConstraintLocator *srcLocator) {
29112911
if (!cs.isForCodeCompletion())
29122912
return false;
2913-
2913+
2914+
auto *holeLoc = cs.getHoleLocator(typeVar);
2915+
29142916
// Don't penalize solutions with unresolved generics.
2915-
if (typeVar->getImpl().getGenericParameter())
2917+
if (holeLoc->getGenericParameter())
29162918
return true;
29172919

29182920
// Don't penalize solutions if we couldn't determine the type of the code
29192921
// completion token. We still want to examine the surrounding types in
29202922
// that case.
2921-
if (typeVar->getImpl().isCodeCompletionToken())
2923+
if (holeLoc->directlyAt<CodeCompletionExpr>())
29222924
return true;
29232925

29242926
// When doing completion in a result builder, we avoid solving unrelated
@@ -2946,7 +2948,7 @@ static bool shouldIgnoreHoleForCodeCompletion(ConstraintSystem &cs,
29462948
if (cs.hasArgumentsIgnoredForCodeCompletion()) {
29472949
// Avoid simplifying the locator if the constraint system didn't ignore
29482950
// any arguments.
2949-
auto argExpr = simplifyLocatorToAnchor(typeVar->getImpl().getLocator());
2951+
auto argExpr = simplifyLocatorToAnchor(holeLoc);
29502952
if (cs.isArgumentIgnoredForCodeCompletion(argExpr.dyn_cast<Expr *>())) {
29512953
return true;
29522954
}

lib/Sema/ConstraintSystem.cpp

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,31 @@ void ConstraintSystem::restoreType(const KeyPathExpr *KP, unsigned I, Type T) {
972972
}
973973
}
974974

975+
TypeVariableType *ConstraintSystem::getHoleTypeVar(TypeVariableType *tv) {
976+
if (!tv->getImpl().isNonRepresentativeHole())
977+
return tv;
978+
979+
// If we have a non-representative hole, the hole type var is given by
980+
// a member of its equivalence class. Pick the the one with the lowest ID to
981+
// ensure consistency.
982+
TypeVariableType *candidate = nullptr;
983+
for (auto equiv : CG[tv].getEquivalenceClass()) {
984+
auto &impl = equiv->getImpl();
985+
if (equiv == tv || !impl.canBindToHole() || impl.isNonRepresentativeHole())
986+
continue;
987+
988+
if (!candidate || candidate->getID() > equiv->getID())
989+
candidate = equiv;
990+
}
991+
ASSERT(candidate &&
992+
"Non-representative hole ought to have hole in its equivalence class");
993+
return candidate;
994+
}
995+
996+
ConstraintLocator *ConstraintSystem::getHoleLocator(TypeVariableType *tv) {
997+
return getHoleTypeVar(tv)->getImpl().getLocator();
998+
}
999+
9751000
std::pair<Type, ExistentialArchetypeType *>
9761001
ConstraintSystem::openAnyExistentialType(Type type,
9771002
ConstraintLocator *locator) {
@@ -5320,7 +5345,9 @@ TypeVarBindingProducer::TypeVarBindingProducer(BindingSet &bindings)
53205345
bindings.getTypeVariable()->getImpl().getLocator()),
53215346
TypeVar(bindings.getTypeVariable()), CanBeNil(bindings.canBeNil()) {
53225347
if (bindings.isDirectHole()) {
5323-
auto *locator = getLocator();
5348+
auto *holeTV = CS.getHoleTypeVar(TypeVar);
5349+
auto *locator = holeTV->getImpl().getLocator();
5350+
53245351
// If this type variable is associated with a code completion token
53255352
// and it failed to infer any bindings let's adjust holes's locator
53265353
// to point to a code completion token to avoid attempting to "fix"
@@ -5331,7 +5358,7 @@ TypeVarBindingProducer::TypeVarBindingProducer(BindingSet &bindings)
53315358
CS.getConstraintLocator(bindings.getAssociatedCodeCompletionToken());
53325359
}
53335360

5334-
Bindings.push_back(Binding::forHole(TypeVar, locator));
5361+
Bindings.push_back(Binding::forHole(holeTV, locator));
53355362
return;
53365363
}
53375364

test/Constraints/diagnostics.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,3 +1585,13 @@ do {
15851585
let x: String = 0 // expected-error {{cannot convert value of type 'Int' to specified type 'String'}}
15861586
}. // expected-error {{expected member name following '.'}}
15871587
}
1588+
1589+
func testArrayLiteralMetatypeMismatch() {
1590+
// Make sure we can correctly turn 'T' into a hole here.
1591+
func foo<T>(_ xs: T.Type, _: Int) -> T {} // expected-note {{in call to function 'foo'}}
1592+
func foo(_ i: Int) {
1593+
let x = foo([], i)
1594+
// expected-error@-1 {{generic parameter 'T' could not be inferred}}
1595+
// expected-error@-2 {{cannot convert value of type '[Any]' to expected argument type 'T.Type'}}
1596+
}
1597+
}

test/Constraints/rdar45511837.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ class Foo<Bar: NSObject> {
1818

1919
lazy var foo: () -> Void = {
2020
// TODO: improve diagnostic message
21-
_ = self.foobar + nil // expected-error {{'Bar' is not convertible to 'String'}}
22-
// expected-note@-1 {{did you mean to use 'as!' to force downcast?}}
23-
// expected-error@-2 {{'nil' is not compatible with expected argument type 'String'}}
21+
_ = self.foobar + nil // expected-error {{generic parameter 'Self' could not be inferred}}
2422
}
2523
}

validation-test/Sema/SwiftUI/rdar88256059.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ struct MyView: View {
99

1010
var body: some View {
1111
Table(self.data) {
12-
// expected-error@-1 {{expected expression of type 'Columns' in result builder 'TableColumnBuilder'}} {{23-23=<#T##Columns#>}}
12+
// expected-error@-1 {{expected expression of type 'Column' in result builder 'TableColumnBuilder'}} {{23-23=<#T##Column#>}}
13+
// expected-error@-2 {{generic parameter 'Column' could not be inferred}}
1314
}
1415
}
1516
}

0 commit comments

Comments
 (0)