Skip to content

Update spelling for representing lifetime dependencies to @_lifetime #82067

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 4 commits into from
Jun 10, 2025
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
5 changes: 3 additions & 2 deletions include/swift/AST/ASTBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -1165,10 +1165,11 @@ BridgedLifetimeEntry BridgedLifetimeEntry_createParsed(
BridgedASTContext cContext, BridgedSourceRange cRange,
BridgedArrayRef cSources, BridgedLifetimeDescriptor cTarget);

SWIFT_NAME("BridgedLifetimeAttr.createParsed(_:atLoc:range:entry:)")
SWIFT_NAME(
"BridgedLifetimeAttr.createParsed(_:atLoc:range:entry:isUnderscored:)")
BridgedLifetimeAttr BridgedLifetimeAttr_createParsed(
BridgedASTContext cContext, BridgedSourceLoc cAtLoc,
BridgedSourceRange cRange, BridgedLifetimeEntry cEntry);
BridgedSourceRange cRange, BridgedLifetimeEntry cEntry, bool isUnderscored);

enum ENUM_EXTENSIBILITY_ATTR(closed) BridgedMacroSyntax {
BridgedMacroSyntaxFreestanding,
Expand Down
19 changes: 15 additions & 4 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ class DeclAttribute : public AttributeBase {

NumFeatures : 31
);

SWIFT_INLINE_BITFIELD(LifetimeAttr, DeclAttribute, 1,
isUnderscored : 1
);
} Bits;
// clang-format on

Expand Down Expand Up @@ -3351,26 +3355,33 @@ class LifetimeAttr final : public DeclAttribute {
LifetimeEntry *entry;

LifetimeAttr(SourceLoc atLoc, SourceRange baseRange, bool implicit,
LifetimeEntry *entry)
LifetimeEntry *entry, bool isUnderscored)
: DeclAttribute(DeclAttrKind::Lifetime, atLoc, baseRange, implicit),
entry(entry) {}
entry(entry) {
Bits.LifetimeAttr.isUnderscored = isUnderscored;
}

public:
static LifetimeAttr *create(ASTContext &context, SourceLoc atLoc,
SourceRange baseRange, bool implicit,
LifetimeEntry *entry);
LifetimeEntry *entry, bool isUnderscored);

LifetimeEntry *getLifetimeEntry() const { return entry; }

bool isUnderscored() const { return bool(Bits.LifetimeAttr.isUnderscored); }

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DeclAttrKind::Lifetime;
}

/// Create a copy of this attribute.
LifetimeAttr *clone(ASTContext &ctx) const {
return new (ctx) LifetimeAttr(AtLoc, Range, isImplicit(), entry);
return new (ctx)
LifetimeAttr(AtLoc, Range, isImplicit(), entry, isUnderscored());
}

std::string getString() const;

bool isEquivalent(const LifetimeAttr *other, Decl *attachedTo) const;
};

Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/DeclAttr.def
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@ DECL_ATTR(lifetime, Lifetime,
OnAccessor | OnConstructor | OnFunc | OnSubscript,
LongAttribute | ABIBreakingToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove | AllowMultipleAttributes | EquivalentInABIAttr,
161)
DECL_ATTR_ALIAS(_lifetime, Lifetime)

