Skip to content

[CS] Explore additional bindings for fixes #30686

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
Mar 29, 2020
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
89 changes: 58 additions & 31 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1369,37 +1369,12 @@ static ConstraintFix *fixRequirementFailure(ConstraintSystem &cs, Type type1,
static unsigned
assessRequirementFailureImpact(ConstraintSystem &cs, Type requirementType,
ConstraintLocatorBuilder locator) {
assert(requirementType);

unsigned impact = 1;
auto *anchor = locator.getAnchor();
if (!anchor)
return 1;

if (requirementType && cs.simplifyType(requirementType)->isStdlibType()) {
if (auto last = locator.last()) {
if (auto requirement = last->getAs<LocatorPathElt::AnyRequirement>()) {
auto kind = requirement->getRequirementKind();
if (kind == RequirementKind::Conformance)
return 3;
}
}
}

// If this requirement is associated with an overload choice let's
// tie impact to how many times this requirement type is mentioned.
if (auto *ODRE = dyn_cast<OverloadedDeclRefExpr>(anchor)) {
if (!(requirementType && requirementType->is<TypeVariableType>()))
return 1;

unsigned choiceImpact = 0;
if (auto choice = cs.findSelectedOverloadFor(ODRE)) {
auto *typeVar = requirementType->castTo<TypeVariableType>();
choice->openedType.visit([&](Type type) {
if (type->isEqual(typeVar))
++choiceImpact;
});
}

return choiceImpact == 0 ? 1 : choiceImpact;
}
return impact;

// If this requirement is associated with a member reference and it
// was possible to check it before overload choice is bound, that means
Expand All @@ -1417,13 +1392,53 @@ assessRequirementFailureImpact(ConstraintSystem &cs, Type requirementType,
// fix for same-type requirement higher impact vs. requirement associated
// with method itself e.g. `func foo<U>() -> U where U : P {}` because
// `foo` is accessible from any `S` regardless of what `T` is.
//
// Don't add this impact with the others, as we want to keep it consistent
// across requirement failures to present the user with a choice.
if (isa<UnresolvedDotExpr>(anchor) || isa<UnresolvedMemberExpr>(anchor)) {
auto *calleeLoc = cs.getCalleeLocator(cs.getConstraintLocator(locator));
if (!cs.findSelectedOverloadFor(calleeLoc))
return 10;
}

return 1;
// Increase the impact of a conformance fix for a standard library type,
// as it's unlikely to be a good suggestion. Also do the same for the builtin
// compiler types Any and AnyObject, which cannot conform to protocols.
// FIXME: We ought not to have the is<TypeVariableType>() condition here, but
// removing it currently regresses the diagnostic for the test case for
// rdar://60727310. Once we better handle the separation of conformance fixes
// from argument mismatches in cases like SR-12438, we should be able to
// remove it from the condition.
auto resolvedTy = cs.simplifyType(requirementType);
if ((requirementType->is<TypeVariableType>() && resolvedTy->isStdlibType()) ||
resolvedTy->isAny() || resolvedTy->isAnyObject()) {
if (auto last = locator.last()) {
if (auto requirement = last->getAs<LocatorPathElt::AnyRequirement>()) {
auto kind = requirement->getRequirementKind();
if (kind == RequirementKind::Conformance)
impact += 2;
}
}
}

// If this requirement is associated with an overload choice let's
// tie impact to how many times this requirement type is mentioned.
if (auto *ODRE = dyn_cast<OverloadedDeclRefExpr>(anchor)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about an idea of not considering requirement failures recoverable in reference to operators?

I'm just trying to figure out how to move a needle in situations like:

func sr12438(_ e: Error) {
  func foo<T>(_ a: T, _ op: ((T, T) -> Bool)) {}
  foo(e, ==) // expected-error {{type of expression is ambiguous without more context}}
}

The problem here is that none of the operator declarations for == are going to match and they are all going to fail in different ways. I think we shouldn't try to rank such situations but instead be better at detecting completely hopeless situations...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, would that be specifically for unapplied operator references, or for operator references in general? Presumably we'd then emit a generic "operator cannot be applied with X operands" diagnostic?

I guess we could just try to detect the case where we have different possible overloads for an operator in diagnoseAmbiguityWithFixes, each with some kind of fix, and call into diagnoseOperatorAmbiguity (though it may need to be tweaked slightly to handle unapplied operator refs).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, would that be specifically for unapplied operator references, or for operator references in general?

In general. I am hoping that improvement is going to be two-fold - fewer operators considered in diagnostics mode and more precise diagnostics when no operator overloads match.

That's what I was thinking as well, the problem is that we need to move diagnoseOperatorAmbiguity before findBestSolution and that could affect some of diagnostics.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general. I am hoping that improvement is going to be two-fold - fewer operators considered in diagnostics mode and more precise diagnostics when no operator overloads match.

My only worry is for people defining their own operators that require conformances for arguments, it would be a shame to lose the requirement fixes there, perhaps we should limit it to known stdlib operators? Or maybe operators that have lots of overloads?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, maybe that should be limited only to stdlib operator overloads only.

if (auto *typeVar = requirementType->getAs<TypeVariableType>()) {
unsigned choiceImpact = 0;
if (auto choice = cs.findSelectedOverloadFor(ODRE)) {
choice->openedType.visit([&](Type type) {
if (type->isEqual(typeVar))
++choiceImpact;
});
}
// If the type is used multiple times in the signature, increase the
// impact for every additional use.
if (choiceImpact > 1)
impact += choiceImpact - 1;
}
}
return impact;
}

/// Attempt to fix missing arguments by introducing type variables
Expand Down Expand Up @@ -3301,6 +3316,17 @@ bool ConstraintSystem::repairFailures(
if (hasFixFor(loc, FixKind::AllowInvalidUseOfTrailingClosure))
return true;

// Don't attempt to fix an argument being passed to a
// _OptionalNilComparisonType parameter. Such an overload should only take
// effect when a nil literal is used in valid code, and doesn't offer any
// useful fixes for invalid code.
if (auto *nominal = rhs->getAnyNominal()) {
if (nominal->isStdlibDecl() &&
nominal->getName() == getASTContext().Id_OptionalNilComparisonType) {
return false;
}
}

if (repairByInsertingExplicitCall(lhs, rhs))
break;

Expand Down Expand Up @@ -5061,6 +5087,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
ConstraintKind kind,
ConstraintLocatorBuilder locator,
TypeMatchOptions flags) {
const auto rawType = type;
auto *typeVar = type->getAs<TypeVariableType>();

// Dig out the fixed type to which this type refers.
Expand Down Expand Up @@ -5232,7 +5259,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(

if (auto *fix =
fixRequirementFailure(*this, type, protocolTy, anchor, path)) {
auto impact = assessRequirementFailureImpact(*this, typeVar, locator);
auto impact = assessRequirementFailureImpact(*this, rawType, locator);
if (!recordFix(fix, impact)) {
// Record this conformance requirement as "fixed".
recordFixedRequirement(type, RequirementKind::Conformance,
Expand Down
12 changes: 12 additions & 0 deletions lib/Sema/CSStep.h
Original file line number Diff line number Diff line change
Expand Up @@ -619,13 +619,25 @@ class TypeVariableStep final : public BindingStep<TypeVarBindingProducer> {
/// Check whether attempting type variable binding choices should
/// be stopped, because optimal solution has already been found.
bool shouldStopAt(const TypeVariableBinding &choice) const override {
// Let's always attempt default types inferred from literals in diagnostic
// mode because that could lead to better diagnostics if the problem is
// contextual like argument/parameter conversion or collection element
// mismatch.
if (CS.shouldAttemptFixes())
return false;

// If we were able to solve this without considering
// default literals, don't bother looking at default literals.
return AnySolved && choice.hasDefaultedProtocol() &&
!SawFirstLiteralConstraint;
}

bool shouldStopAfter(const TypeVariableBinding &choice) const override {
// Let's always attempt additional bindings in diagnostic mode, as that
// could lead to better diagnostic for e.g trying the unwrapped type.
if (CS.shouldAttemptFixes())
return false;

// If there has been at least one solution so far
// at a current batch of bindings is done it's a
// success because each new batch would be less
Expand Down
1 change: 1 addition & 0 deletions test/Constraints/bridging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ func rdar19831698() {
var v71 = true + 1.0 // expected-error{{binary operator '+' cannot be applied to operands of type 'Bool' and 'Double'}}
// expected-note@-1{{overloads for '+'}}
var v72 = true + true // expected-error{{binary operator '+' cannot be applied to two 'Bool' operands}}
// expected-note@-1{{overloads for '+'}}
var v73 = true + [] // expected-error@:13 {{cannot convert value of type 'Bool' to expected argument type 'Array<Bool>'}}
var v75 = true + "str" // expected-error@:13 {{cannot convert value of type 'Bool' to expected argument type 'String'}}
}
Expand Down
6 changes: 3 additions & 3 deletions test/Constraints/closures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -273,17 +273,17 @@ func verify_NotAC_to_AC_failure(_ arg: () -> ()) {
// SR-1069 - Error diagnostic refers to wrong argument
class SR1069_W<T> {
func append<Key: AnyObject>(value: T, forKey key: Key) where Key: Hashable {}
// expected-note@-1 {{where 'Key' = 'Object?'}}
}
class SR1069_C<T> { let w: SR1069_W<(AnyObject, T) -> ()> = SR1069_W() }
struct S<T> {
let cs: [SR1069_C<T>] = []

func subscribe<Object: AnyObject>(object: Object?, method: (Object, T) -> ()) where Object: Hashable {
let wrappedMethod = { (object: AnyObject, value: T) in }
// expected-error @+2 {{instance method 'append(value:forKey:)' requires that 'Object?' be a class type}}
// expected-note @+1 {{wrapped type 'Object' satisfies this requirement}}
cs.forEach { $0.w.append(value: wrappedMethod, forKey: object) }
// expected-error@-1 {{value of optional type 'Object?' must be unwrapped to a value of type 'Object'}}
// expected-note@-2 {{coalesce using '??' to provide a default when the optional value contains 'nil'}}
// expected-note@-3 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}
}
}

Expand Down
24 changes: 17 additions & 7 deletions test/Constraints/diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ func f7() -> (c: Int, v: A) {
}

func f8<T:P2>(_ n: T, _ f: @escaping (T) -> T) {} // expected-note {{where 'T' = 'Int'}}
// expected-note@-1 {{required by global function 'f8' where 'T' = '(Int, Double)'}}
// expected-note@-1 {{required by global function 'f8' where 'T' = 'Tup' (aka '(Int, Double)')}}
f8(3, f4) // expected-error {{global function 'f8' requires that 'Int' conform to 'P2'}}
typealias Tup = (Int, Double)
func f9(_ x: Tup) -> Tup { return x }
f8((1,2.0), f9) // expected-error {{type '(Int, Double)' cannot conform to 'P2'; only struct/enum/class types can conform to protocols}}
f8((1,2.0), f9) // expected-error {{type 'Tup' (aka '(Int, Double)') cannot conform to 'P2'; only struct/enum/class types can conform to protocols}}

// <rdar://problem/19658691> QoI: Incorrect diagnostic for calling nonexistent members on literals
1.doesntExist(0) // expected-error {{value of type 'Int' has no member 'doesntExist'}}
Expand Down Expand Up @@ -404,11 +404,11 @@ f7(1)(1.0) // expected-error {{cannot convert value of type 'Double' to ex
f7(1)(b: 1.0) // expected-error{{extraneous argument label 'b:' in call}}
// expected-error@-1 {{cannot convert value of type 'Double' to expected argument type 'Int'}}

let f8 = f7(2)
_ = f8(1)
f8(10) // expected-warning {{result of call to function returning 'Int' is unused}}
f8(1.0) // expected-error {{cannot convert value of type 'Double' to expected argument type 'Int'}}
f8(b: 1.0) // expected-error {{extraneous argument label 'b:' in call}}
let f10 = f7(2)
_ = f10(1)
f10(10) // expected-warning {{result of call to function returning 'Int' is unused}}
f10(1.0) // expected-error {{cannot convert value of type 'Double' to expected argument type 'Int'}}
f10(b: 1.0) // expected-error {{extraneous argument label 'b:' in call}}
// expected-error@-1 {{cannot convert value of type 'Double' to expected argument type 'Int'}}


Expand Down Expand Up @@ -1405,3 +1405,13 @@ func foo(frame: Frame) {
// expected-note@-1 {{overloads for '+' exist with these partially matching parameter lists: (Double, Double), (Int, Int)}}

}

// Make sure we prefer the conformance failure.
func f11(_ n: Int) {}
func f11<T : P2>(_ n: T, _ f: @escaping (T) -> T) {} // expected-note {{where 'T' = 'Int'}}
f11(3, f4) // expected-error {{global function 'f11' requires that 'Int' conform to 'P2'}}

// FIXME: Arguably we should also prefer the conformance failure in this case.
let f12: (Int) -> Void = { _ in }
func f12<T : P2>(_ n: T, _ f: @escaping (T) -> T) {}
f12(3, f4)// expected-error {{extra argument in call}}
15 changes: 15 additions & 0 deletions test/Constraints/fixes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ var s1116 = Set(1...10).subtracting(a1116.map({ $0.s })) // expected-error {{val
// expected-note@-1{{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{49-49=(}} {{53-53= ?? <#default value#>)}}
// expected-note@-2{{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{53-53=!}}

func makeArray<T>(_ x: T) -> [T] { [x] }

func sr12399(_ x: Int?) {
_ = Set(0...10).subtracting(makeArray(x)) // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
// expected-note@-1{{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{42-42= ?? <#default value#>}}
// expected-note@-2{{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{42-42=!}}
}

func moreComplexUnwrapFixes() {
struct S {
let value: Int
Expand Down Expand Up @@ -348,3 +356,10 @@ func testKeyPathSubscriptArgFixes(_ fn: @escaping () -> Int) {
_ = \S.[nil] // expected-error {{'nil' is not compatible with expected argument type 'Int'}}
_ = \S.[fn] // expected-error {{function produces expected type 'Int'; did you mean to call it with '()'?}} {{13-13=()}}
}

func sr12426(a: Any, _ str: String?) {
a == str // expected-error {{cannot convert value of type 'Any' to expected argument type 'String'}}
// expected-error@-1 {{value of optional type 'String?' must be unwrapped to a value of type 'String'}}
// expected-note@-2 {{coalesce using '??' to provide a default when the optional value contains 'nil'}}
// expected-note@-3 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}
}
4 changes: 0 additions & 4 deletions test/Constraints/keyword_arguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -798,19 +798,15 @@ func trailingclosure4(f: () -> Int) {}
trailingclosure4 { 5 }

func trailingClosure5<T>(_ file: String = #file, line: UInt = #line, expression: () -> T?) { }
// expected-note@-1 {{in call to function 'trailingClosure5(_:line:expression:)'}}
func trailingClosure6<T>(value: Int, expression: () -> T?) { }
// expected-note@-1 {{in call to function 'trailingClosure6(value:expression:)'}}

trailingClosure5(file: "hello", line: 17) { // expected-error{{extraneous argument label 'file:' in call}}{{18-24=}}
// expected-error@-1 {{generic parameter 'T' could not be inferred}}
return Optional.Some(5)
// expected-error@-1 {{enum type 'Optional<Wrapped>' has no case 'Some'; did you mean 'some'?}} {{19-23=some}}
// expected-error@-2 {{generic parameter 'Wrapped' could not be inferred}}
// expected-note@-3 {{explicitly specify the generic arguments to fix this issue}}
}
trailingClosure6(5) { // expected-error{{missing argument label 'value:' in call}}{{18-18=value: }}
// expected-error@-1 {{generic parameter 'T' could not be inferred}}
return Optional.Some(5)
// expected-error@-1 {{enum type 'Optional<Wrapped>' has no case 'Some'; did you mean 'some'?}} {{19-23=some}}
// expected-error@-2 {{generic parameter 'Wrapped' could not be inferred}}
Expand Down
13 changes: 10 additions & 3 deletions test/Constraints/operator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,11 @@ func rdar46459603() {
var arr = ["key": e]

_ = arr.values == [e]
// expected-error@-1 {{binary operator '==' cannot be applied to operands of type 'Dictionary<String, E>.Values' and '[E]'}}
// expected-error@-1 {{referencing operator function '==' on 'Equatable' requires that 'Dictionary<String, E>.Values' conform to 'Equatable'}}
// expected-error@-2 {{cannot convert value of type '[E]' to expected argument type 'Dictionary<String, E>.Values'}}
_ = [arr.values] == [[e]]
// expected-error@-1 {{value of protocol type 'Any' cannot conform to 'Equatable'; only struct/enum/class types can conform to protocols}}
// expected-note@-2 {{requirement from conditional conformance of '[Any]' to 'Equatable'}}
// expected-error@-1 {{operator function '==' requires that 'Dictionary<String, E>.Values' conform to 'Equatable'}}
// expected-error@-2 {{cannot convert value of type '[E]' to expected element type 'Dictionary<String, E>.Values'}}
}

// SR-10843
Expand Down Expand Up @@ -272,3 +273,9 @@ func rdar60727310() {
var e: Error? = nil
myAssertion(e, ==, nil) // expected-error {{binary operator '==' cannot be applied to two 'Error?' operands}}
}

// FIXME(SR-12438): Bad diagnostic.
func sr12438(_ e: Error) {
func foo<T>(_ a: T, _ op: ((T, T) -> Bool)) {}
foo(e, ==) // expected-error {{type of expression is ambiguous without more context}}
}
5 changes: 3 additions & 2 deletions test/Generics/deduction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,9 @@ func foo() {
let j = min(Int(3), Float(2.5)) // expected-error{{conflicting arguments to generic parameter 'T' ('Int' vs. 'Float')}}
let k = min(A(), A()) // expected-error{{global function 'min' requires that 'A' conform to 'Comparable'}}
let oi : Int? = 5
let l = min(3, oi) // expected-error{{global function 'min' requires that 'Int?' conform to 'Comparable'}}
// expected-note@-1{{wrapped type 'Int' satisfies this requirement}}
let l = min(3, oi) // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}}
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}
}

infix operator +&
Expand Down
5 changes: 4 additions & 1 deletion test/Misc/misc_diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ func test17875634() {

// <rdar://problem/20770032> Pattern matching ranges against tuples crashes the compiler
func test20770032() {
if case let 1...10 = (1, 1) { // expected-warning{{'let' pattern has no effect; sub-pattern didn't bind any variables}} {{11-15=}} expected-error{{expression pattern of type 'ClosedRange<Int>' cannot match values of type '(Int, Int)'}}
if case let 1...10 = (1, 1) { // expected-warning{{'let' pattern has no effect; sub-pattern didn't bind any variables}} {{11-15=}}
// expected-error@-1 {{expression pattern of type 'ClosedRange<Int>' cannot match values of type '(Int, Int)'}}
// expected-error@-2 {{'(Int, Int)' cannot conform to 'Equatable'; only struct/enum/class types can conform to protocols}}
// expected-note@-3 {{required by operator function '~=' where 'T' = '(Int, Int)'}}
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/expr/delayed-ident/static_var.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ var _: HasClosure = .factoryOpt(3)
// expected-note@-2 {{coalesce}}
// expected-note@-3 {{force-unwrap}}
// FIXME: we should accept this
var _: HasClosure = .factoryOpt!(4) // expected-error {{type 'Optional<_>' has no member 'factoryOpt'}}
var _: HasClosure = .factoryOpt!(4) // expected-error {{cannot infer contextual base in reference to member 'factoryOpt'}}