Skip to content

Commit 0ea0b8e

Browse files
committed
[ConstraintSystem] Adjust recording of "fixed" requirements to avoid conflicts
Currently it's possible to have a type conflict between different requirements deduced as the same type which leads to incorrect diagnostics. To mitigate that let's adjust how "fixed" requirements are stored - instead of using resolved type for the left-hand side, let's use originating generic parameter type.
1 parent c4f4ce1 commit 0ea0b8e

File tree

10 files changed

+146
-34
lines changed

10 files changed

+146
-34
lines changed

lib/Sema/CSSimplify.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2339,7 +2339,7 @@ ConstraintSystem::matchExistentialTypes(Type type1, Type type2,
23392339

23402340
auto *fix = fixRequirementFailure(*this, type1, type2, locator);
23412341
if (fix && !recordFix(fix)) {
2342-
recordFixedRequirement(type1, RequirementKind::Layout, type2);
2342+
recordFixedRequirement(getConstraintLocator(locator), type2);
23432343
return getTypeMatchSuccess();
23442344
}
23452345
}
@@ -3875,14 +3875,13 @@ bool ConstraintSystem::repairFailures(
38753875
if (hasConversionOrRestriction(ConversionRestrictionKind::DeepEquality))
38763876
break;
38773877

3878-
auto reqElt = elt.castTo<LocatorPathElt::AnyRequirement>();
3879-
auto reqKind = reqElt.getRequirementKind();
3878+
auto *reqLoc = getConstraintLocator(locator);
38803879

3881-
if (hasFixedRequirement(lhs, reqKind, rhs))
3880+
if (isFixedRequirement(reqLoc, rhs))
38823881
return true;
38833882

38843883
if (auto *fix = fixRequirementFailure(*this, lhs, rhs, anchor, path)) {
3885-
recordFixedRequirement(lhs, reqKind, rhs);
3884+
recordFixedRequirement(reqLoc, rhs);
38863885
conversionsOrFixes.push_back(fix);
38873886
}
38883887
break;
@@ -5404,7 +5403,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
54045403
auto protocolTy = protocol->getDeclaredType();
54055404

54065405
// If this conformance has been fixed already, let's just consider this done.
5407-
if (hasFixedRequirement(type, RequirementKind::Conformance, protocolTy))
5406+
if (isFixedRequirement(getConstraintLocator(locator), protocolTy))
54085407
return SolutionKind::Solved;
54095408

54105409
// If this is a generic requirement let's try to record that
@@ -5491,7 +5490,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
54915490
auto impact = assessRequirementFailureImpact(*this, rawType, locator);
54925491
if (!recordFix(fix, impact)) {
54935492
// Record this conformance requirement as "fixed".
5494-
recordFixedRequirement(type, RequirementKind::Conformance,
5493+
recordFixedRequirement(getConstraintLocator(anchor, path),
54955494
protocolTy);
54965495
return SolutionKind::Solved;
54975496
}

lib/Sema/ConstraintSystem.cpp

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4810,3 +4810,103 @@ SourceLoc constraints::getLoc(ASTNode anchor) {
48104810
SourceRange constraints::getSourceRange(ASTNode anchor) {
48114811
return anchor.getSourceRange();
48124812
}
4813+
4814+
static Optional<Requirement> getRequirement(ConstraintSystem &cs,
4815+
ConstraintLocator *reqLocator) {
4816+
auto reqLoc = reqLocator->getLastElementAs<LocatorPathElt::AnyRequirement>();
4817+
if (!reqLoc)
4818+
return None;
4819+
4820+
if (reqLoc->isConditionalRequirement()) {
4821+
auto path = reqLocator->getPath();
4822+
auto *typeReqLoc =
4823+
cs.getConstraintLocator(reqLocator->getAnchor(), path.drop_back());
4824+
4825+
auto conformances = cs.getCheckedConformances();
4826+
auto result = llvm::find_if(
4827+
conformances,
4828+
[&typeReqLoc](
4829+
const std::pair<ConstraintLocator *, ProtocolConformanceRef>
4830+
&conformance) { return conformance.first == typeReqLoc; });
4831+
assert(result != conformances.end());
4832+
4833+
auto conformance = result->second;
4834+
assert(conformance.isConcrete());
4835+
4836+
return conformance.getConditionalRequirements()[reqLoc->getIndex()];
4837+
}
4838+
4839+
if (auto openedGeneric =
4840+
reqLocator->findLast<LocatorPathElt::OpenedGeneric>()) {
4841+
auto signature = openedGeneric->getSignature();
4842+
return signature->getRequirements()[reqLoc->getIndex()];
4843+
}
4844+
4845+
return None;
4846+
}
4847+
4848+
static Optional<std::pair<GenericTypeParamType *, RequirementKind>>
4849+
getRequirementInfo(ConstraintSystem &cs, ConstraintLocator *reqLocator) {
4850+
auto requirement = getRequirement(cs, reqLocator);
4851+
if (!requirement)
4852+
return None;
4853+
4854+
auto *GP = requirement->getFirstType()->getAs<GenericTypeParamType>();
4855+
if (!GP)
4856+
return None;
4857+
4858+
auto path = reqLocator->getPath();
4859+
auto iter = path.rbegin();
4860+
auto openedGeneric =
4861+
reqLocator->findLast<LocatorPathElt::OpenedGeneric>(iter);
4862+
assert(openedGeneric);
4863+
4864+
auto newPath = path.drop_back(iter - path.rbegin() + 1);
4865+
auto *baseLoc = cs.getConstraintLocator(reqLocator->getAnchor(), newPath);
4866+
4867+
auto openedTypes = cs.getOpenedTypes();
4868+
auto substitutions = llvm::find_if(
4869+
openedTypes,
4870+
[&baseLoc](
4871+
const std::pair<ConstraintLocator *, ArrayRef<OpenedType>> &entry) {
4872+
return entry.first == baseLoc;
4873+
});
4874+
4875+
if (substitutions == openedTypes.end())
4876+
return None;
4877+
4878+
auto replacement =
4879+
llvm::find_if(substitutions->second, [&GP](const OpenedType &entry) {
4880+
auto *typeVar = entry.second;
4881+
return typeVar->getImpl().getGenericParameter() == GP;
4882+
});
4883+
4884+
if (replacement == substitutions->second.end())
4885+
return None;
4886+
4887+
auto *repr = cs.getRepresentative(replacement->second);
4888+
return std::make_pair(repr->getImpl().getGenericParameter(),
4889+
requirement->getKind());
4890+
}
4891+
4892+
bool ConstraintSystem::isFixedRequirement(ConstraintLocator *reqLocator,
4893+
Type requirementTy) {
4894+
if (auto reqInfo = getRequirementInfo(*this, reqLocator)) {
4895+
auto *GP = reqInfo->first;
4896+
auto reqKind = static_cast<unsigned>(reqInfo->second);
4897+
return FixedRequirements.count(
4898+
std::make_tuple(GP, reqKind, requirementTy.getPointer()));
4899+
}
4900+
4901+
return false;
4902+
}
4903+
4904+
void ConstraintSystem::recordFixedRequirement(ConstraintLocator *reqLocator,
4905+
Type requirementTy) {
4906+
if (auto reqInfo = getRequirementInfo(*this, reqLocator)) {
4907+
auto *GP = reqInfo->first;
4908+
auto reqKind = static_cast<unsigned>(reqInfo->second);
4909+
FixedRequirements.insert(
4910+
std::make_tuple(GP, reqKind, requirementTy.getPointer()));
4911+
}
4912+
}

lib/Sema/ConstraintSystem.h

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1947,20 +1947,13 @@ class ConstraintSystem {
19471947

19481948
/// The list of all generic requirements fixed along the current
19491949
/// solver path.
1950-
using FixedRequirement = std::tuple<TypeBase *, RequirementKind, TypeBase *>;
1951-
SmallVector<FixedRequirement, 4> FixedRequirements;
1950+
using FixedRequirement =
1951+
std::tuple<GenericTypeParamType *, unsigned, TypeBase *>;
1952+
llvm::SmallSetVector<FixedRequirement, 4> FixedRequirements;
19521953

1953-
bool hasFixedRequirement(Type lhs, RequirementKind kind, Type rhs) {
1954-
auto reqInfo = std::make_tuple(lhs.getPointer(), kind, rhs.getPointer());
1955-
return llvm::any_of(
1956-
FixedRequirements,
1957-
[&reqInfo](const FixedRequirement &entry) { return entry == reqInfo; });
1958-
}
1959-
1960-
void recordFixedRequirement(Type lhs, RequirementKind kind, Type rhs) {
1961-
FixedRequirements.push_back(
1962-
std::make_tuple(lhs.getPointer(), kind, rhs.getPointer()));
1963-
}
1954+
bool isFixedRequirement(ConstraintLocator *reqLocator, Type requirementTy);
1955+
void recordFixedRequirement(ConstraintLocator *reqLocator,
1956+
Type requirementTy);
19641957

19651958
/// A mapping from constraint locators to the opened existential archetype
19661959
/// used for the 'self' of an existential type.
@@ -3546,6 +3539,19 @@ class ConstraintSystem {
35463539
const DeclRefExpr *base = nullptr,
35473540
OpenedTypeMap *replacements = nullptr);
35483541

3542+
/// Retrieve a list of conformances established along the current solver path.
3543+
ArrayRef<std::pair<ConstraintLocator *, ProtocolConformanceRef>>
3544+
getCheckedConformances() const {
3545+
return CheckedConformances;
3546+
}
3547+
3548+
/// Retrieve a list of generic parameter types solver has "opened" (replaced
3549+
/// with a type variable) along the current path.
3550+
ArrayRef<std::pair<ConstraintLocator *, ArrayRef<OpenedType>>>
3551+
getOpenedTypes() const {
3552+
return OpenedTypes;
3553+
}
3554+
35493555
private:
35503556
/// Adjust the constraint system to accomodate the given selected overload, and
35513557
/// recompute the type of the referenced declaration.

test/AutoDiff/Sema/differentiable_func_type.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,10 @@ extension Vector: Differentiable where T: Differentiable {
175175
mutating func move(along direction: TangentVector) { fatalError() }
176176
}
177177

178-
// expected-note@+1 2 {{candidate requires that 'Int' conform to 'Differentiable' (requirement specified as 'T' == 'Differentiable')}}
178+
// expected-note@+1 2 {{found this candidate}}
179179
func inferredConformancesGeneric<T, U>(_: @differentiable (Vector<T>) -> Vector<U>) {}
180180

181-
// expected-note @+5 2 {{candidate requires that 'Int' conform to 'Differentiable' (requirement specified as 'T' == 'Differentiable')}}
181+
// expected-note @+5 2 {{found this candidate}}
182182
// expected-error @+4 {{generic signature requires types 'Vector<T>' and 'Vector<T>.TangentVector' to be the same}}
183183
// expected-error @+3 {{generic signature requires types 'Vector<U>' and 'Vector<U>.TangentVector' to be the same}}
184184
// expected-error @+2 {{parameter type 'Vector<T>' does not conform to 'Differentiable' and satisfy 'Vector<T> == Vector<T>.TangentVector', but the enclosing function type is '@differentiable(linear)'}}
@@ -210,10 +210,10 @@ extension Linear: Differentiable where T: Differentiable, T == T.TangentVector {
210210
typealias TangentVector = Self
211211
}
212212

213-
// expected-note @+1 2 {{candidate requires that 'Int' conform to 'Differentiable' (requirement specified as 'T' == 'Differentiable')}}
213+
// expected-note @+1 2 {{found this candidate}}
214214
func inferredConformancesGeneric<T, U>(_: @differentiable (Linear<T>) -> Linear<U>) {}
215215

216-
// expected-note @+1 2 {{candidate requires that 'Int' conform to 'Differentiable' (requirement specified as 'T' == 'Differentiable')}}
216+
// expected-note @+1 2 {{found this candidate}}
217217
func inferredConformancesGenericLinear<T, U>(_: @differentiable(linear) (Linear<T>) -> Linear<U>) {}
218218

219219
func nondiff(x: Linear<Int>) -> Linear<Int> {}

test/Constraints/diagnostics.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ enum AssocTest {
687687
case one(Int)
688688
}
689689

690-
if AssocTest.one(1) == AssocTest.one(1) {} // expected-error{{binary operator '==' cannot be applied to two 'AssocTest' operands}}
690+
if AssocTest.one(1) == AssocTest.one(1) {} // expected-error{{referencing operator function '==' on 'Equatable' requires that 'AssocTest' conform to 'Equatable'}}
691691
// expected-note @-1 {{binary operator '==' cannot be synthesized for enums with associated values}}
692692

693693

@@ -1100,9 +1100,13 @@ func rdar17170728() {
11001100
}
11011101

11021102
let _ = [i, j, k].reduce(0 as Int?) {
1103+
// expected-error@-1 3 {{cannot convert value of type 'Int?' to expected element type 'Int'}}
11031104
$0 && $1 ? $0 + $1 : ($0 ? $0 : ($1 ? $1 : nil))
1104-
// expected-error@-1 {{binary operator '+' cannot be applied to two 'Int?' operands}}
1105-
// expected-error@-2 4 {{optional type 'Int?' cannot be used as a boolean; test for '!= nil' instead}}
1105+
// expected-error@-1 2 {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
1106+
// expected-error@-2 {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
1107+
// expected-error@-3 2 {{optional type 'Int?' cannot be used as a boolean; test for '!= nil' instead}}
1108+
// expected-note@-4:16 {{coalesce using '??' to provide a default when the optional value contains 'nil'}}
1109+
// expected-note@-5:16 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}
11061110
}
11071111
}
11081112

test/Constraints/operator.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,5 +288,6 @@ func rdar_62054241() {
288288

289289
func test(_ arr: [Foo]) -> [Foo] {
290290
return arr.sorted(by: <) // expected-error {{no exact matches in reference to operator function '<'}}
291+
// expected-note@-1 {{found candidate with type '(Foo, Foo) -> Bool'}}
291292
}
292293
}

test/Generics/deduction.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,7 @@ protocol Addable {
247247
static func +(x: Self, y: Self) -> Self
248248
}
249249
func addAddables<T : Addable, U>(_ x: T, y: T, u: U) -> T {
250-
// FIXME(diagnostics): This should report the "no exact matches" diagnostic.
251-
u + u // expected-error{{referencing operator function '+' on 'RangeReplaceableCollection' requires that 'U' conform to 'RangeReplaceableCollection'}}
250+
u + u // expected-error{{binary operator '+' cannot be applied to two 'U' operands}}
252251
return x+y
253252
}
254253

test/Sema/struct_equatable_hashable.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ struct StructWithoutExplicitConformance {
114114
}
115115

116116
func structWithoutExplicitConformance() {
117-
if StructWithoutExplicitConformance(a: 1, b: "b") == StructWithoutExplicitConformance(a: 2, b: "a") { } // expected-error{{binary operator '==' cannot be applied to two 'StructWithoutExplicitConformance' operands}}
117+
// This diagnostic is about `Equatable` because it's considered the best possible solution among other ones for operator `==`.
118+
if StructWithoutExplicitConformance(a: 1, b: "b") == StructWithoutExplicitConformance(a: 2, b: "a") { } // expected-error{{referencing operator function '==' on 'Equatable' requires that 'StructWithoutExplicitConformance' conform to 'Equatable'}}
118119
}
119120

120121
// Structs with non-hashable/equatable stored properties don't derive conformance.

test/expr/unary/keypath/salvage-with-other-type-errors.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
struct P<T: K> { }
77

88
struct S {
9-
init<B>(_ a: P<B>) {
9+
init<B>(_ a: P<B>) { // expected-note {{in call to initializer}}
1010
fatalError()
1111
}
1212
}
1313

1414
protocol K { }
1515

16-
func + <Object>(lhs: KeyPath<A, Object>, rhs: String) -> P<Object> { // expected-note {{where 'Object' = 'String'}}
16+
func + <Object>(lhs: KeyPath<A, Object>, rhs: String) -> P<Object> {
1717
fatalError()
1818
}
1919

@@ -27,7 +27,9 @@ struct A {
2727
}
2828

2929
extension A: K {
30-
static let j = S(\A.id + "id") // expected-error {{operator function '+' requires that 'String' conform to 'K'}}
30+
static let j = S(\A.id + "id") // expected-error {{generic parameter 'B' could not be inferred}}
31+
// expected-error@-1 {{binary operator '+' cannot be applied to operands of type 'KeyPath<A, String>' and 'String'}}
32+
// expected-note@-2 {{overloads for '+' exist with these partially matching parameter lists: (String, String)}}
3133
}
3234

3335
// SR-5034

test/stdlib/UnicodeScalarDiagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ func test_UnicodeScalarDoesNotImplementArithmetic(_ us: UnicodeScalar, i: Int) {
1212
let a3 = "a" * "b" // expected-error {{binary operator '*' cannot be applied to two 'String' operands}}
1313
let a4 = "a" / "b" // expected-error {{binary operator '/' cannot be applied to two 'String' operands}}
1414

15-
let b1 = us + us // expected-error {{referencing operator function '+' on 'RangeReplaceableCollection' requires that 'UnicodeScalar' (aka 'Unicode.Scalar') conform to 'RangeReplaceableCollection'}}
15+
let b1 = us + us // expected-error {{binary operator '+' cannot be applied to two 'UnicodeScalar' (aka 'Unicode.Scalar') operands}}
1616
let b2 = us - us // expected-error {{binary operator '-' cannot be applied to two 'UnicodeScalar' (aka 'Unicode.Scalar') operands}}
1717
let b3 = us * us // expected-error {{binary operator '*' cannot be applied to two 'UnicodeScalar' (aka 'Unicode.Scalar') operands}}
1818
let b4 = us / us // expected-error {{binary operator '/' cannot be applied to two 'UnicodeScalar' (aka 'Unicode.Scalar') operands}}

0 commit comments

Comments
 (0)