Skip to content

Commit f36ecf2

Browse files
committed
[CSSimplify] Allow overload choices with missing labels to be considered for diagnostics
Let's make use of a newly added "disable for performance" flag to allow solver to consider overload choices where the only issue is missing one or more labels - this makes it for a much better diagnostic experience without any performance impact for valid code.
1 parent 957e05c commit f36ecf2

File tree

9 files changed

+88
-30
lines changed

9 files changed

+88
-30
lines changed

lib/Sema/CSSimplify.cpp

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ bool constraints::doesMemberRefApplyCurriedSelf(Type baseTy,
125125

126126
static bool areConservativelyCompatibleArgumentLabels(
127127
OverloadChoice choice, SmallVectorImpl<FunctionType::Param> &args,
128+
MatchCallArgumentListener &listener,
128129
Optional<unsigned> unlabeledTrailingClosureArgIndex) {
129130
ValueDecl *decl = nullptr;
130131
switch (choice.getKind()) {
@@ -163,7 +164,6 @@ static bool areConservativelyCompatibleArgumentLabels(
163164
auto params = fnType->getParams();
164165
ParameterListInfo paramInfo(params, decl, hasAppliedSelf);
165166

166-
MatchCallArgumentListener listener;
167167
return matchCallArguments(args, params, paramInfo,
168168
unlabeledTrailingClosureArgIndex,
169169
/*allow fixes*/ false, listener,
@@ -1220,6 +1220,32 @@ class ArgumentFailureTracker : public MatchCallArgumentListener {
12201220
}
12211221
};
12221222

1223+
class AllowLabelMismatches : public MatchCallArgumentListener {
1224+
SmallVector<Identifier, 4> NewLabels;
1225+
bool HadLabelingIssues = false;
1226+
1227+
public:
1228+
bool missingLabel(unsigned paramIndex) override {
1229+
HadLabelingIssues = true;
1230+
return false;
1231+
}
1232+
1233+
bool relabelArguments(ArrayRef<Identifier> newLabels) override {
1234+
NewLabels.append(newLabels.begin(), newLabels.end());
1235+
HadLabelingIssues = true;
1236+
return false;
1237+
}
1238+
1239+
bool hadLabelingIssues() const { return HadLabelingIssues; }
1240+
1241+
Optional<ArrayRef<Identifier>> getLabelReplacements() const {
1242+
if (!hadLabelingIssues() || NewLabels.empty())
1243+
return None;
1244+
1245+
return {NewLabels};
1246+
}
1247+
};
1248+
12231249
// Match the argument of a call to the parameter.
12241250
ConstraintSystem::TypeMatchResult constraints::matchCallArguments(
12251251
ConstraintSystem &cs, FunctionType *contextualType,
@@ -9726,11 +9752,57 @@ bool ConstraintSystem::simplifyAppliedOverloadsImpl(
97269752
argsWithLabels.append(args.begin(), args.end());
97279753
FunctionType::relabelParams(argsWithLabels, argumentInfo->Labels);
97289754

9729-
if (!areConservativelyCompatibleArgumentLabels(
9730-
choice, argsWithLabels,
9731-
argumentInfo->UnlabeledTrailingClosureIndex)) {
9755+
auto labelsMatch = [&](MatchCallArgumentListener &listener) {
9756+
if (areConservativelyCompatibleArgumentLabels(
9757+
choice, argsWithLabels, listener,
9758+
argumentInfo->UnlabeledTrailingClosureIndex))
9759+
return true;
9760+
97329761
labelMismatch = true;
97339762
return false;
9763+
};
9764+
9765+
AllowLabelMismatches listener;
9766+
9767+
// This overload has more problems than just missing/invalid labels.
9768+
if (!labelsMatch(listener))
9769+
return false;
9770+
9771+
// If overload did match, let's check if it needs to be disabled
9772+
// in "performance" mode because it has missing labels.
9773+
if (listener.hadLabelingIssues()) {
9774+
// In performance mode, let's just disable the choice,
9775+
// this decision could be rolled back for diagnostics.
9776+
if (!shouldAttemptFixes())
9777+
return false;
9778+
9779+
// Match expected vs. actual to see whether the only kind
9780+
// of problem here is missing label(s).
9781+
auto onlyMissingLabels =
9782+
[&argumentInfo](ArrayRef<Identifier> expectedLabels) {
9783+
auto actualLabels = argumentInfo->Labels;
9784+
if (actualLabels.size() != expectedLabels.size())
9785+
return false;
9786+
9787+
for (unsigned i = 0; i != actualLabels.size(); ++i) {
9788+
auto actual = actualLabels[i];
9789+
auto expected = expectedLabels[i];
9790+
9791+
if (actual.compare(expected) != 0 && !actual.empty())
9792+
return false;
9793+
}
9794+
9795+
return true;
9796+
};
9797+
9798+
auto replacementLabels = listener.getLabelReplacements();
9799+
// Either it's just one argument or all issues are missing labels.
9800+
if (!replacementLabels || onlyMissingLabels(*replacementLabels)) {
9801+
constraint->setDisabled(/*enableForDiagnostics=*/true);
9802+
// Don't include this overload in "common result" computation
9803+
// because it has issues.
9804+
return true;
9805+
}
97349806
}
97359807
}
97369808

test/Constraints/diagnostics.swift

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,18 +1102,10 @@ func rdar17170728() {
11021102
// expected-error@-1 4 {{optional type 'Int?' cannot be used as a boolean; test for '!= nil' instead}}
11031103
}
11041104

1105-
let _ = [i, j, k].reduce(0 as Int?) {
1106-
// expected-error@-1 3 {{cannot convert value of type 'Int?' to expected element type 'Bool'}}
1107-
// expected-error@-2 {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
1108-
// expected-note@-3 {{coalesce using '??' to provide a default when the optional value contains 'nil'}}
1109-
// expected-note@-4 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}
1105+
let _ = [i, j, k].reduce(0 as Int?) { // expected-error {{missing argument label 'into:' in call}}
1106+
// expected-error@-1 {{cannot convert value of type 'Int?' to expected argument type '(inout @escaping (Bool, Bool) -> Bool?, Int?) throws -> ()'}}
11101107
$0 && $1 ? $0 + $1 : ($0 ? $0 : ($1 ? $1 : nil))
1111-
// expected-error@-1 {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
1112-
// expected-error@-2 {{cannot convert value of type 'Bool?' to closure result type 'Int'}}
1113-
// expected-error@-3 {{result values in '? :' expression have mismatching types 'Int' and 'Bool?'}}
1114-
// expected-error@-4 {{cannot convert value of type 'Bool' to expected argument type 'Int'}}
1115-
// expected-error@-5 {{type 'Int' cannot be used as a boolean; test for '!= 0' instead}}
1116-
// expected-error@-6 {{result values in '? :' expression have mismatching types 'Int' and 'Bool?'}}
1108+
// expected-error@-1 {{binary operator '+' cannot be applied to two 'Bool' operands}}
11171109
}
11181110
}
11191111

test/Constraints/generics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ let arr = [BottleLayout]()
648648
let layout = BottleLayout(count:1)
649649
let ix = arr.firstIndex(of:layout) // expected-error {{referencing instance method 'firstIndex(of:)' on 'Collection' requires that 'BottleLayout' conform to 'Equatable'}}
650650

651-
let _: () -> UInt8 = { .init("a" as Unicode.Scalar) } // expected-error {{initializer 'init(_:)' requires that 'Unicode.Scalar' conform to 'BinaryInteger'}}
651+
let _: () -> UInt8 = { .init("a" as Unicode.Scalar) } // expected-error {{missing argument label 'ascii:' in call}}
652652

653653
// https://bugs.swift.org/browse/SR-9068
654654
func compare<C: Collection, Key: Hashable, Value: Equatable>(c: C)

test/Constraints/overload_filtering_objc.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ import Foundation
1919
}
2020

2121
func testOptional(obj: P) {
22-
// CHECK: disabled disjunction term $T2 bound to decl overload_filtering_objc.(file).P.opt(double:)
22+
// CHECK: [disabled] $T2 bound to decl overload_filtering_objc.(file).P.opt(double:)
2323
_ = obj.opt?(1)
2424
}

test/Constraints/result_builder_diags.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ func erroneousSR11350(x: Int) {
249249
if b {
250250
acceptInt(0) { }
251251
}
252-
}).domap(0) // expected-error{{value of type 'Optional<()>' has no member 'domap'}}
252+
}).domap(0) // expected-error{{value of type '()?' has no member 'domap'}}
253253
}
254254
}
255255