SIMPLE_DECL_ATTR(_addressableSelf, AddressableSelf,
OnAccessor | OnConstructor | OnFunc | OnSubscript,
Expand Down
17 changes: 10 additions & 7 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -8259,6 +8259,9 @@ ERROR(pack_iteration_where_clause_not_supported, none,
// MARK: Lifetime Dependence Syntax
//------------------------------------------------------------------------------

WARNING(use_lifetime_underscored, PointsToFirstBadToken,
"Unsupported use of @lifetime, use @_lifetime to specify lifetime dependencies", ())

ERROR(lifetime_dependence_invalid_param_name, none,
"invalid parameter name specified %0", (Identifier))
ERROR(lifetime_dependence_invalid_param_index, none,
Expand Down Expand Up @@ -8293,7 +8296,7 @@ ERROR(lifetime_dependence_immortal_alone, none,
"cannot specify any other dependence source along with immortal", ())
ERROR(lifetime_dependence_invalid_inherit_escapable_type, none,
"cannot copy the lifetime of an Escapable type, use "
"'@lifetime(%1%0)' instead",
"'@_lifetime(%1%0)' instead",
(StringRef, StringRef))
ERROR(lifetime_dependence_cannot_use_parsed_borrow_consuming, none,
"invalid use of %0 dependence with %1 ownership",
Expand Down Expand Up @@ -8324,11 +8327,11 @@ ERROR(lifetime_dependence_feature_required_inout, none,
(StringRef, Identifier))

ERROR(lifetime_dependence_cannot_infer_return, none,
"%0 with a ~Escapable result requires '@lifetime(...)'", (StringRef))
"%0 with a ~Escapable result requires '@_lifetime(...)'", (StringRef))
ERROR(lifetime_dependence_cannot_infer_mutating, none,
"%0 with a ~Escapable 'self' requires '@lifetime(self: ...)'", (StringRef))
"%0 with a ~Escapable 'self' requires '@_lifetime(self: ...)'", (StringRef))
ERROR(lifetime_dependence_cannot_infer_inout, none,
"%0 with a ~Escapable 'inout' parameter requires '@lifetime(%1: ...)'",
"%0 with a ~Escapable 'inout' parameter requires '@_lifetime(%1: ...)'",
(StringRef, Identifier))

//------------------------------------------------------------------------------
Expand All @@ -8339,15 +8342,15 @@ ERROR(lifetime_dependence_cannot_infer_return_no_param, none,
"%0 with a ~Escapable result needs a parameter to depend on",
(StringRef))
NOTE(lifetime_dependence_cannot_infer_return_immortal, none,
"'@lifetime(immortal)' can be used to indicate that values produced by "
"'@_lifetime(immortal)' can be used to indicate that values produced by "
"this initializer have no lifetime dependencies", ())
ERROR(lifetime_dependence_cannot_infer_bitwisecopyable, none,
"cannot infer lifetime dependence on %0 because '%1' is BitwiseCopyable, "
"specify '@lifetime(borrow self)'",
"specify '@_lifetime(borrow self)'",
(StringRef, StringRef))
ERROR(lifetime_dependence_cannot_infer_kind, none,
"cannot infer the lifetime dependence scope on %0 with a ~Escapable "
"parameter, specify '@lifetime(borrow %1)' or '@lifetime(copy %1)'",
"parameter, specify '@_lifetime(borrow %1)' or '@_lifetime(copy %1)'",
(StringRef, StringRef))
ERROR(lifetime_dependence_cannot_infer_scope_ownership, none,
"cannot borrow the lifetime of '%0', which has consuming ownership on %1",
Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,9 @@ class ModuleDecl
/// \returns true if this module is the "swift" standard library module.
bool isStdlibModule() const;

/// \returns true if this module is the "Cxx" module.
bool isCxxModule() const;

/// \returns true if this module is the "_Concurrency" standard library module.
bool isConcurrencyModule() const;

Expand Down
3 changes: 3 additions & 0 deletions include/swift/AST/PrintOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,9 @@ struct PrintOptions {
/// as public
bool SuppressIsolatedDeinit = false;

/// Suppress @_lifetime attribute and emit @lifetime instead.
bool SuppressLifetimes = false;

/// Whether to print the \c{/*not inherited*/} comment on factory initializers.
bool PrintFactoryInitializerComment = true;

Expand Down
3 changes: 3 additions & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,9 @@ EXPERIMENTAL_FEATURE(ModuleSelector, false)
/// in a file scope.
EXPERIMENTAL_FEATURE(DefaultIsolationPerFile, false)

/// Enable @_lifetime attribute
SUPPRESSIBLE_EXPERIMENTAL_FEATURE(Lifetimes, true)

#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
#undef EXPERIMENTAL_FEATURE
#undef UPCOMING_FEATURE
Expand Down
7 changes: 4 additions & 3 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1127,8 +1127,8 @@ class Parser {
);

/// Parse the @lifetime attribute.
ParserResult<LifetimeAttr> parseLifetimeAttribute(SourceLoc AtLoc,
SourceLoc Loc);
ParserResult<LifetimeAttr>
parseLifetimeAttribute(StringRef attrName, SourceLoc atLoc, SourceLoc loc);

/// Common utility to parse swift @lifetime decl attribute and SIL @lifetime
/// type modifier.
Expand Down Expand Up @@ -1158,7 +1158,8 @@ class Parser {

bool isParameterSpecifier() {
if (Tok.is(tok::kw_inout)) return true;
if (Context.LangOpts.hasFeature(Feature::LifetimeDependence) &&
if ((Context.LangOpts.hasFeature(Feature::LifetimeDependence) ||
Context.LangOpts.hasFeature(Feature::Lifetimes)) &&
isSILLifetimeDependenceToken())
return true;
if (!canHaveParameterSpecifierContextualKeyword()) return false;
Expand Down
7 changes: 7 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3261,6 +3261,13 @@ suppressingFeatureIsolatedDeinit(PrintOptions &options,
action();
}

static void
suppressingFeatureLifetimes(PrintOptions &options,
llvm::function_ref<void()> action) {
llvm::SaveAndRestore<bool> scope(options.SuppressLifetimes, true);
action();
}

namespace {
struct ExcludeAttrRAII {
std::vector<AnyAttrKind> &ExcludeAttrList;
Expand Down
19 changes: 15 additions & 4 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1690,7 +1690,11 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,

case DeclAttrKind::Lifetime: {
auto *attr = cast<LifetimeAttr>(this);
Printer << attr->getLifetimeEntry()->getString();
if (!attr->isUnderscored() || Options.SuppressLifetimes) {
Printer << "@lifetime" << attr->getLifetimeEntry()->getString();
} else {
Printer << "@_lifetime" << attr->getLifetimeEntry()->getString();
}
break;
}

Expand Down Expand Up @@ -1962,7 +1966,7 @@ StringRef DeclAttribute::getAttrName() const {
return "_allowFeatureSuppression";
}
case DeclAttrKind::Lifetime:
return "lifetime";
return cast<LifetimeAttr>(this)->isUnderscored() ? "_lifetime" : "lifetime";
}
llvm_unreachable("bad DeclAttrKind");
}
Expand Down Expand Up @@ -3224,8 +3228,15 @@ isEquivalent(const AllowFeatureSuppressionAttr *other, Decl *attachedTo) const {

LifetimeAttr *LifetimeAttr::create(ASTContext &context, SourceLoc atLoc,
SourceRange baseRange, bool implicit,
LifetimeEntry *entry) {
return new (context) LifetimeAttr(atLoc, baseRange, implicit, entry);
LifetimeEntry *entry, bool isUnderscored) {
return new (context)
LifetimeAttr(atLoc, baseRange, implicit, entry, isUnderscored);
}

std::string LifetimeAttr::getString() const {
return (isUnderscored() ? std::string("@_lifetime")
: std::string("@lifetime")) +
getLifetimeEntry()->getString();
}

bool LifetimeAttr::isEquivalent(const LifetimeAttr *other,
Expand Down
5 changes: 3 additions & 2 deletions lib/AST/Bridging/DeclAttributeBridging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -527,10 +527,11 @@ BridgedLifetimeEntry BridgedLifetimeEntry_createParsed(

BridgedLifetimeAttr BridgedLifetimeAttr_createParsed(
BridgedASTContext cContext, BridgedSourceLoc cAtLoc,
BridgedSourceRange cRange, BridgedLifetimeEntry cEntry) {
BridgedSourceRange cRange, BridgedLifetimeEntry cEntry,
bool isUnderscored) {
return LifetimeAttr::create(cContext.unbridged(), cAtLoc.unbridged(),
cRange.unbridged(), /*implicit=*/false,
cEntry.unbridged());
cEntry.unbridged(), isUnderscored);
}

BridgedMacroRole BridgedMacroRole_fromString(BridgedStringRef str) {
Expand Down
30 changes: 30 additions & 0 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,36 @@ static bool usesFeatureSendingArgsAndResults(Decl *decl) {
return false;
}

static bool findUnderscoredLifetimeAttr(Decl *decl) {
auto hasUnderscoredLifetimeAttr = [](Decl *decl) {
if (!decl->getAttrs().hasAttribute<LifetimeAttr>()) {
return false;
}
// Since we ban mixing @lifetime and @_lifetime on the same decl, checking
// any one LifetimeAttr on the decl is sufficient.
// FIXME: Implement the ban.
return decl->getAttrs().getAttribute<LifetimeAttr>()->isUnderscored();
};

switch (decl->getKind()) {
case DeclKind::Var: {
auto *var = cast<VarDecl>(decl);
return llvm::any_of(var->getAllAccessors(), hasUnderscoredLifetimeAttr);
}
default:
return hasUnderscoredLifetimeAttr(decl);
}
}

static bool usesFeatureLifetimeDependence(Decl *decl) {
if (decl->getAttrs().hasAttribute<LifetimeAttr>()) {
if (findUnderscoredLifetimeAttr(decl)) {
// Experimental feature Lifetimes will guard the decl.
return false;
}
return true;
}

if (auto *afd = dyn_cast<AbstractFunctionDecl>(decl)) {
return afd->getInterfaceType()
->getAs<AnyFunctionType>()
Expand All @@ -273,6 +299,10 @@ static bool usesFeatureLifetimeDependence(Decl *decl) {
return false;
}

static bool usesFeatureLifetimes(Decl *decl) {
return findUnderscoredLifetimeAttr(decl);
}

static bool usesFeatureInoutLifetimeDependence(Decl *decl) {
auto hasInoutLifetimeDependence = [](Decl *decl) {
for (auto attr : decl->getAttrs().getAttributes<LifetimeAttr>()) {
Expand Down
5 changes: 3 additions & 2 deletions lib/AST/LifetimeDependence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ LifetimeEntry::create(const ASTContext &ctx, SourceLoc startLoc,
}

std::string LifetimeEntry::getString() const {
std::string result = "@lifetime(";
std::string result = "(";
if (targetDescriptor.has_value()) {
result += targetDescriptor->getString();
result += ": ";
Expand Down Expand Up @@ -300,14 +300,15 @@ class LifetimeDependenceChecker {
assert(lifetimeDependencies.empty());

// Handle Builtins first because, even though Builtins require
// LifetimeDependence, we don't force Feature::LifetimeDependence
// LifetimeDependence, we don't force the experimental feature
// to be enabled when importing the Builtin module.
if (afd->isImplicit() && afd->getModuleContext()->isBuiltinModule()) {
inferBuiltin();
return currentDependencies();
}

if (!ctx.LangOpts.hasFeature(Feature::LifetimeDependence)
&& !ctx.LangOpts.hasFeature(Feature::Lifetimes)
&& !ctx.SourceMgr.isImportMacroGeneratedLoc(returnLoc)) {

// Infer inout dependencies without requiring a feature flag. On
Expand Down
4 changes: 4 additions & 0 deletions lib/AST/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2017,6 +2017,10 @@ bool ModuleDecl::isStdlibModule() const {
return !getParent() && getName() == getASTContext().StdlibModuleName;
}

bool ModuleDecl::isCxxModule() const {
return !getParent() && getName() == getASTContext().Id_Cxx;
}

bool ModuleDecl::isConcurrencyModule() const {
return !getParent() && getName() == getASTContext().Id_Concurrency;
}
Expand Down
8 changes: 3 additions & 5 deletions lib/ASTGen/Sources/ASTGen/DeclAttrs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1169,19 +1169,17 @@ extension ASTGenVisitor {
/// @lifetime(self)
/// ```
func generateLifetimeAttr(attribute node: AttributeSyntax) -> BridgedLifetimeAttr? {
guard self.ctx.langOptsHasFeature(.LifetimeDependence) else {
// TODO: Diagnose
fatalError("@lifetime attribute requires 'LifetimeDependence' feature")
}
guard let entry = self.generateLifetimeEntry(attribute: node) else {
// TODO: Diagnose?
return nil
}

return .createParsed(
self.ctx,
atLoc: self.generateSourceLoc(node.atSign),
range: self.generateAttrSourceRange(node),
entry: entry
entry: entry,
isUnderscored: node.attributeName.as(IdentifierTypeSyntax.self)?.name.text == "_lifetime"
)
}

Expand Down
6 changes: 3 additions & 3 deletions lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,7 @@ func getReturnLifetimeAttribute(
.attribute(
AttributeSyntax(
atSign: .atSignToken(),
attributeName: IdentifierTypeSyntax(name: "lifetime"),
attributeName: IdentifierTypeSyntax(name: "_lifetime"),
leftParen: .leftParenToken(),
arguments: .argumentList(LabeledExprListSyntax(args)),
rightParen: .rightParenToken()))
Expand Down Expand Up @@ -1445,7 +1445,7 @@ func containsLifetimeAttr(_ attrs: AttributeListSyntax, for paramName: TokenSynt
guard let attr = elem.as(AttributeSyntax.self) else {
continue
}
if attr.attributeName != "lifetime" {
if attr.attributeName != "_lifetime" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see traces of still supporting @lifetime in some restricted scenarios in this PR. This makes me wonder if this is also a codepath that should at least temporarily support both @lifetime and @_lifetime.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This codepath does not seem to be exercised in the stdlib/cxx modules - but only in case we are importing C++ as Swift in which we want @_lifetime instead of @lifetime.

cc @hnrklssn

continue
}
guard let args = attr.arguments?.as(LabeledExprListSyntax.self) else {
Expand Down Expand Up @@ -1479,7 +1479,7 @@ func paramLifetimeAttributes(
.attribute(
AttributeSyntax(
atSign: .atSignToken(),
attributeName: IdentifierTypeSyntax(name: "lifetime"),
attributeName: IdentifierTypeSyntax(name: "_lifetime"),
leftParen: .leftParenToken(),
arguments: .argumentList(LabeledExprListSyntax([LabeledExprSyntax(expression: expr)])),
rightParen: .rightParenToken())))
Expand Down
Loading