Sema: Disambiguate some more bidirectional conversions

This fixes an ambiguity introduced by the stdlib change in
0f99458900.

Since (borrowing T) -> () and (T) -> () both convert to
each other, we could end up with ambiguous solutions where
neither one was better than the other. Generalize the
existing trick we use for labeled vs unlabeled tuples to
also strip off ownership specifiers and @convention(...)
from function types. This fixes the regression, as well
an existing FIXME in a test I added a while ago where
the same problem arises with @convention(block).
This commit is contained in:
Slava Pestov
2025-12-09 17:32:48 -05:00
parent 1e9f8826a7
commit f3e6b4ceda
4 changed files with 70 additions and 12 deletions

View File

@@ -852,7 +852,7 @@ Comparison TypeChecker::compareDeclarations(DeclContext *dc,
return decl1Better ? Comparison::Better : Comparison::Worse;
}
static Type getUnlabeledType(Type type, ASTContext &ctx) {
static Type getStrippedType(Type type, ASTContext &ctx) {
return type.transformRec([&](TypeBase *type) -> std::optional<Type> {
if (auto *tupleType = dyn_cast<TupleType>(type)) {
if (tupleType->getNumElements() == 1)
@@ -866,6 +866,31 @@ static Type getUnlabeledType(Type type, ASTContext &ctx) {
return TupleType::get(elts, ctx);
}
if (auto *funcType = dyn_cast<FunctionType>(type)) {
auto params = funcType->getParams();
SmallVector<AnyFunctionType::Param, 4> newParams;
for (auto param : params) {
auto newParam = param;
switch (param.getParameterFlags().getOwnershipSpecifier()) {
case ParamSpecifier::Borrowing:
case ParamSpecifier::Consuming: {
auto flags = param.getParameterFlags()
.withOwnershipSpecifier(ParamSpecifier::Default);
newParams.push_back(param.withFlags(flags));
break;
}
default:
newParams.push_back(newParam);
break;
}
}
auto newExtInfo = funcType->getExtInfo().withRepresentation(
AnyFunctionType::Representation::Swift);
return FunctionType::get(newParams,
funcType->getResult(),
newExtInfo);
}
return std::nullopt;
});
}
@@ -1438,15 +1463,16 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
if (type2Better)
++score2;
// Prefer the unlabeled form of a type.
auto unlabeled1 = getUnlabeledType(type1, cs.getASTContext());
auto unlabeled2 = getUnlabeledType(type2, cs.getASTContext());
if (unlabeled1->isEqual(unlabeled2)) {
if (type1->isEqual(unlabeled1) && !types.Type1WasLabeled) {
// Prefer the "stripped" form of a type. See getStrippedType()
// for the definition.
auto stripped1 = getStrippedType(type1, cs.getASTContext());
auto stripped2 = getStrippedType(type2, cs.getASTContext());
if (stripped1->isEqual(stripped2)) {
if (type1->isEqual(stripped1) && !types.Type1WasLabeled) {
++score1;
continue;
}
if (type2->isEqual(unlabeled2) && !types.Type2WasLabeled) {
if (type2->isEqual(stripped2) && !types.Type2WasLabeled) {
++score2;
continue;
}

View File

@@ -43,3 +43,37 @@ func baz2(x: (Int, Int)?) {
func f(_: (x: Int, y: Int)) {}
f(unwrap(x))
}
/////////////
func borrowingFn(fn: @escaping (borrowing AnyObject) -> ()) -> (AnyObject) -> () {
return id(id(fn))
}
/////////////
infix operator <+
infix operator >+
protocol P {
static func <+ (lhs: borrowing Self, rhs: borrowing Self)
static func >+ (lhs: borrowing Self, rhs: borrowing Self)
}
extension P {
static func >+ (lhs: borrowing Self, rhs: borrowing Self) {}
}
struct S: P {
static func <+ (lhs: Self, rhs: Self) {}
}
let _: (S, S) -> () = false ? (<+) : (>+)
/////////////
struct MyString: Comparable {
static func < (lhs: Self, rhs: Self) -> Bool { fatalError() }
}
let _: (MyString, MyString) -> Bool = false ? (<) : (>)

View File

@@ -36,14 +36,12 @@ func id<T>(_: T) -> T {}
func bar3(x: @escaping () -> ()) {
func f(_: @escaping @convention(block) () -> ()) {}
// FIXME
f(id(x)) // expected-error {{conflicting arguments to generic parameter 'T' ('@convention(block) () -> ()' vs. '() -> ()')}}
f(id(x))
}
func bar4(x: @escaping @convention(block) () -> ()) {
func f(_: @escaping () -> ()) {}
// FIXME
f(id(x)) // expected-error {{conflicting arguments to generic parameter 'T' ('() -> ()' vs. '@convention(block) () -> ()')}}
f(id(x))
}
func bar5(x: Double) {

View File

@@ -551,7 +551,7 @@ do {
let qux: () -> Void
f(qux)
f(id(qux)) // expected-error {{conflicting arguments to generic parameter 'T' ('() -> Void' vs. '@convention(block) () -> Void')}}
f(id(qux))
func forceUnwrap<T>(_: T?) -> T {}