Skip to content

[TypeChecker] SE-0324: Extend Swift -> C pointer conversions to inout #59051

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 1 commit into from
May 26, 2022
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
6 changes: 4 additions & 2 deletions include/swift/Sema/Constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ enum class ConversionRestrictionKind {
ProtocolMetatypeToProtocolClass,
/// Inout-to-pointer conversion.
InoutToPointer,
/// Converting from `inout` to a C pointer has `PointerToCPointer` semantics.
InoutToCPointer,
/// Array-to-pointer conversion.
ArrayToPointer,
/// String-to-pointer conversion.
Expand Down Expand Up @@ -302,8 +304,8 @@ enum class ConversionRestrictionKind {
/// via an implicit Double initializer call passing a CGFloat value.
CGFloatToDouble,
/// Implicit conversion between Swift and C pointers:
// - Unsafe[Mutable]RawPointer -> Unsafe[Mutable]Pointer<[U]Int>
// - Unsafe[Mutable]Pointer<Int{8, 16, ...}> <-> Unsafe[Mutable]Pointer<UInt{8, 16, ...}>
/// - Unsafe[Mutable]RawPointer -> Unsafe[Mutable]Pointer<[U]Int>
/// - Unsafe[Mutable]Pointer<Int{8, 16, ...}> <-> Unsafe[Mutable]Pointer<UInt{8, 16, ...}>
PointerToCPointer,
// Convert a pack into a type with an equivalent arity.
// - If the arity of the pack is 1, drops the pack structure <T> => T
Expand Down
5 changes: 3 additions & 2 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6611,7 +6611,8 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
return buildCollectionUpcastExpr(expr, toType, /*bridged=*/false, locator);
}

case ConversionRestrictionKind::InoutToPointer: {
case ConversionRestrictionKind::InoutToPointer:
case ConversionRestrictionKind::InoutToCPointer: {
bool isOptional = false;
Type unwrappedTy = toType;
if (Type unwrapped = toType->getOptionalObjectType()) {
Expand All @@ -6629,7 +6630,7 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
result = cs.cacheType(new (ctx) InjectIntoOptionalExpr(result, toType));
return result;
}

case ConversionRestrictionKind::ArrayToPointer: {
bool isOptional = false;
Type unwrappedTy = toType;
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6895,6 +6895,7 @@ void NonEphemeralConversionFailure::emitSuggestionNotes() const {
break;
}
case ConversionRestrictionKind::InoutToPointer:
case ConversionRestrictionKind::InoutToCPointer:
// For an arbitrary inout-to-pointer, we can suggest
// withUnsafe[Mutable][Bytes/Pointer].
if (auto alternative = getAlternativeKind())
Expand Down
40 changes: 39 additions & 1 deletion lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6589,9 +6589,18 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
// Only try an inout-to-pointer conversion if we know it's not
// an array being converted to a raw pointer type. Such
// conversions can only use array-to-pointer.
if (!baseIsArray || !isRawPointerKind(pointerKind))
if (!baseIsArray || !isRawPointerKind(pointerKind)) {
conversionsOrFixes.push_back(
ConversionRestrictionKind::InoutToPointer);

// If regular inout-to-pointer conversion doesn't work,
// let's try C pointer conversion that has special semantics
// for imported declarations.
if (isArgumentOfImportedDecl(locator)) {
conversionsOrFixes.push_back(
ConversionRestrictionKind::InoutToCPointer);
}
}
}
}

Expand Down Expand Up @@ -12222,6 +12231,35 @@ ConstraintSystem::simplifyRestrictedConstraintImpl(
case ConversionRestrictionKind::PointerToCPointer:
return simplifyPointerToCPointerRestriction(type1, type2, flags, locator);

case ConversionRestrictionKind::InoutToCPointer: {
SmallVector<Type, 2> optionals;

auto ptr2 =
type2->getDesugaredType()->lookThroughAllOptionalTypes(optionals);

increaseScore(SK_ValueToOptional, optionals.size());

PointerTypeKind pointerKind;
(void)ptr2->getAnyPointerElementType(pointerKind);

auto baseType1 = type1->getInOutObjectType();

Type ptr1;
// The right-hand size is a raw pointer, so let's use `UnsafeMutablePointer`
// for the `inout` type.
if (pointerKind == PTK_UnsafeRawPointer ||
pointerKind == PTK_UnsafeMutableRawPointer) {
ptr1 = BoundGenericType::get(Context.getUnsafeMutablePointerDecl(),
/*parent=*/nullptr, {baseType1});
} else {
ptr1 = baseType1->wrapInPointer(pointerKind);
}

assert(ptr1);

return simplifyPointerToCPointerRestriction(ptr1, ptr2, flags, locator);
}

// T < U or T is bridged to V where V < U ===> Array<T> <c Array<U>
case ConversionRestrictionKind::ArrayUpcast: {
Type baseType1 = *isArrayType(type1);
Expand Down
2 changes: 2 additions & 0 deletions lib/Sema/Constraint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,8 @@ StringRef swift::constraints::getName(ConversionRestrictionKind kind) {
return "[string-to-pointer]";
case ConversionRestrictionKind::InoutToPointer:
return "[inout-to-pointer]";
case ConversionRestrictionKind::InoutToCPointer:
return "[inout-to-c-pointer]";
case ConversionRestrictionKind::PointerToPointer:
return "[pointer-to-pointer]";
case ConversionRestrictionKind::PointerToCPointer:
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5719,7 +5719,8 @@ ConstraintSystem::isConversionEphemeral(ConversionRestrictionKind conversion,
case ConversionRestrictionKind::StringToPointer:
// Always ephemeral.
return ConversionEphemeralness::Ephemeral;
case ConversionRestrictionKind::InoutToPointer: {
case ConversionRestrictionKind::InoutToPointer:
case ConversionRestrictionKind::InoutToCPointer: {

// Ephemeral, except if the expression is a reference to a global or
// static stored variable, or a directly accessed stored property on such a
Expand Down
5 changes: 5 additions & 0 deletions test/Constraints/Inputs/c_pointer_conversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

#include "stdint.h"

void void_ptr_func(void * _Nonnull buffer);
void const_void_ptr_func(const void * _Nonnull buffer);
void opt_void_ptr_func(void * _Nullable buffer);
void const_opt_void_ptr_func(const void * _Nullable buffer);

void char_ptr_func(char * _Nonnull buffer);
void const_char_ptr_func(const char * _Nonnull buffer);

Expand Down
27 changes: 27 additions & 0 deletions test/Constraints/swift_to_c_pointer_conversions.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,30 @@ func test_tailored_diagnostic(ptr: UnsafeRawPointer, tptr: UnsafePointer<Int8>)
opt_arg_func(optrU8)
// expected-error@-1 {{cannot convert value of type 'UnsafePointer<UInt8>?' to expected argument type 'UnsafePointer<Int8>?' because local function 'opt_arg_func' was not imported from C header}}
}

func test_inout_to_pointer_conversion() {
% for Size in ['16', '32', '64']:
var x${Size}: Int${Size} = 0

void_ptr_func(&x${Size}) // Ok
const_void_ptr_func(&x${Size}) // Ok
opt_void_ptr_func(&x${Size}) // Ok

char_ptr_func(&x${Size}) // Ok
opt_char_ptr_func(&x${Size}) // Ok

const_char_ptr_func(&x${Size}) // Ok
const_opt_char_ptr_func(&x${Size}) // Ok

int_${Size}_ptr_func(&x${Size}) // Ok
uint_${Size}_ptr_func(&x${Size}) // Ok

opt_int_${Size}_ptr_func(&x${Size}) // Ok
opt_uint_${Size}_ptr_func(&x${Size}) // Ok

const_int_${Size}_ptr_func(&x${Size}) // OK
const_uint_${Size}_ptr_func(&x${Size}) // OK
const_opt_int_${Size}_ptr_func(&x${Size}) // OK
const_opt_uint_${Size}_ptr_func(&x${Size}) // OK
% end
}