minimally implement is (RFC 3573), sans parsing by dianne · Pull Request #144174 · rust-lang/rust (original) (raw)
This PR partially implements rust-lang/rfcs#3573 for experimentation. It's not yet suitable for general use and may not yet reflect the intended design, but this should hopefully make it easier to explore its design space. r? @joshtriplett for design concerns
Placeholder syntax
Since is isn't parsed as an infix operator, I'm using permanently-unstable builtin# syntax as a placeholder. Instead of expr is pat, write builtin # is(expr is pat) or define a macro expanding to that. One possible improvement if raw keywords (RFC 3098) were implemented would be to use k#is as an infix operator. My understanding is that this syntax is already reserved by RFC 3101.
Scoping
I've opted for a simple and restrictive interpretation of the scoping rules in the RFC, based on the rules for let-chains:
// For is in an && operator chain at the top-level of an if or while's
// condition, such that a let expression would be permitted, is's bindings
// and temporaries are in-scope for the success branch of the condition, as if
// let pat = expr were written instead of expr is pat.
if mutex.lock().unwrap().test() is true {
// Like with if let, the mutex guard isn't dropped, so it remains locked.
}
// Otherwise, an is expression's bindings and temporaries are in scope for the
// remainder of the && operator chain it's in.
if (mutex.lock().unwrap().test() is true) {
// let isn't allowed in parentheses. For this PR's interpretation of is,
// that means it introduced a new scope. The mutex is unlocked here.
}
I don't think this is necessarily the intended scope for is (or the ideal scope for let expressions in that first case, honestly), but my hope is that it will be easier to refine given a concrete implementation1. Lints or errors to prevent shadowing mistakes are left for future work.
Desugaring
builtin # is(expr is pat) is desugared as follows when lowering to HIR:
- In a condition where a
letexpression would be permitted,expr is patdesugars tolet pat = expr. - Anywhere else, the
&&-chain containing theisis wrapped in anifcondition, thenisis desugared tolet:... && expr is pat &&...becomesif ... && let pat = expr && ... { true } else { false }.
This results in non-ideal MIR in some cases, but jump-threading optimizations should hopefully clean it up. I haven't included any tests for that, since this implementation isn't meant for production use. This also doesn't currently enforce Rust 2024 scoping rules for ifs in its desugaring; how to handle older editions is left as an open question.
Behavior in macro expansions
I couldn't find discussion of macros on the RFC, so I've taken the simplest approach: it doesn't really work yet. Similar to let statements but unlike let expressions, you can put builtin # is($e is $p) in a macro and it will introduce its pattern's bindings into scope as if it were inlined into its expansion site. This doesn't work for let expressions because of how macros are parsed: whether let is allowed is determined when parsing, and the macro doesn't have the context of its expansion site to work with, so it doesn't know if it's being expanded into a condition or not. There's one catch though: because of this, most parts of the compiler that work with let expressions assume && operators associate to the left. Since macro expansions sites aren't re-parsed (cc #61733 (comment)), macros expanding to &&-chains containing is operators will break that assumption: putting one of those expansions on the right-hand-side of an && will cause this implementation to panic in check_match (and also likely make some incorrect assumptions in region_scope_tree).
I've also tried to handle attributes on is correctly. There's no tests since it would be impossible to put an attribute directly on an is operator expression currently without parentheses (cc #127436), but it may eventually be possible if attributes can apply to macro expansions (cc #63221).
Feature gate and tracking issue
None yet. I can add those if there's interest in merging these changes. Since the current builtin # is(expr is pat) syntax is permanently unstable, this PR does not stabilize anything.
Footnotes
- Since
let-chains already exist, I'd be curious if it'd make sense to restrict the scope ofisfurther, to avoid its scope depending on whether it appears in a condition. Possibly some of the questions around shadowing would be easier to resolve too. But it raises some questions around temporary lifetimes and how mixingisandletin&&-chains should work. Alternatively, I'd be happy with shortening the lifetime ofletexpressions' temporaries by default and keepingisconsistent withlet. I've been running into some trouble withlettemporary lifetimes in designingif letguard patterns too. ↩