Skip to content

Commit e240dd5

Browse files
authored
Merge pull request swiftlang#81656 from atrick/lifedep-inout-infer
Allow passing MutableSpan 'inout' without an experimental feature.
2 parents e805d93 + c04e555 commit e240dd5

File tree

5 files changed

+126
-21
lines changed

5 files changed

+126
-21
lines changed

lib/AST/Evaluator.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ void Evaluator::diagnoseCycle(const ActiveRequest &request) {
127127
for (const auto &step : llvm::reverse(activeRequests)) {
128128
if (step == request) return;
129129

130+
// Reporting the lifetime dependence location generates a redundant
131+
// diagnostic.
132+
if (step.getAs<LifetimeDependenceInfoRequest>()) {
133+
continue;
134+
}
135+
130136
step.noteCycleStep(diags);
131137
}
132138

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
}

test/decl/protocol/protocols.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ struct DoesNotConform : Up {
111111

112112
// Circular protocols
113113

114+
protocol CircleStart : CircleEnd { func circle_start() } // expected-error 2 {{protocol 'CircleStart' refines itself}}
114115
protocol CircleMiddle : CircleStart { func circle_middle() }
115116
// expected-note@-1 2 {{protocol 'CircleMiddle' declared here}}
116-
protocol CircleStart : CircleEnd { func circle_start() } // expected-error 2 {{protocol 'CircleStart' refines itself}}
117117
protocol CircleEnd : CircleMiddle { func circle_end()} // expected-note 2 {{protocol 'CircleEnd' declared here}}
118118

119119
protocol CircleEntry : CircleTrivial { }

0 commit comments

Comments
 (0)