Skip to content
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

[red-knot] use an invariant lifetime to avoid exposing unsafe API for AstNodeRef #12055

Closed
carljm opened this issue Jun 26, 2024 · 2 comments
Closed
Labels
red-knot Multi-file analysis & type inference

Comments

@carljm
Copy link
Contributor

carljm commented Jun 26, 2024

I've seen some crates making clever use of invariant lifetimes (e.g. via a PhantomData which is an fn(&'a ()) -> &'a ()). I haven't worked out the details yet, but I think in principle this technique should be exactly what we need to have the borrow checker enforce that the the descendant node passed to AstNodeRef::new is actually from the tree of the root node given. If they have exactly the same lifetime (not just lifetimes where a meet can be found), that gives the guarantee we need.

So not high priority, but at some point we could try that out and see if it allows us to better encapsulate the unsafe.

@carljm carljm added the red-knot Multi-file analysis & type inference label Jun 26, 2024
@carljm carljm changed the title use an invariant lifetime to avoid exposing unsafe API for AstNodeRef [red-knot] use an invariant lifetime to avoid exposing unsafe API for AstNodeRef Jun 26, 2024
@MichaReiser
Copy link
Member

MichaReiser commented Jun 27, 2024

Do you still have the crates around? It would be helpful to better understand what you're describing here.

I think we discussed that the signature fn new<'a>(root: &'a ParsedModule, child: &'a T) -> Self isn't enough because that only constraints that root and child have a common lifetime, but that would also permit:

let a = parse_module("a.py");
let b = parse_module("b.py");

AstNodeRef::new(&a, a.syntax()); // Works as expected
AstNodeRef::new(&a, b.syntax()); // should not work but works too

Calling new with a as root but with b as a childworks just fine because bothaandb` life as long as the enclosing scope.

I think what would work is the following signature:

fn new<'a>(root: &'a ParsedModule, child: FnOnce(&'a ParsedModule) -> &'a T): -> Self

The problem here is that it would require to reselect child from root, which we have no convenient or performant way of doing.

@carljm
Copy link
Contributor Author

carljm commented Jun 27, 2024

The most recent crate where I ran across this idea, which prompted me to file this issue (just as a reminder to look into it some time in the future, since I don't think it's a priority) was https://docs.rs/compact_arena/latest/src/compact_arena/lib.rs.html#103

Searching just now I ran across this article about it: https://lord.io/lifetimes-as-tokens/

But my tentative conclusion from skimming this article is that this technique is probably more hacky and painful than it's worth, at least for our case. The current approach with unsafe is fine in practice. So I think we should just close this.

@carljm carljm closed this as not planned Won't fix, can't repro, duplicate, stale Jun 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
red-knot Multi-file analysis & type inference
Projects
None yet
Development

No branches or pull requests

2 participants