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

Pattern matches with ranges & unsucessful guards don't behave correctly #13027

Closed
ftxqxd opened this issue Mar 20, 2014 · 3 comments · Fixed by #13034
Closed

Pattern matches with ranges & unsucessful guards don't behave correctly #13027

ftxqxd opened this issue Mar 20, 2014 · 3 comments · Fixed by #13034

Comments

@ftxqxd
Copy link
Contributor

ftxqxd commented Mar 20, 2014

The following code prints 3 instead of 2:

println!("{}", match 1 {
    1 if false => 1,
    1..2 => 2,
    _ => 3
});

Removing the first arm (1 if false => ...) makes it print 2 as expected.

@nikomatsakis
Copy link
Contributor

Based on reading PR #13034 and the code, I think the bug is a bug in enter_opt, which doesn't properly consider guards and the possible overlap between literals and ranges. However, I don't think the fix in PR #13034 is quite right -- opt_eq is not meant to be a general "might match" check.

What enter_opt seems to currently do is to iterate over the list of patterns, given that the given option opt matched. So, in the example test case, the state would be that the remaining patterns are:

  1. literal(1) with guard
  2. range(1, 2)
  3. wildcard

and the opt here is literal(1). What enter_opt incorrectly does is to consider 1 and 3 a match but not 2. I'm not....100% sure what the intended answer is here actually, and the introduction of a guard makes things more complicated. In particular I don't quite understand where the precedence of earlier matches comes into play.

Imagine that there were no guard: in that case, I could imagine the correct answer being just 1, because that match has the highest precedence. But the code doesn't appear to be setup that way, it seems to test each option independently with no regard for order. Therefore I gather that it would yield (in the case with no guard) 1 and 3. And then I think in the next recursion call to compile_match(), we would not consider option 3 because it is a "default" match. (Ah, I see. What was confusing me is how a match like this would work match foo { 1 => x, 1 => y, _ => z }. It seems like we would infinitely recurse because enter_opt would always yield both options. But the answer is that we rule these cases out by an earlier check, which reports that the second 1 is unreachable.)

Note: Marijn's article may shed some light on the intended workings of the code. For some reason I haven't read it. I'm a glutton for punishment I guess. I really ought to, particularly since he writes so well.

Based on all this, the right fix is... not immediately obvious to me. One option is to have enter_opt consider guards and precedence, but as I said that doesn't seem to be what the code is setup to do. Another option is to handle guards early -- right now, if the next option in the list has a guard, we will execute it, but only if the pattern is fully matched. I could imagine trying to test the remaining bits of pattern imperatively and test the guard, but that'd be a whole other code path (it might be one that we want, actually, if we are going to permit overloadable deref in patterns...but that's a separate bug).

Having enter_opt unconditionally consider literals as matching against ranges feels a bit creepy. I think the code would fail to terminate for a test case like match foo { 1 => ..., 1 => 3 => ... }. Perhaps if the exhaustiveness check reported an error for this case, but it doesn't. Also, I think there is a related bug that the current patch would not solve, though if we patched up enter_opt more thoroughly it might:

match 2 { 
    1 .. 3 if false => { ... }
    1 .. 4 => { ... }
     _ => { .. }
 }

My conclusion is thus that the best thing would be to change enter_opt to consider guards and precedence, but I want to read marijn's article maybe and see if it offers a 3rd way.

@edwardw
Copy link
Contributor

edwardw commented Mar 21, 2014

I think I understand the logic of enter_opt now. It translates the following:

match val {
    m1 if g1 => b1,
    m2 => b2,
    _ => b3
}

to something like:

if match(val, m1) { b1 or b3 }  // m1 is stricter than m2, so no need to have b2 here
else if match(val, m2) { b2 or b3 }
else { b3 }

By the virtue of the fact that we are in trans already, enter_opt assumes m1 is "stricter" than m2 because otherwise compiler should've bailed out with "unreachable pattern" error. Unfortunately, "m1 + guard" is not necessarily stricter than m2 since guard function may return false.

Note,

match val {
    m1 if g1 => b1,
    m1 => b2,
    _ => b3
}

is translated correctly to:

if match(val, m1) { b1 or b2 or b3 }
else { b3 }

because we have exact equality of two m1 here.

So, we probably need to change the semantic of opt_eq to do range overlap, single value + range overlap and range + single value overlap check after all. A possible optimization is that the extended check is only necessary if there's a guard.

@edwardw
Copy link
Contributor

edwardw commented Mar 22, 2014

I've revised #13034. It now only touches enter_opt and passes a more comprehensive test. It also fixes #12582. Please review.

bors added a commit that referenced this issue Mar 27, 2014
The `_match.rs` takes advantage of passes prior to `trans` and
aggressively prunes the sub-match tree based on exact equality. When it
comes to literal or range, the strategy may lead to wrong result if
there's guard function or multiple patterns inside tuple.

Closes #12582.
Closes #13027.
@bors bors closed this as completed in 4112941 Mar 27, 2014
edwardw added a commit to edwardw/rust that referenced this issue May 5, 2014
By carefully distinguishing falling back to the default arm from moving
on to the next pattern, this patch adjusts the codegen logic for range
and guarded arms of pattern matching expression. It is a more
appropriate way of fixing rust-lang#12582 and rust-lang#13027 without causing regressions
such as rust-lang#13867.

Closes rust-lang#13867
bors added a commit that referenced this issue May 6, 2014
By carefully distinguishing falling back to the default arm from moving
on to the next pattern, this patch adjusts the codegen logic for range
and guarded arms of pattern matching expression. It is a more
appropriate way of fixing #12582 and #13027 without causing regressions
such as #13867.
    
Closes #13867
lnicola pushed a commit to lnicola/rust that referenced this issue Aug 23, 2022
…iling-empty-macro, r=jonas-schievink

fix: Fix incorrect type mismatch with `cfg_if!` and other macros in expression position

Fixes rust-lang/rust-analyzer#12940

This is a bit of a hack, ideally `MacroStmts` would not exist at all after HIR lowering, but that requires changing how the lowering code works.
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 a pull request may close this issue.

4 participants