Skip to content

Commit dbcba01

Browse files
committed
Allow passing MutableSpan 'inout' without an experimental feature.
This adds a new lifetime inference rule, loosening the requirement for @Lifetime annotations even when the experimental LifetimeDependence mode is enabled. Additionally, it enables this new inference rule even when the experimental mode is disabled. All other inference rules continue to require the experimental feature. The rule is: If a function or method has a single inout non-Escapable parameter other than 'self' and has no other non-Escapable parameters including 'self', then infer a single @Lifetime(copy) dependency on the inout parameter from its own incoming value. This supports the common case in which the user of a non-Escapable type, such as MutableSpan, wants to modify the span's contents without modifying the span value itself. It should be possible to use MutableSpan this way without requiring any knowledge of lifetime annotations. The tradeoff is that it makes authoring non-Escapable types less safe. For example, a MutableSpan method could update the underlying unsafe pointer and forget to declare a dependence on the incoming pointer. Disallowing other non-Escapable parameters rules out the easy mistake of programmers attempting to trivially reassign the inout parameter. There's is no way to rule out the possibility that they derive another non-Escapable value from an Escapable parameteter. So users can still write the following: func reassign(s: inout MutableSpan<Int>, a: [Int]) { s = a.mutableSpan } The 'reassign' declaration will type check, but it's implementation will diagnose a lifetime error on 's'. Fixes rdar://150557314 ([nonescapable] Declaration of inout MutableSpan parameter requires LifetimeDependence experimental feature)
1 parent 6534b9b commit dbcba01

File tree

3 files changed

+119
-20
lines changed

3 files changed

+119
-20
lines changed

lib/AST/LifetimeDependence.cpp

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -309,13 +309,21 @@ class LifetimeDependenceChecker {
309309

310310
if (!ctx.LangOpts.hasFeature(Feature::LifetimeDependence)
311311
&& !ctx.SourceMgr.isImportMacroGeneratedLoc(returnLoc)) {
312+
313+
// Infer inout dependencies without requiring a feature flag. On
314+
// returning, 'lifetimeDependencies' contains any inferred
315+
// dependencies. This does not issue any diagnostics because any invalid
316+
// usage should generate a missing feature flag diagnostic instead.
317+
inferInoutParams();
318+
312319
diagnoseMissingResultDependencies(
313320
diag::lifetime_dependence_feature_required_return.ID);
314321
diagnoseMissingSelfDependencies(
315322
diag::lifetime_dependence_feature_required_mutating.ID);
316323
diagnoseMissingInoutDependencies(
317324
diag::lifetime_dependence_feature_required_inout.ID);
318-
return std::nullopt;
325+
326+
return currentDependencies();
319327
}
320328

321329
if (afd->getAttrs().hasAttribute<LifetimeAttr>()) {
@@ -851,22 +859,30 @@ class LifetimeDependenceChecker {
851859
return;
852860
}
853861

854-
// Infer mutating methods.
855-
if (hasImplicitSelfParam()) {
856-
if (isDiagnosedNonEscapable(dc->getSelfTypeInContext())) {
857-
assert(!isInit() && "class initializers have Escapable self");
858-
auto *selfDecl = afd->getImplicitSelfDecl();
859-
if (selfDecl->isInOut()) {
860-
// Mutating methods (excluding initializers)
861-
inferMutatingSelf(selfDecl);
862-
return;
863-
}
864-
}
865-
}
862+
// Infer mutating non-Escapable methods (excluding initializers).
863+
inferMutatingSelf();
864+
866865
// Infer inout parameters.
867866
inferInoutParams();
868867
}
869868

869+
/// If the current function is a mutating method and 'self' is non-Escapable,
870+
/// return 'self's ParamDecl.
871+
bool isMutatingNonEscapableSelf() {
872+
if (!hasImplicitSelfParam())
873+
return false;
874+
875+
if (!isDiagnosedNonEscapable(dc->getSelfTypeInContext()))
876+
return false;
877+
878+
assert(!isInit() && "class initializers have Escapable self");
879+
auto *selfDecl = afd->getImplicitSelfDecl();
880+
if (!selfDecl->isInOut())
881+
return false;
882+
883+
return true;
884+
}
885+
870886
// Infer method dependence: result depends on self. This includes _modify.
871887
void inferNonEscapableResultOnSelf() {
872888
Type selfTypeInContext = dc->getSelfTypeInContext();
@@ -1069,10 +1085,13 @@ class LifetimeDependenceChecker {
10691085
pushDeps(createDeps(resultIndex).add(*candidateParamIndex,
10701086
*candidateLifetimeKind));
10711087
}
1072-
1088+
10731089
// Infer a mutating 'self' dependency when 'self' is non-Escapable and the
10741090
// result is 'void'.
1075-
void inferMutatingSelf(ParamDecl *selfDecl) {
1091+
void inferMutatingSelf() {
1092+
if (!isMutatingNonEscapableSelf()) {
1093+
return;
1094+
}
10761095
// Handle implicit setters before diagnosing mutating methods. This
10771096
// does not include global accessors, which have no implicit 'self'.
10781097
if (auto accessor = dyn_cast<AccessorDecl>(afd)) {
@@ -1161,8 +1180,59 @@ class LifetimeDependenceChecker {
11611180
// Infer 'inout' parameter dependency when the only parameter is
11621181
// non-Escapable.
11631182
//
1164-
// This is needed for most generic Builtin functions.
1183+
// This supports the common case in which the user of a non-Escapable type,
1184+
// such as MutableSpan, wants to modify the span's contents without modifying
1185+
// the span value itself. It should be possible to use MutableSpan this way
1186+
// without requiring any knowledge of lifetime annotations. The tradeoff is
1187+
// that it makes authoring non-Escapable types less safe. For example, a
1188+
// MutableSpan method could update the underlying unsafe pointer and forget to
1189+
// declare a dependence on the incoming pointer.
1190+
//
1191+
// Disallowing other non-Escapable parameters rules out the easy mistake of
1192+
// programmers attempting to trivially reassign the inout parameter. There's
1193+
// is no way to rule out the possibility that they derive another
1194+
// non-Escapable value from an Escapable parameteter. So they can still write
1195+
// the following and will get a lifetime diagnostic:
1196+
//
1197+
// func reassign(s: inout MutableSpan<Int>, a: [Int]) {
1198+
// s = a.mutableSpan
1199+
// }
1200+
//
1201+
// Do not issue any diagnostics. This inference is triggered even when the
1202+
// feature is disabled!
11651203
void inferInoutParams() {
1204+
if (isMutatingNonEscapableSelf()) {
1205+
return;
1206+
}
1207+
std::optional<unsigned> candidateParamIndex;
1208+
bool hasNonEscapableParameter = false;
1209+
if (hasImplicitSelfParam()
1210+
&& isDiagnosedNonEscapable(dc->getSelfTypeInContext())) {
1211+
hasNonEscapableParameter = true;
1212+
}
1213+
for (unsigned paramIndex : range(afd->getParameters()->size())) {
1214+
auto *param = afd->getParameters()->get(paramIndex);
1215+
if (isDiagnosedNonEscapable(
1216+
afd->mapTypeIntoContext(param->getInterfaceType()))) {
1217+
if (param->isInOut()) {
1218+
if (hasNonEscapableParameter)
1219+
return;
1220+
candidateParamIndex = paramIndex;
1221+
continue;
1222+
}
1223+
if (candidateParamIndex)
1224+
return;
1225+
1226+
hasNonEscapableParameter = true;
1227+
}
1228+
}
1229+
if (candidateParamIndex) {
1230+
pushDeps(createDeps(*candidateParamIndex).add(
1231+
*candidateParamIndex, LifetimeDependenceKind::Inherit));
1232+
}
1233+
}
1234+
1235+
void inferUnambiguousInoutParams() {
11661236
if (afd->getParameters()->size() != 1) {
11671237
return;
11681238
}
@@ -1181,7 +1251,7 @@ class LifetimeDependenceChecker {
11811251

11821252
void inferBuiltin() {
11831253
// Normal inout parameter inference works for most generic Builtins.
1184-
inferInoutParams();
1254+
inferUnambiguousInoutParams();
11851255
if (!lifetimeDependencies.empty()) {
11861256
return;
11871257
}

test/Sema/lifetime_depend_infer.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ struct EscapableNonTrivialSelf {
129129
@lifetime(borrow self)
130130
func methodNoParamBorrow() -> NEImmortal { NEImmortal() }
131131

132-
func mutatingMethodNoParam() -> NEImmortal { NEImmortal() }
132+
mutating func mutatingMethodNoParam() -> NEImmortal { NEImmortal() }
133+
134+
func methodInoutNonEscapableParam(_: inout NE) {}
135+
136+
mutating func mutatingMethodInoutNonEscapableParam(_: inout NE) {}
133137

134138
@lifetime(self)
135139
mutating func mutatingMethodNoParamLifetime() -> NEImmortal { NEImmortal() }
@@ -276,6 +280,10 @@ func neParamInoutLifetime(ne: inout NE) -> NE { ne }
276280

277281
func neTwoParam(ne: NE, _:Int) -> NE { ne } // expected-error{{a function with a ~Escapable result requires '@lifetime(...)'}}
278282

283+
func voidInoutOneParam(_: inout NE) {} // OK
284+
285+
func voidInoutTwoParams(_: inout NE, _: Int) {} // OK
286+
279287
// =============================================================================
280288
// Handle Accessors:
281289
//

test/Sema/lifetime_depend_nofeature.swift

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,37 @@ struct EmptyNonEscapable: ~Escapable {} // expected-error{{an implicit initializ
1111
// Don't allow non-Escapable return values.
1212
func neReturn(span: RawSpan) -> RawSpan { span } // expected-error{{a function with a ~Escapable result requires '-enable-experimental-feature LifetimeDependence'}}
1313

14-
func neInout(span: inout RawSpan) {} // expected-error{{a function with a ~Escapable 'inout' parameter requires '-enable-experimental-feature LifetimeDependence'}}
14+
func neInout(span: inout RawSpan) {} // OK
15+
16+
func neInoutNEParam(span: inout RawSpan, _: RawSpan) {} // expected-error{{a function with a ~Escapable 'inout' parameter requires '-enable-experimental-feature LifetimeDependence'}}
1517

1618
struct S {
1719
func neReturn(span: RawSpan) -> RawSpan { span } // expected-error{{a method with a ~Escapable result requires '-enable-experimental-feature LifetimeDependence'}}
1820

19-
func neInout(span: inout RawSpan) {} // expected-error{{a method with a ~Escapable 'inout' parameter requires '-enable-experimental-feature LifetimeDependence'}}
21+
func neInout(span: inout RawSpan) {} // OK
22+
23+
func neInoutNEParam(span: inout RawSpan, _: RawSpan) {} // expected-error{{a method with a ~Escapable 'inout' parameter requires '-enable-experimental-feature LifetimeDependence'}}
24+
25+
mutating func mutatingNEInout(span: inout RawSpan) {} // OK
26+
27+
mutating func mutatingNEInoutParam(span: inout RawSpan, _: RawSpan) {} // expected-error{{a mutating method with a ~Escapable 'inout' parameter requires '-enable-experimental-feature LifetimeDependence'}}
2028
}
2129

2230
class C {
2331
func neReturn(span: RawSpan) -> RawSpan { span } // expected-error{{a method with a ~Escapable result requires '-enable-experimental-feature LifetimeDependence'}}
2432

33+
func neInout(span: inout RawSpan) {} // OK
34+
}
35+
36+
extension MutableSpan {
37+
func method() {} // OK
38+
39+
mutating func mutatingMethod() {} // expected-error{{a mutating method with ~Escapable 'self' requires '-enable-experimental-feature LifetimeDependence'}}
40+
41+
func neReturn(span: RawSpan) -> RawSpan { span } // expected-error{{a method with a ~Escapable result requires '-enable-experimental-feature LifetimeDependence'}}
42+
2543
func neInout(span: inout RawSpan) {} // expected-error{{a method with a ~Escapable 'inout' parameter requires '-enable-experimental-feature LifetimeDependence'}}
44+
45+
mutating func mutatingNEInout(span: inout RawSpan) {} // expected-error{{a mutating method with ~Escapable 'self' requires '-enable-experimental-feature LifetimeDependence'}}
46+
// expected-error@-1{{a mutating method with a ~Escapable 'inout' parameter requires '-enable-experimental-feature LifetimeDependence'}}
2647
}

0 commit comments

Comments
 (0)