Introduce a new suppressible experimental feature to guard @_lifetime

This commit is contained in:
Meghana Gupta
2025-06-06 14:18:31 -07:00
parent 0dfa1fc312
commit dcf072f9d0
9 changed files with 73 additions and 17 deletions

View File

@@ -395,6 +395,9 @@ public:
/// as public /// as public
bool SuppressIsolatedDeinit = false; bool SuppressIsolatedDeinit = false;
/// Suppress @_lifetime attribute and emit @lifetime instead.
bool SuppressLifetimes = false;
/// Whether to print the \c{/*not inherited*/} comment on factory initializers. /// Whether to print the \c{/*not inherited*/} comment on factory initializers.
bool PrintFactoryInitializerComment = true; bool PrintFactoryInitializerComment = true;

View File

@@ -525,6 +525,9 @@ EXPERIMENTAL_FEATURE(ModuleSelector, false)
/// in a file scope. /// in a file scope.
EXPERIMENTAL_FEATURE(DefaultIsolationPerFile, false) EXPERIMENTAL_FEATURE(DefaultIsolationPerFile, false)
/// Enable @_lifetime attribute
SUPPRESSIBLE_EXPERIMENTAL_FEATURE(Lifetimes, true)
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
#undef EXPERIMENTAL_FEATURE #undef EXPERIMENTAL_FEATURE
#undef UPCOMING_FEATURE #undef UPCOMING_FEATURE

View File

@@ -1158,7 +1158,8 @@ public:
bool isParameterSpecifier() { bool isParameterSpecifier() {
if (Tok.is(tok::kw_inout)) return true; 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()) isSILLifetimeDependenceToken())
return true; return true;
if (!canHaveParameterSpecifierContextualKeyword()) return false; if (!canHaveParameterSpecifierContextualKeyword()) return false;

View File

