Skip to content

Allow passing MutableSpan 'inout' without an experimental feature. #81656

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 3 commits into from
May 21, 2025

Conversation

atrick
Copy link
Contributor

@atrick atrick commented May 20, 2025

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)

@atrick
Copy link
Contributor Author

atrick commented May 21, 2025

@swift-ci test

@atrick
Copy link
Contributor Author

atrick commented May 21, 2025

This is also failing on main:
ModuleInterface/clang-args-transitive-availability.swift

@atrick
Copy link
Contributor Author

atrick commented May 21, 2025

@swift-ci test

@atrick
Copy link
Contributor Author

atrick commented May 21, 2025

@slavapestov @DougGregor in this commit
b71cc44

I have suppressed the extra "through reference here" diagnostic by ignoring LifetimeDependenceRequest. But I noticed that we have redundant "through reference here" notes in other cases. Does this suppression make sense or should I allow the redundant note?

@atrick
Copy link
Contributor Author

atrick commented May 21, 2025

@slavapestov @DougGregor is it acceptable to change the order of declarations in test/decl/protocols/protocols.swift?
c40fd2a

On this test, we now say protocol 'CircleMiddle' refines itself instead of protocol 'CircleStart refines itself. I don't see why one would be preferred over the other. In fact, CircleMiddle is declared first. Is this change acceptable? Simply reordering the declarations produces the original output.

protocol 'CircleMiddle' refines itself

protocol CircleMiddle : CircleStart { func circle_middle() }
// expected-note@-1 2 {{protocol 'CircleMiddle' declared here}}
protocol CircleStart : CircleEnd { func circle_start() } // expected-error 2 {{protocol 'CircleStart' refines itself}}
protocol CircleEnd : CircleMiddle { func circle_end()} // expected-note 2 {{protocol 'CircleEnd' declared here}}

Current main:

protocols.swift:7:10: error: protocol 'CircleStart' refines itself
3 | // Circular protocols
4 |
5 | protocol CircleMiddle : CircleStart { func circle_middle() }
  |          `- note: protocol 'CircleMiddle' declared here
6 | // expected-note@-1 2 {{protocol 'CircleMiddle' declared here}}
7 | protocol CircleStart : CircleEnd { func circle_start() } // expected-error 2 {{protocol 'CircleStart' refines itself}}
  |          `- error: protocol 'CircleStart' refines itself
8 | protocol CircleEnd : CircleMiddle { func circle_end()} // expected-note 2 {{protocol 'CircleEnd' declared here}}
  |          `- note: protocol 'CircleEnd' declared here

With this PR:

protocols.swift:5:10: error: protocol 'CircleMiddle' refines itself
3 | // Circular protocols
4 |
5 | protocol CircleMiddle : CircleStart { func circle_middle() }
  |          `- error: protocol 'CircleMiddle' refines itself
6 | // expected-note@-1 2 {{protocol 'CircleMiddle' declared here}}
7 | protocol CircleStart : CircleEnd { func circle_start() } // expected-error 2 {{protocol 'CircleStart' refines itself}}
  |          `- note: protocol 'CircleStart' declared here
8 | protocol CircleEnd : CircleMiddle { func circle_end()} // expected-note 2 {{protocol 'CircleEnd' declared here}}
  |          `- note: protocol 'CircleEnd' declared here
9 |

@atrick
Copy link
Contributor Author

atrick commented May 21, 2025

@meg-gupta did you run into similar problems before you guarded all the lifetime dependence type checking by the feature flag?

Copy link
Member

@DougGregor DougGregor left a comment

Choose a reason for hiding this comment

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

LGTM, thank you!

@atrick
Copy link
Contributor Author

atrick commented May 21, 2025

@swift-ci test

@atrick atrick enabled auto-merge May 21, 2025 07:21
atrick added 3 commits May 21, 2025 00:25
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)
When reporting the declarations that lead to a cycle, we end up printing an
extra "through reference here" on the function declaration:

class C2: C1, P {
   |       |- note: through reference here
   |       `- note: through reference here
15 |     // expected-note@-1 2{{through reference here}}
16 |     override func run(a: A) {}
   |                   |   |  `- note: while resolving type 'A'
   |                   |   `- note: through reference here
   |                   |- error: circular reference
   |                   |- note: through reference here
   |                   `- note: through reference here
Evaluating LifetimeDependence changes the order that the declarations are
diagnosed. Fix this test output by flipping the order of two declarations. The
new order actually makes more sense to me.
@atrick atrick force-pushed the lifedep-inout-infer branch from 22d477d to c04e555 Compare May 21, 2025 07:25
@atrick
Copy link
Contributor Author

atrick commented May 21, 2025

@swift-ci test

@atrick
Copy link
Contributor Author

atrick commented May 21, 2025

Unrelated linux failure

[2025-05-21T08:37:12.792Z] Unresolved Tests (1):
[2025-05-21T08:37:12.792Z]   lldb-api :: lang/swift/completion/TestSwiftREPLCompletion.py

@atrick
Copy link
Contributor Author

atrick commented May 21, 2025

@swift-ci smoke test linux

@atrick atrick merged commit e240dd5 into swiftlang:main May 21, 2025
4 of 5 checks passed
@atrick atrick deleted the lifedep-inout-infer branch May 21, 2025 18:52
hamishknight added a commit to hamishknight/swift that referenced this pull request May 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants