Skip to content

minimally implement is (RFC 3573), sans parsing #144174

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

dianne
Copy link
Contributor

@dianne dianne commented Jul 19, 2025

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 let expression would be permitted, expr is pat desugars to let pat = expr.
  • Anywhere else, the &&-chain containing the is is wrapped in an if condition, then is is desugared to let: ... && expr is pat &&... becomes if ... && 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

  1. Since let-chains already exist, I'd be curious if it'd make sense to restrict the scope of is further, 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 mixing is and let in &&-chains should work. Alternatively, I'd be happy with shortening the lifetime of let expressions' temporaries by default and keeping is consistent with let. I've been running into some trouble with let temporary lifetimes in designing if let guard patterns too.

@rustbot
Copy link
Collaborator

rustbot commented Jul 19, 2025

joshtriplett is not on the review rotation at the moment.
They may take a while to respond.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-clippy Relevant to the Clippy team. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jul 19, 2025
@rustbot
Copy link
Collaborator

rustbot commented Jul 19, 2025

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

@rust-log-analyzer

This comment has been minimized.

dianne added 2 commits July 19, 2025 04:47
Until it can be parsed properly, `expr is pat` is written
`builtin # is(expr is pat)`.

To be more concise, the included tests use a macro wrapper around it.
Outside of the top level of an `if`/`while` condition,
`... && expr is pat && ...`
becomes
`if ... && let pat = expr && ... { true } else { false }`.
@rustbot
Copy link
Collaborator

rustbot commented Jul 19, 2025

Some changes occurred in src/tools/rustfmt

cc @rust-lang/rustfmt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-clippy Relevant to the Clippy team. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants