Skip to content

[Sema] Implement @_nonEphemeral #27705

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

Closed
wants to merge 3 commits into from
Closed
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
4 changes: 4 additions & 0 deletions include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,10 @@ DECL_ATTR(_projectedValueProperty, ProjectedValueProperty,
OnVar | UserInaccessible |
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
89)
SIMPLE_DECL_ATTR(_nonEphemeral, NonEphemeral,
OnParam | UserInaccessible |
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
90)

SIMPLE_DECL_ATTR(IBSegueAction, IBSegueAction,
OnFunc |
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5320,6 +5320,11 @@ class ParamDecl : public VarDecl {
: flags - Flags::IsAutoClosure);
}

/// Does this parameter reject temporary pointer conversions?
bool isNonEphemeral() const {
return getAttrs().hasAttribute<NonEphemeralAttr>();
}

/// Remove the type of this varargs element designator, without the array
/// type wrapping it. A parameter like "Int..." will have formal parameter
/// type of "[Int]" and this returns "Int".
Expand Down
37 changes: 37 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,39 @@ ERROR(cannot_convert_argument_value_generic,none,
"cannot convert value of type %0 (%1) to expected argument type %2 (%3)",
(Type, StringRef, Type, StringRef))

// @_nonEphemeral conversion diagnostics
ERROR(cannot_pass_type_to_non_ephemeral,none,
"cannot pass %0 to parameter; argument #%1 must be a pointer that "
"outlives the call%select{| to %3}2", (Type, unsigned, bool, DeclName))
WARNING(cannot_pass_type_to_non_ephemeral_warning,none,
"passing %0 to parameter, but argument #%1 should be a pointer that "
"outlives the call%select{| to %3}2", (Type, unsigned, bool, DeclName))
ERROR(cannot_use_inout_non_ephemeral,none,
"cannot use inout expression here; argument #%0 must be a pointer that "
"outlives the call%select{| to %2}1", (unsigned, bool, DeclName))
WARNING(cannot_use_inout_non_ephemeral_warning,none,
"inout expression creates a temporary pointer, but argument #%0 should "
"be a pointer that outlives the call%select{| to %2}1",
(unsigned, bool, DeclName))
NOTE(ephemeral_pointer_argument_conversion_note,none,
"implicit argument conversion from %0 to %1 produces a pointer valid only "
"for the duration of the call%select{| to %3}2",
(Type, Type, bool, DeclName))
NOTE(ephemeral_use_with_unsafe_pointer,none,
"use 'withUnsafe%select{Bytes|MutableBytes|Pointer|MutablePointer}0' in "
"order to explicitly convert argument to %select{buffer |buffer ||}0"
"pointer valid for a defined scope", (unsigned))
NOTE(ephemeral_use_string_with_c_string,none,
"use the 'withCString' method on String in order to explicitly "
"convert argument to pointer valid for a defined scope", ())
NOTE(ephemeral_use_array_with_unsafe_buffer,none,
"use the 'withUnsafe%select{Bytes|MutableBytes|BufferPointer|"
"MutableBufferPointer}0' method on Array in order to explicitly convert "
"argument to buffer pointer valid for a defined scope", (unsigned))
NOTE(candidate_performs_illegal_ephemeral_conv,none,
"candidate expects pointer that outlives the call for parameter #%0",
(unsigned))

