Skip to content

Commit 9875fcf

Browse files
committed
[CSBindings] Apply more checking to transitively inferred bindings
This makes sure that we never run into infinite recursion situations with transitive bindings that could have been aseembled and not properly checked.
1 parent 78c1e1b commit 9875fcf

File tree

3 files changed

+56
-10
lines changed

3 files changed

+56
-10
lines changed

include/swift/Sema/CSBindings.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ class BindingSet {
388388
BindingSet(const PotentialBindings &info)
389389
: CS(info.CS), TypeVar(info.TypeVar), Info(info) {
390390
for (const auto &binding : info.Bindings)
391-
addBinding(binding);
391+
addBinding(binding, /*isTransitive=*/false);
392392

393393
for (auto *literal : info.Literals)
394394
addLiteralRequirement(literal);
@@ -479,7 +479,12 @@ class BindingSet {
479479
}
480480

481481
/// Check if this binding is viable for inclusion in the set.
482-
bool isViable(PotentialBinding &binding);
482+
///
483+
/// \param binding The binding to validate.
484+
/// \param isTransitive Indicates whether this binding has been
485+
/// acquired through transitive inference and requires extra
486+
/// checking.
487+
bool isViable(PotentialBinding &binding, bool isTransitive);
483488

484489
explicit operator bool() const {
485490
return hasViableBindings() || isDirectHole();
@@ -618,7 +623,13 @@ class BindingSet {
618623
void dump(llvm::raw_ostream &out, unsigned indent) const;
619624

620625
private:
621-
void addBinding(PotentialBinding binding);
626+
/// Add a new binding to the set.
627+
///
628+
/// \param binding The binding to add.
629+
/// \param isTransitive Indicates whether this binding has been
630+
/// acquired through transitive inference and requires validity
631+
/// checking.
632+
void addBinding(PotentialBinding binding, bool isTransitive);
622633

623634
void addLiteralRequirement(Constraint *literal);
624635

lib/Sema/CSBindings.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ using namespace swift;
2727
using namespace constraints;
2828
using namespace inference;
2929

30+
static llvm::Optional<Type> checkTypeOfBinding(TypeVariableType *typeVar,
31+
Type type);
32+
3033
bool BindingSet::forClosureResult() const {
3134
return Info.TypeVar->getImpl().isClosureResultType();
3235
}
@@ -473,7 +476,7 @@ void BindingSet::inferTransitiveBindings(
473476

474477
// Copy the bindings over to the root.
475478
for (const auto &binding : bindings.Bindings)
476-
addBinding(binding);
479+
addBinding(binding, /*isTransitive=*/true);
477480

478481
// Make a note that the key path root is transitively adjacent
479482
// to contextual root type variable and all of its variables.
@@ -484,7 +487,8 @@ void BindingSet::inferTransitiveBindings(
484487
}
485488
} else {
486489
addBinding(
487-
binding.withSameSource(inferredRootTy, BindingKind::Exact));
490+
binding.withSameSource(inferredRootTy, BindingKind::Exact),
491+
/*isTransitive=*/true);
488492
}
489493
}
490494
}
@@ -553,7 +557,8 @@ void BindingSet::inferTransitiveBindings(
553557
if (ConstraintSystem::typeVarOccursInType(TypeVar, type))
554558
continue;
555559

556-
addBinding(binding.withSameSource(type, BindingKind::Supertypes));
560+
addBinding(binding.withSameSource(type, BindingKind::Supertypes),
561+
/*isTransitive=*/true);
557562
}
558563
}
559564
}
@@ -631,7 +636,8 @@ void BindingSet::finalize(
631636
continue;
632637
}
633638

634-
addBinding({protocolTy, AllowedBindingKind::Exact, constraint});
639+
addBinding({protocolTy, AllowedBindingKind::Exact, constraint},
640+
/*isTransitive=*/false);
635641
}
636642
}
637643
}
@@ -740,11 +746,11 @@ void BindingSet::finalize(
740746
}
741747
}
742748

743-
void BindingSet::addBinding(PotentialBinding binding) {
749+
void BindingSet::addBinding(PotentialBinding binding, bool isTransitive) {
744750
if (Bindings.count(binding))
745751
return;
746752

747-
if (!isViable(binding))
753+
if (!isViable(binding, isTransitive))
748754
return;
749755

750756
SmallPtrSet<TypeVariableType *, 4> referencedTypeVars;
@@ -1165,14 +1171,17 @@ void PotentialBindings::addLiteral(Constraint *constraint) {
11651171
Literals.insert(constraint);
11661172
}
11671173

1168-
bool BindingSet::isViable(PotentialBinding &binding) {
1174+
bool BindingSet::isViable(PotentialBinding &binding, bool isTransitive) {
11691175
// Prevent against checking against the same opened nominal type
11701176
// over and over again. Doing so means redundant work in the best
11711177
// case. In the worst case, we'll produce lots of duplicate solutions
11721178
// for this constraint system, which is problematic for overload
11731179
// resolution.
11741180
auto type = binding.BindingType;
11751181

1182+
if (isTransitive && !checkTypeOfBinding(TypeVar, type))
1183+
return false;
1184+
11761185
auto *NTD = type->getAnyNominal();
11771186
if (!NTD)
11781187
return true;

test/Sema/keypath_bidirectional_inference.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,29 @@ func testCollectionUpcastWithTupleLabelErasure() {
8282
.sorted(by: \.key.rawValue) // Ok
8383
}
8484
}
85+
86+
do {
87+
struct URL {
88+
var path: String
89+
func appendingPathComponent(_: String) -> URL { fatalError() }
90+
}
91+
92+
struct EntryPoint {
93+
var directory: URL { fatalError() }
94+
}
95+
96+
func test(entryPoint: EntryPoint, data: [[String]]) {
97+
let _ = data.map { suffixes in
98+
let elements = ["a", "b"]
99+
.flatMap { dir in
100+
let directory = entryPoint.directory.appendingPathComponent(dir)
101+
return suffixes.map { suffix in
102+
directory.appendingPathComponent("\(suffix)")
103+
}
104+
}
105+
.map(\.path) // Ok
106+
107+
return elements.joined(separator: ",")
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)