Skip to content

Commit 3feb25c

Browse files
committed
[TypeChecker] Disambiguite cases of implicit pointer conversions with optionals
Currently `{inout, array, string}-to-pointer` conversion doesn't track whether there was a difference in optionality between involved types which leads to ambiguity when different overload choices have different optionality requirements. Let's fix that by increasing a score in cases if pointer type is itself optional e.g.: ```swift func foo(_ x: UnsafeMutablePointer<Int>) {} func foo(_ x: UnsafeMutablePointer<Int>?) {} foo(&foo) // Should pick the least optional overload choice. ``` Resolves: [SR-8411](https://bugs.swift.org/browse/SR-8411)
1 parent 9931aab commit 3feb25c

File tree

4 files changed

+61
-31
lines changed

4 files changed

+61
-31
lines changed

lib/Sema/CSSimplify.cpp

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,7 +2199,6 @@ static bool isStringCompatiblePointerBaseType(TypeChecker &TC,
21992199
/// is potentially more optional than the second type with its number of
22002200
/// optionals.
22012201
static bool isPotentiallyMoreOptionalThan(Type type1, Type type2) {
2202-
22032202
SmallVector<Type, 2> optionals1;
22042203
Type objType1 = type1->lookThroughAllOptionalTypes(optionals1);
22052204
auto numOptionals1 = optionals1.size();
@@ -7661,11 +7660,17 @@ ConstraintSystem::simplifyDynamicCallableApplicableFnConstraint(
76617660
return SolutionKind::Solved;
76627661
}
76637662

7664-
static Type getBaseTypeForPointer(ConstraintSystem &cs, TypeBase *type) {
7665-
auto pointeeTy = type->lookThroughSingleOptionalType()
7666-
->getAnyPointerElementType();
7663+
static llvm::PointerIntPair<Type, 3, unsigned>
7664+
getBaseTypeForPointer(TypeBase *type) {
7665+
unsigned unwrapCount = 0;
7666+
while (auto objectTy = type->getOptionalObjectType()) {
7667+
type = objectTy.getPointer();
7668+
++unwrapCount;
7669+
}
7670+
7671+
auto pointeeTy = type->getAnyPointerElementType();
76677672
assert(pointeeTy);
7668-
return pointeeTy;
7673+
return {pointeeTy, unwrapCount};
76697674
}
76707675

76717676
void ConstraintSystem::addRestrictedConstraint(
@@ -7858,22 +7863,26 @@ ConstraintSystem::simplifyRestrictedConstraintImpl(
78587863
auto t2 = type2->getDesugaredType();
78597864

78607865
auto baseType1 = getFixedTypeRecursive(*isArrayType(obj1), false);
7861-
auto baseType2 = getBaseTypeForPointer(*this, t2);
7866+
auto ptr2 = getBaseTypeForPointer(t2);
78627867

7863-
return matchPointerBaseTypes(baseType1, baseType2);
7868+
increaseScore(SK_ValueToOptional, ptr2.getInt());
7869+
7870+
return matchPointerBaseTypes(baseType1, ptr2.getPointer());
78647871
}
78657872

78667873
// String ===> UnsafePointer<[U]Int8>
78677874
case ConversionRestrictionKind::StringToPointer: {
78687875
addContextualScore();
78697876

7870-
auto baseType2 = getBaseTypeForPointer(*this, type2->getDesugaredType());
7871-
7877+
auto ptr2 = getBaseTypeForPointer(type2->getDesugaredType());
7878+
7879+
increaseScore(SK_ValueToOptional, ptr2.getInt());
7880+
78727881
// The pointer element type must be void or a byte-sized type.
78737882
// TODO: Handle different encodings based on pointer element type, such as
78747883
// UTF16 for [U]Int16 or UTF32 for [U]Int32. For now we only interop with
78757884
// Int8 pointers using UTF8 encoding.
7876-
baseType2 = getFixedTypeRecursive(baseType2, false);
7885+
auto baseType2 = getFixedTypeRecursive(ptr2.getPointer(), false);
78777886
// If we haven't resolved the element type, generate constraints.
78787887
if (baseType2->isTypeVariableOrMember()) {
78797888
if (flags.contains(TMF_GenerateConstraints)) {
@@ -7910,22 +7919,24 @@ ConstraintSystem::simplifyRestrictedConstraintImpl(
79107919
addContextualScore();
79117920

79127921
auto t2 = type2->getDesugaredType();
7913-
7922+
79147923
auto baseType1 = type1->getInOutObjectType();
7915-
auto baseType2 = getBaseTypeForPointer(*this, t2);
7924+
auto ptr2 = getBaseTypeForPointer(t2);
79167925

7917-
return matchPointerBaseTypes(baseType1, baseType2);
7926+
increaseScore(SK_ValueToOptional, ptr2.getInt());
7927+
7928+
return matchPointerBaseTypes(baseType1, ptr2.getPointer());
79187929
}
79197930

79207931
// T <p U ===> UnsafeMutablePointer<T> <a UnsafeMutablePointer<U>
79217932
case ConversionRestrictionKind::PointerToPointer: {
79227933
auto t1 = type1->getDesugaredType();
79237934
auto t2 = type2->getDesugaredType();
7924-
7925-
Type baseType1 = getBaseTypeForPointer(*this, t1);
7926-
Type baseType2 = getBaseTypeForPointer(*this, t2);
79277935

7928-
return matchPointerBaseTypes(baseType1, baseType2);
7936+
auto ptr1 = getBaseTypeForPointer(t1);
7937+
auto ptr2 = getBaseTypeForPointer(t2);
7938+
7939+
return matchPointerBaseTypes(ptr1.getPointer(), ptr2.getPointer());
79297940
}
79307941

79317942
// T < U or T is bridged to V where V < U ===> Array<T> <c Array<U>

test/Constraints/optional.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,3 +373,24 @@ func rdar_53238058() {
373373
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}
374374
}
375375
}
376+
377+
// SR-8411 - Inconsistent ambiguity with optional and non-optional inout-to-pointer
378+
func sr8411() {
379+
struct S {
380+
init(_ x: UnsafeMutablePointer<Int>) {}
381+
init(_ x: UnsafeMutablePointer<Int>?) {}
382+
383+
static func foo(_ x: UnsafeMutablePointer<Int>) {}
384+
static func foo(_ x: UnsafeMutablePointer<Int>?) {}
385+
386+
static func bar(_ x: UnsafeMutablePointer<Int>, _ y: Int) {}
387+
static func bar(_ x: UnsafeMutablePointer<Int>?, _ y: Int) {}
388+
}
389+
390+
var foo = 0
391+
392+
_ = S(&foo) // Ok
393+
_ = S.init(&foo) // Ok
394+
_ = S.foo(&foo) // Ok
395+
_ = S.bar(&foo, 42) // Ok
396+
}

test/stdlib/UnsafePointerDiagnostics.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,17 @@ func unsafePointerInitEphemeralConversions() {
152152
// expected-note@-1 {{implicit argument conversion from 'Int' to 'UnsafePointer<Int>' produces a pointer valid only for the duration of the call to '+'}}
153153
// expected-note@-2 {{use 'withUnsafePointer' in order to explicitly convert argument to pointer valid for a defined scope}}
154154

155-
// FIXME(SR-8411): This is currently ambiguous. The reason why we don't get
156-
// the usual "no exact matches in call to initializer" error is we cannot
157-
// currently diagnose ambiguities between solutions with multiple fixes, so we
158-
// leave it up to CSDiag.
159-
_ = UnsafePointer.init(&foo) // expected-error {{ambiguous reference to member 'init(_:)'}}
155+
_ = UnsafePointer.init(&foo) // expected-error {{initialization of 'UnsafePointer<Int>' results in a dangling pointer}}
156+
// expected-note@-1 {{implicit argument conversion from 'Int' to 'UnsafePointer<Int>' produces a pointer valid only for the duration of the call to 'init(_:)'}}
157+
// expected-note@-2 {{use 'withUnsafePointer' in order to explicitly convert argument to pointer valid for a defined scope}}
160158

161159
_ = UnsafePointer<Int8>("") // expected-error {{initialization of 'UnsafePointer<Int8>' results in a dangling pointer}}
162160
// expected-note@-1 {{implicit argument conversion from 'String' to 'UnsafePointer<Int8>' produces a pointer valid only for the duration of the call to 'init(_:)'}}
163161
// expected-note@-2 {{use the 'withCString' method on String in order to explicitly convert argument to pointer valid for a defined scope}}
164162

165-
// FIXME(SR-8411): This is currently ambiguous.
166-
_ = UnsafePointer<Int8>.init("") // expected-error {{no exact matches in call to initializer}}
163+
_ = UnsafePointer<Int8>.init("") // expected-error {{initialization of 'UnsafePointer<Int8>' results in a dangling pointer}}
164+
// expected-note@-1 {{implicit argument conversion from 'String' to 'UnsafePointer<Int8>' produces a pointer valid only for the duration of the call to 'init(_:)'}}
165+
// expected-note@-2 {{use the 'withCString' method on String in order to explicitly convert argument to pointer valid for a defined scope}}
167166

168167
_ = UnsafePointer<Int8>(str) // expected-error {{initialization of 'UnsafePointer<Int8>' results in a dangling pointer}}
169168
// expected-note@-1 {{implicit argument conversion from 'String' to 'UnsafePointer<Int8>' produces a pointer valid only for the duration of the call to 'init(_:)'}}

test/stdlib/UnsafePointerDiagnostics_warning.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,17 @@ func unsafePointerInitEphemeralConversions() {
1717
// expected-note@-1 {{implicit argument conversion from 'Int' to 'UnsafePointer<Int>' produces a pointer valid only for the duration of the call to '+'}}
1818
// expected-note@-2 {{use 'withUnsafePointer' in order to explicitly convert argument to pointer valid for a defined scope}}
1919

20-
// FIXME(SR-8411): This is currently ambiguous. The reason why we don't get
21-
// the usual "no exact matches in call to initializer" error is we cannot
22-
// currently diagnose ambiguities between solutions with multiple fixes, so we
23-
// leave it up to CSDiag.
24-
_ = UnsafePointer.init(&foo) // expected-error {{ambiguous reference to member 'init(_:)'}}
20+
_ = UnsafePointer.init(&foo) // expected-warning {{initialization of 'UnsafePointer<Int>' results in a dangling pointer}}
21+
// expected-note@-1 {{implicit argument conversion from 'Int' to 'UnsafePointer<Int>' produces a pointer valid only for the duration of the call to 'init(_:)'}}
22+
// expected-note@-2 {{use 'withUnsafePointer' in order to explicitly convert argument to pointer valid for a defined scope}}
2523

2624
_ = UnsafePointer<Int8>("") // expected-warning {{initialization of 'UnsafePointer<Int8>' results in a dangling pointer}}
2725
// expected-note@-1 {{implicit argument conversion from 'String' to 'UnsafePointer<Int8>' produces a pointer valid only for the duration of the call to 'init(_:)'}}
2826
// expected-note@-2 {{use the 'withCString' method on String in order to explicitly convert argument to pointer valid for a defined scope}}
2927

30-
// FIXME(SR-8411): This is currently ambiguous.
31-
_ = UnsafePointer<Int8>.init("") // expected-error {{no exact matches in call to initializer}}
28+
_ = UnsafePointer<Int8>.init("") // expected-warning {{initialization of 'UnsafePointer<Int8>' results in a dangling pointer}}
29+
// expected-note@-1 {{implicit argument conversion from 'String' to 'UnsafePointer<Int8>' produces a pointer valid only for the duration of the call to 'init(_:)'}}
30+
// expected-note@-2 {{use the 'withCString' method on String in order to explicitly convert argument to pointer valid for a defined scope}}
3231

3332
_ = UnsafePointer<Int8>(str) // expected-warning {{initialization of 'UnsafePointer<Int8>' results in a dangling pointer}}
3433
// expected-note@-1 {{implicit argument conversion from 'String' to 'UnsafePointer<Int8>' produces a pointer valid only for the duration of the call to 'init(_:)'}}

0 commit comments

Comments
 (0)