ERROR(cannot_convert_argument_value_protocol,none,
"argument type %0 does not conform to expected type %1", (Type, Type))
ERROR(cannot_convert_partial_argument_value_protocol,none,
Expand Down Expand Up @@ -2749,6 +2782,10 @@ ERROR(escaping_non_function_parameter,none,
NOTE(escaping_optional_type_argument, none,
"closure is already escaping in optional type argument", ())

// @_nonEphemeral attribute
ERROR(non_ephemeral_non_pointer_type,none,
"@_nonEphemeral attribute currently only applies to pointer types", ())

// NSManaged attribute
ERROR(attr_NSManaged_not_instance_member,none,
"@NSManaged only allowed on an instance property or method", ())
Expand Down
35 changes: 24 additions & 11 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -1761,13 +1761,14 @@ class TypeAliasType final
/// escaping.
class ParameterTypeFlags {
enum ParameterFlags : uint8_t {
None = 0,
Variadic = 1 << 0,
AutoClosure = 1 << 1,
OwnershipShift = 2,
Ownership = 7 << OwnershipShift,

NumBits = 5
None = 0,
Variadic = 1 << 0,
AutoClosure = 1 << 1,
NonEphemeral = 1 << 2,
OwnershipShift = 3,
Ownership = 7 << OwnershipShift,

NumBits = 6
};
OptionSet<ParameterFlags> value;
static_assert(NumBits < 8*sizeof(OptionSet<ParameterFlags>), "overflowed");
Expand All @@ -1780,19 +1781,21 @@ class ParameterTypeFlags {
return ParameterTypeFlags(OptionSet<ParameterFlags>(raw));
}

ParameterTypeFlags(bool variadic, bool autoclosure,
ParameterTypeFlags(bool variadic, bool autoclosure, bool nonEphemeral,
ValueOwnership ownership)
: value((variadic ? Variadic : 0) | (autoclosure ? AutoClosure : 0) |
(nonEphemeral ? NonEphemeral : 0) |
uint8_t(ownership) << OwnershipShift) {}

/// Create one from what's present in the parameter type
inline static ParameterTypeFlags
fromParameterType(Type paramTy, bool isVariadic, bool isAutoClosure,
ValueOwnership ownership);
bool isNonEphemeral, ValueOwnership ownership);

bool isNone() const { return !value; }
bool isVariadic() const { return value.contains(Variadic); }
bool isAutoClosure() const { return value.contains(AutoClosure); }
bool isNonEphemeral() const { return value.contains(NonEphemeral); }
bool isInOut() const { return getValueOwnership() == ValueOwnership::InOut; }
bool isShared() const { return getValueOwnership() == ValueOwnership::Shared;}
bool isOwned() const { return getValueOwnership() == ValueOwnership::Owned; }
Expand Down Expand Up @@ -1832,6 +1835,12 @@ class ParameterTypeFlags {
: value - ParameterTypeFlags::AutoClosure);
}

ParameterTypeFlags withNonEphemeral(bool isNonEphemeral) const {
return ParameterTypeFlags(isNonEphemeral
? value | ParameterTypeFlags::NonEphemeral
: value - ParameterTypeFlags::NonEphemeral);
}

bool operator ==(const ParameterTypeFlags &other) const {
return value.toRaw() == other.value.toRaw();
}
Expand Down Expand Up @@ -1898,6 +1907,7 @@ class YieldTypeFlags {
ParameterTypeFlags asParamFlags() const {
return ParameterTypeFlags(/*variadic*/ false,
/*autoclosure*/ false,
/*nonEphemeral*/ false,
getValueOwnership());
}

Expand Down Expand Up @@ -2767,6 +2777,9 @@ class AnyFunctionType : public TypeBase {
/// Whether the parameter is marked 'owned'
bool isOwned() const { return Flags.isOwned(); }

/// Whether the parameter is marked '@_nonEphemeral'
bool isNonEphemeral() const { return Flags.isNonEphemeral(); }

ValueOwnership getValueOwnership() const {
return Flags.getValueOwnership();
}
Expand Down Expand Up @@ -5562,7 +5575,7 @@ inline TupleTypeElt TupleTypeElt::getWithType(Type T) const {
/// Create one from what's present in the parameter decl and type
inline ParameterTypeFlags
ParameterTypeFlags::fromParameterType(Type paramTy, bool isVariadic,
bool isAutoClosure,
bool isAutoClosure, bool isNonEphemeral,
ValueOwnership ownership) {
// FIXME(Remove InOut): The last caller that needs this is argument
// decomposition. Start by enabling the assertion there and fixing up those
Expand All @@ -5573,7 +5586,7 @@ ParameterTypeFlags::fromParameterType(Type paramTy, bool isVariadic,
ownership == ValueOwnership::InOut);
ownership = ValueOwnership::InOut;
}
return {isVariadic, isAutoClosure, ownership};
return {isVariadic, isAutoClosure, isNonEphemeral, ownership};
}

inline const Type *BoundGenericType::getTrailingObjectsPointer() const {
Expand Down
4 changes: 4 additions & 0 deletions include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ namespace swift {
/// operator protocol designator feature.
bool SolverEnableOperatorDesignatedTypes = false;

/// Whether to diagnose an ephemeral to non-ephemeral conversion as an
/// error.
bool DiagnoseInvalidEphemeralnessAsError = false;

/// The maximum depth to which to test decl circularity.
unsigned MaxCircularityDepth = 500;

Expand Down
4 changes: 4 additions & 0 deletions include/swift/Option/FrontendOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@ def solver_enable_operator_designated_types :
Flag<["-"], "solver-enable-operator-designated-types">,
HelpText<"Enable operator designated types in constraint solver">;

def diagnose_invalid_ephemeralness_as_error :
Flag<["-"], "diagnose-invalid-ephemeralness-as-error">,
HelpText<"Diagnose invalid ephemeral to non-ephemeral conversions as errors">;

def switch_checking_invocation_threshold_EQ : Joined<["-"],
"switch-checking-invocation-threshold=">;

Expand Down
2 changes: 1 addition & 1 deletion lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2922,7 +2922,7 @@ void AnyFunctionType::decomposeInput(
default:
result.emplace_back(type->getInOutObjectType(), Identifier(),
ParameterTypeFlags::fromParameterType(
type, false, false, ValueOwnership::Default));
type, false, false, false, ValueOwnership::Default));
return;
}
}
Expand Down
4 changes: 4 additions & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,9 @@ namespace {
if (P->isAutoClosure())
OS << " autoclosure";

if (P->isNonEphemeral())
OS << " nonEphemeral";

if (P->getDefaultArgumentKind() != DefaultArgumentKind::None) {
printField("default_arg",
getDefaultArgumentKindString(P->getDefaultArgumentKind()));
Expand Down Expand Up @@ -3306,6 +3309,7 @@ namespace {
void dumpParameterFlags(ParameterTypeFlags paramFlags) {
printFlag(paramFlags.isVariadic(), "vararg");
printFlag(paramFlags.isAutoClosure(), "autoclosure");
printFlag(paramFlags.isNonEphemeral(), "nonEphemeral");
switch (paramFlags.getValueOwnership()) {
case ValueOwnership::Default: break;
case ValueOwnership::Owned: printFlag("owned"); break;
Expand Down
5 changes: 4 additions & 1 deletion lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2535,7 +2535,9 @@ static Type mapSignatureFunctionType(ASTContext &ctx, Type type,
SmallVector<AnyFunctionType::Param, 4> newParams;
for (const auto &param : funcTy->getParams()) {
auto newParamType = mapSignatureParamType(ctx, param.getPlainType());
ParameterTypeFlags newFlags = param.getParameterFlags();

// Don't allow overloading by @_nonEphemeral.
auto newFlags = param.getParameterFlags().withNonEphemeral(false);

// For the 'self' of a method, strip off 'inout'.
if (isMethod) {
Expand Down Expand Up @@ -5879,6 +5881,7 @@ AnyFunctionType::Param ParamDecl::toFunctionParam(Type type) const {
auto flags = ParameterTypeFlags::fromParameterType(type,
isVariadic(),
isAutoClosure(),
isNonEphemeral(),
getValueOwnership());
return AnyFunctionType::Param(type, label, flags);
}
Expand Down
5 changes: 4 additions & 1 deletion lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,10 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,

Opts.EnableOperatorDesignatedTypes |=
Args.hasArg(OPT_enable_operator_designated_types);


Opts.DiagnoseInvalidEphemeralnessAsError |=
Args.hasArg(OPT_diagnose_invalid_ephemeralness_as_error);

// Always enable operator designated types for the standard library.
Opts.EnableOperatorDesignatedTypes |= FrontendOpts.ParseStdlib;

Expand Down
141 changes: 141 additions & 0 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5426,3 +5426,144 @@ bool ExpandArrayIntoVarargsFailure::diagnoseAsNote() {
}
return false;
}

void NonEphemeralConversionFailure::emitSuggestionNotes() const {
auto getPointerKind = [](Type ty) -> PointerTypeKind {
PointerTypeKind pointerKind;
auto pointeeType = ty->lookThroughSingleOptionalType()
->getAnyPointerElementType(pointerKind);
assert(pointeeType && "Expected a pointer!");
(void)pointeeType;

return pointerKind;
};

// This must stay in sync with diag::ephemeral_use_array_with_unsafe_buffer
// and diag::ephemeral_use_with_unsafe_pointer.
enum AlternativeKind {
AK_Raw = 0,
AK_MutableRaw,
AK_Typed,
AK_MutableTyped,
};

auto getAlternativeKind = [&]() -> Optional<AlternativeKind> {
switch (getPointerKind(getParamType())) {
case PTK_UnsafeRawPointer:
return AK_Raw;
case PTK_UnsafeMutableRawPointer:
return AK_MutableRaw;
case PTK_UnsafePointer:
return AK_Typed;
case PTK_UnsafeMutablePointer:
return AK_MutableTyped;
case PTK_AutoreleasingUnsafeMutablePointer:
return None;
}
};

// First emit a note about the implicit conversion only lasting for the
// duration of the call.
auto *argExpr = getArgExpr();
emitDiagnostic(argExpr->getLoc(),
diag::ephemeral_pointer_argument_conversion_note,
getArgType(), getParamType(), getCallee(), getCalleeFullName())
.highlight(argExpr->getSourceRange());

// Then try to find a suitable alternative.
switch (ConversionKind) {
case ConversionRestrictionKind::ArrayToPointer: {
// Don't suggest anything for optional arrays, as there's currently no
// direct alternative.
if (getArgType()->getOptionalObjectType())
break;

// We can suggest using withUnsafe[Mutable][Bytes/BufferPointer].
if (auto alternative = getAlternativeKind())
emitDiagnostic(argExpr->getLoc(),
diag::ephemeral_use_array_with_unsafe_buffer,
*alternative);
break;
}
case ConversionRestrictionKind::StringToPointer: {
// Don't suggest anything for optional strings, as there's currently no
// direct alternative.
if (getArgType()->getOptionalObjectType())
break;

// We can suggest withCString as long as the resulting pointer is
// immutable.
switch (getPointerKind(getParamType())) {
case PTK_UnsafePointer:
case PTK_UnsafeRawPointer:
emitDiagnostic(argExpr->getLoc(),
diag::ephemeral_use_string_with_c_string);
break;
case PTK_UnsafeMutableRawPointer:
case PTK_UnsafeMutablePointer:
case PTK_AutoreleasingUnsafeMutablePointer:
// There's nothing really sensible we can suggest for a mutable pointer.
break;
}
break;
}
case ConversionRestrictionKind::InoutToPointer:
// For an arbitrary inout-to-pointer, we can suggest
// withUnsafe[Mutable][Bytes/Pointer].
if (auto alternative = getAlternativeKind())
emitDiagnostic(argExpr->getLoc(), diag::ephemeral_use_with_unsafe_pointer,
*alternative);
break;
case ConversionRestrictionKind::DeepEquality:
case ConversionRestrictionKind::Superclass:
case ConversionRestrictionKind::Existential:
case ConversionRestrictionKind::MetatypeToExistentialMetatype:
case ConversionRestrictionKind::ExistentialMetatypeToMetatype:
case ConversionRestrictionKind::ValueToOptional:
case ConversionRestrictionKind::OptionalToOptional:
case ConversionRestrictionKind::ClassMetatypeToAnyObject:
case ConversionRestrictionKind::ExistentialMetatypeToAnyObject:
case ConversionRestrictionKind::ProtocolMetatypeToProtocolClass:
case ConversionRestrictionKind::PointerToPointer:
case ConversionRestrictionKind::ArrayUpcast:
case ConversionRestrictionKind::DictionaryUpcast:
case ConversionRestrictionKind::SetUpcast:
case ConversionRestrictionKind::HashableToAnyHashable:
case ConversionRestrictionKind::CFTollFreeBridgeToObjC:
case ConversionRestrictionKind::ObjCTollFreeBridgeToCF:
llvm_unreachable("Expected an ephemeral conversion!");
}
}

bool NonEphemeralConversionFailure::diagnoseAsNote() {
// We can only emit a useful note if we have a callee.
if (auto *callee = getCallee()) {
emitDiagnostic(callee, diag::candidate_performs_illegal_ephemeral_conv,
getParamPosition());
return true;
}
return false;
}

bool NonEphemeralConversionFailure::diagnoseAsError() {
auto *argExpr = getArgExpr();
if (isa<InOutExpr>(argExpr)) {
auto diagID = DowngradeToWarning
? diag::cannot_use_inout_non_ephemeral_warning
: diag::cannot_use_inout_non_ephemeral;

emitDiagnostic(argExpr->getLoc(), diagID, getArgPosition(), getCallee(),
getCalleeFullName())
.highlight(argExpr->getSourceRange());
} else {
auto diagID = DowngradeToWarning
? diag::cannot_pass_type_to_non_ephemeral_warning
: diag::cannot_pass_type_to_non_ephemeral;

emitDiagnostic(argExpr->getLoc(), diagID, getArgType(), getArgPosition(),
getCallee(), getCalleeFullName())
.highlight(argExpr->getSourceRange());
}
emitSuggestionNotes();
return true;
}
Loading