@@ -3261,6 +3261,13 @@ suppressingFeatureIsolatedDeinit(PrintOptions &options,
action(); action();
} }
static void
suppressingFeatureLifetimes(PrintOptions &options,
llvm::function_ref<void()> action) {
llvm::SaveAndRestore<bool> scope(options.SuppressLifetimes, true);
action();
}
namespace { namespace {
struct ExcludeAttrRAII { struct ExcludeAttrRAII {
std::vector<AnyAttrKind> &ExcludeAttrList; std::vector<AnyAttrKind> &ExcludeAttrList;

View File

@@ -1690,7 +1690,11 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
case DeclAttrKind::Lifetime: { case DeclAttrKind::Lifetime: {
auto *attr = cast<LifetimeAttr>(this); auto *attr = cast<LifetimeAttr>(this);
Printer << attr->getString(); if (!attr->isUnderscored() || Options.SuppressLifetimes) {
Printer << "@lifetime" << attr->getLifetimeEntry()->getString();
} else {
Printer << "@_lifetime" << attr->getLifetimeEntry()->getString();
}
break; break;
} }

View File

@@ -258,10 +258,36 @@ static bool usesFeatureSendingArgsAndResults(Decl *decl) {
return false; 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) { static bool usesFeatureLifetimeDependence(Decl *decl) {
if (decl->getAttrs().hasAttribute<LifetimeAttr>()) { if (decl->getAttrs().hasAttribute<LifetimeAttr>()) {
if (findUnderscoredLifetimeAttr(decl)) {
// Experimental feature Lifetimes will guard the decl.
return false;
}
return true; return true;
} }
if (auto *afd = dyn_cast<AbstractFunctionDecl>(decl)) { if (auto *afd = dyn_cast<AbstractFunctionDecl>(decl)) {
return afd->getInterfaceType() return afd->getInterfaceType()
->getAs<AnyFunctionType>() ->getAs<AnyFunctionType>()
@@ -273,6 +299,10 @@ static bool usesFeatureLifetimeDependence(Decl *decl) {
return false; return false;
} }
static bool usesFeatureLifetimes(Decl *decl) {
return findUnderscoredLifetimeAttr(decl);
}
static bool usesFeatureInoutLifetimeDependence(Decl *decl) { static bool usesFeatureInoutLifetimeDependence(Decl *decl) {
auto hasInoutLifetimeDependence = [](Decl *decl) { auto hasInoutLifetimeDependence = [](Decl *decl) {
for (auto attr : decl->getAttrs().getAttributes<LifetimeAttr>()) { for (auto attr : decl->getAttrs().getAttributes<LifetimeAttr>()) {

View File

@@ -300,7 +300,7 @@ public:
assert(lifetimeDependencies.empty()); assert(lifetimeDependencies.empty());
// Handle Builtins first because, even though Builtins require // 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. // to be enabled when importing the Builtin module.
if (afd->isImplicit() && afd->getModuleContext()->isBuiltinModule()) { if (afd->isImplicit() && afd->getModuleContext()->isBuiltinModule()) {
inferBuiltin(); inferBuiltin();
@@ -308,6 +308,7 @@ public:
} }
if (!ctx.LangOpts.hasFeature(Feature::LifetimeDependence) if (!ctx.LangOpts.hasFeature(Feature::LifetimeDependence)
&& !ctx.LangOpts.hasFeature(Feature::Lifetimes)
&& !ctx.SourceMgr.isImportMacroGeneratedLoc(returnLoc)) { && !ctx.SourceMgr.isImportMacroGeneratedLoc(returnLoc)) {
// Infer inout dependencies without requiring a feature flag. On // Infer inout dependencies without requiring a feature flag. On

View File

@@ -5374,7 +5374,8 @@ ParserStatus Parser::ParsedTypeAttributeList::slowParse(Parser &P) {
} }
if (P.isSILLifetimeDependenceToken()) { if (P.isSILLifetimeDependenceToken()) {
if (!P.Context.LangOpts.hasFeature(Feature::LifetimeDependence)) { if (!P.Context.LangOpts.hasFeature(Feature::LifetimeDependence) &&
!P.Context.LangOpts.hasFeature(Feature::Lifetimes)) {
P.diagnose(Tok, diag::requires_experimental_feature, P.diagnose(Tok, diag::requires_experimental_feature,
"lifetime dependence specifier", false, "lifetime dependence specifier", false,
Feature::LifetimeDependence.getName()); Feature::LifetimeDependence.getName());

View File

@@ -8226,19 +8226,25 @@ void AttributeChecker::visitWeakLinkedAttr(WeakLinkedAttr *attr) {
} }
void AttributeChecker::visitLifetimeAttr(LifetimeAttr *attr) { void AttributeChecker::visitLifetimeAttr(LifetimeAttr *attr) {
// Allow @lifetime only in the stdlib, cxx and backward compatibility modules if (!attr->isUnderscored()) {
if (!attr->isUnderscored() && // Allow @lifetime only in the stdlib, cxx and backward compatibility
!(Ctx.MainModule->isStdlibModule() || Ctx.MainModule->isCxxModule() || // modules under -enable-experimental-feature LifetimeDependence
Ctx.MainModule->getABIName() == Ctx.StdlibModuleName)) { if (!Ctx.MainModule->isStdlibModule() && !Ctx.MainModule->isCxxModule() &&
Ctx.Diags.diagnose(attr->getLocation(), diag::use_lifetime_underscored); Ctx.MainModule->getABIName() != Ctx.StdlibModuleName) {
} Ctx.Diags.diagnose(attr->getLocation(), diag::use_lifetime_underscored);
if (!Ctx.LangOpts.hasFeature(Feature::LifetimeDependence) && }
!Ctx.SourceMgr.isImportMacroGeneratedLoc(attr->getLocation())) { if (!Ctx.LangOpts.hasFeature(Feature::LifetimeDependence)) {
diagnose(attr->getLocation(), diag::requires_experimental_feature, diagnose(attr->getLocation(), diag::requires_experimental_feature,
std::string("@") + (attr->isUnderscored() "@lifetime", false, Feature::LifetimeDependence.getName());
? std::string("_lifetime") }
: std::string("lifetime")), } else {
false, Feature::LifetimeDependence.getName()); // Allow @_lifetime under -enable-experimental-feature Lifetimes
if (!Ctx.LangOpts.hasFeature(Feature::Lifetimes) &&
!Ctx.SourceMgr.isImportMacroGeneratedLoc(attr->getLocation())) {
diagnose(attr->getLocation(), diag::requires_experimental_feature,
"@_lifetime",
false, Feature::Lifetimes.getName());
}
} }
} }