test/Sema/keypath_subscript_nolabel.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,7 @@ struct S3 {
1919
subscript(v v: KeyPath<S3, Int>) -> Int { get { 0 } set(newValue) {} }
2020
}
2121
var s3 = S3()
22-
// TODO(diagnostics): This should actually be a diagnostic that correctly identifies that in the presence
23-
// of a missing label, there are two options for resolution: 'keyPath' and 'v:' and to offer the user
24-
// a choice.
25-
// Today, the ExprTypeChecker identifies the disjunction with two of these possibilities, but
26-
// filters out some of the terms based on label mismatch (but not implicit keypath terms, for example).
27-
// It should probably not do that.
28-
s3[\.x] = 10 // expected-error {{missing argument label 'keyPath:' in subscript}} {{4-4=keyPath: }}
22+
s3[\.x] = 10 // expected-error {{missing argument label 'v:' in subscript}} {{4-4=v: }}
2923

3024
struct S4 {
3125
var x : Int = 0

test/stdlib/UnsafePointerDiagnostics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func unsafeRawBufferPointerConversions(
113113
_ = UnsafeRawBufferPointer(start: rp, count: 1)
114114
_ = UnsafeMutableRawBufferPointer(mrbp)
115115
_ = UnsafeRawBufferPointer(mrbp)
116-
_ = UnsafeMutableRawBufferPointer(rbp) // expected-error {{cannot convert value of type 'UnsafeRawBufferPointer' to expected argument type 'UnsafeMutableRawBufferPointer'}}
116+
_ = UnsafeMutableRawBufferPointer(rbp) // expected-error {{missing argument label 'mutating:' in call}}
117117
_ = UnsafeRawBufferPointer(rbp)
118118
_ = UnsafeMutableRawBufferPointer(mbpi)
119119
_ = UnsafeRawBufferPointer(mbpi)

validation-test/Sema/type_checker_crashers_fixed/rdar45470505.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
extension BinaryInteger {
44
init(bytes: [UInt8]) { fatalError() }
55

6-
init<S: Sequence>(bytes: S) where S.Iterator.Element == UInt8 {
6+
init<S: Sequence>(bytes: S) where S.Iterator.Element == UInt8 { // expected-note {{incorrect labels for candidate (have: '(_:)', expected: '(bytes:)')}}
77
self.init(bytes // expected-error {{no exact matches in call to initializer}}
88
// expected-note@-1 {{}}
99

validation-test/compiler_crashers_2_fixed/0158-rdar40165062.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// RUN: %target-typecheck-verify-swift
22

3-
struct Foo<T, U> {
3+
struct Foo<T, U> { // expected-note {{incorrect labels for candidate (have: '(_:)', expected: '(value:)')}}
44
var value: U
55
func bar() -> Foo<T, U> {
66
return Foo(value)
7-
// expected-error@-1 {{referencing initializer 'init(_:)' on 'Foo' requires the types 'T' and 'U' be equivalent}}
7+
// expected-error@-1 {{no exact matches in call to initializer}}
88
}
99
}
1010

11-
extension Foo where T == U { // expected-note {{where 'T' = 'T', 'U' = 'U'}}
11+
extension Foo where T == U { // expected-note {{candidate requires that the types 'T' and 'U' be equivalent (requirement specified as 'T' == 'U')}}
1212
init(_ value: U) {
1313
self.value = value
1414
}

0 commit comments

Comments
 (0)