-
Notifications
You must be signed in to change notification settings - Fork 179
Fix issues about block expression evaluation #364
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
Conversation
Nice work, I would suggest that you run the 3 xfail tests manually. They might be failing only because an error message might be updated/changed as part of this PR so you might find it is all working as you expect. I would also suggest that you add any test cases you can from the relevant issue: #317 |
Also I would like to hear any opinions you have on the code you had to work with on this change. |
@philberty Thanks for your review.
Yes, the failed tests did fail as expected. After I perform more code testing and (probably) discover and fix some bugs, I'll fix the existing tests and add new tests. (But I have not noticed the relevant issue before and I will take a closer look at it soon.)
I've explained the NeverType in the above comments. Here I'll explain the termination check. Since NeverType is not a real type (it contains no data and just means that it is not reachable), the termination check at GENERIC compiling is used to make sure that it won't mess up the generated GENERIC trees. It will exit as soon as it process one of the The termination check at HIR lowering is reinforced in this PR, but it is still not wonderful. It tries its best to remove all unreachable statements, instead of expressions. It matters because something like |
There was one more thing I forgot to mention yesterday. Removing unreachable statements at HIR lowering will make gccrs and rustc behaves differently. But I'm not sure whether or not it is a problem that we need to address. For example, this code fails to compile with rustc. fn foo() -> i32 {
return 1;
let tmp: f32 = 0.0;
tmp // expected `i32`, found `f32`
} But at HIR lowering, gccrs will remove the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding more test cases here it helps with the review.
Let's make that a new issue I did this thinking it would make it easier for the dead code caes but we can change that. In a seperate PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: I don't know anything about the GCC(rs) compiler internals, so take my suggestions about the code with a grain of salt.
gcc/rust/typecheck/rust-tyty-rules.h
Outdated
|
||
virtual void visit (ADTType &type) override { resolved = type.clone (); } | ||
|
||
virtual void visit (InferType &type) override { resolved = type.clone (); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This case isn't specified in (stable) Rust yet. If InferType
can't be inferred to anything, it will fall back to the ()
(unit) type when unified with the NeverType
. This is unstable in Rust and may change to be inferred to the !
type in the future.
I don't think you have to change anything here yet, but I would add a FIXME
here, since this will probably cause problems down the road. That is, because if you don't unify it with anything here, it will error after type inference iff no type could be inferred otherwise.[¹]
Since this is unstable Rust, I don't think you have to address this yet, but I would add a note. Some more explanation about this: https://hackmd.io/@FohtAO04T92wF-8_TVATYQ/SJ0vcjyWL?type=view
[¹]: So either you will have to deal with this here in some way (which I don't think you can do?) or mark it here somehow, that it might be the !
type and implement a fallback after type inference (or however this is down with integer types).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your review first. I agree with you and I won't deal with this (at least in this PR). Actually when implementing NeverType, I tested what you said with rustc and its behavior really confuses me. I will add a FIXME
later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the confusion comes mainly from !
getting inferred to ()
currently. But yeah, the never type is just confusing by nature I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exact. I wonder why !
becomes ()
sometimes. Thanks for the article you provided, which answers the question.
@@ -5,6 +5,6 @@ fn main() { | |||
let _fib = { | |||
continue; // { dg-error "cannot `continue` outside of a loop" } | |||
// { dg-error "failed to type resolve expression" "" { target { *-*-* } } .-1 } | |||
123 | |||
123 // { dg-warning "unreachable expression" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This warning appearing here is wrong-ish. It is true in the sense that it can't be reached, because the program doesn't compile. From a UX perspective this produces unnecessary clutter though. This shouldn't block the PR from going through.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you are right. This code cannot compile with rustc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I won't deal with this (at least in this PR). I've explained the reason below, see #364 (comment). (Note removing unreachable statements/expressions happens eariler than type check.)
return 1; | ||
// { dg-warning "unreachable expression" "" { target *-*-* } .+1 } | ||
true | ||
return true; // { dg-error "expected .i32. got .bool." } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep the previous test and add this modification as a new test case. This tests a different thing. Before it checked that true
is unreachable (warning, but compile-pass) AND has the wrong type (hard error, compile fail), where the first warning should not prevent detecting the second error.
With this change it removes the dependency of those errors.
Before: "This test fails even though the expression is unreachable"
Now: "This test fails because it returns the wrong type and emits a warning"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you. But I will justify my changes. Before this PR, all unreachable statements are removed but unreachable trailing expressions are kept. So here is a type-mismatched error. But it is not reasonable at all since the unreachable expression may refer removed variables:
fn foo() -> i32 {
return 1;
let tmp: f32 = 0.0; // removed because it is unreachable
tmp // gccrs cannot find tmp since it is removed
}
So in this PR, unreachable statements AND trailing expressions are removed. Thus the type-mismatched error disappears and the test is modified accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not saying that your changes are wrong. I'm just saying that you should keep the old test and that it should still fail the compilation. It doesn't really matter what/how many errors/warnings are emitted as long as it still fails.
Generally removing regression tests isn't something you should do. This practically removes one test and replaces it with another. I would just add the new test and keep the old test (with the dg
comments adjusted).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I understand now. But maybe the key problem is that the original test won't fail anymore, because the trailing expression is removed.
In my opinion, the type check against unreachable statements and expressions is totally broken and unreasonable before this PR. It skips all unreachable statements and checks the unreachable expression. The original test is just an accident so we have got the right error message. (You can see the bad case in the example above.)
As far as I'm concerned, I would rather make the type check skip all unreachable statements AND the trailing expressions temporarily (and then the original test does compile). How do you think about it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the type check against unreachable statements and expressions is totally broken and unreasonable before this PR
AFAICT it complies with the Rust reference and rustc
. You can't skip unreachable code in the semantic analysis as long as the unreachable
analysis only produces a warning and not a hard error.
As far as I'm concerned, I would rather make the type check skip all unreachable statements AND the trailing expressions temporarily (and then the original test does compile). How do you think about it?
This is against the Rust reference though, so it breaks the compiler. This test must not compile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, that would be really awkward as some unreachable code would build fine until one day it's not unreachable anymore and causes type errors without having touched it...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original implementation is also against the Rust reference. After all it skips all unreachable statements. So maybe I should either do not change it at all, or make it completely right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, that would be really awkward as some unreachable code would build fine until one day it's not unreachable anymore and causes type errors without having touched it...
Ah yes, the python way 🐍
So maybe I should either do not change it at all, or make it completely right?
No, but you shouldn't break existing regression tests, aka introducing new bugs (that were fixed before hence the regression test).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. I'll revert the change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This issue should be fixed completely. It is actually very easy to fix (sorry for this since I didn't think it carefully before and I thought it should be a separate issue).
gcc/rust/typecheck/rust-tyty-rules.h
Outdated
virtual void visit (NeverType &type) override | ||
{ | ||
resolved = get_base ()->clone (); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And what's more, assigning
<integer>
to!
is illegal and assigning!
to<integer>
is legal. At present, it seems that we do not distinguish betweenA->unify(B)
andB->unify(A)
.
Wouldn't emitting an error here solve this?
If A=!
and B=?
, then this is the direction B->unify(A)
where you have a type B
and try to make it work with the !
type, aka assigning B to A (?). This is only allowed if B==A==!
, which is covered by the NeverRules
.
On the other hand, the A->unify(B)
is handled in the NeverRules
, which means that you try to make A
work with B
, aka assigning A to B, which is always allowed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it helps. My opinion is that we must distinguish unify
and assign
explicitly. For example, we do need unify !
with <integer>
as well as unifying <integer>
with !
type. Consider the following two code segments.
let a = if x > y {
return -1; // ! type
} else {
1 // <integer> type
}; // `!`->unify(`<integer>`);
let a = if x > y {
1 // <integer> type
} else {
return -1; // ! type
}; // `<integer>`->unify(`!`);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah, I tried to think about a situation where the other direction also works. Should have thought about it longer 😄 Nvm then. This has to be addressed, but I would say this is too big for this PR. I suggest to add a FIXME
here as well. Maybe also add a XPASS
test that does let a: ! = 123;
(not sure if GCC has xpass tests though).
EDIT:
Yes, but actually this problem does not exist now. It is because ! has not been integrated and code like
let a: !
orfn foo() -> ! { /* ... */ }
simply does not compile.
If it doesn't compile anyway, just add a xfail test for it and write a comment in the test that it should actually fail for a different reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I totally agree with this.
else if (expr.is_tail_reachable ()) | ||
infered = new TyTy::TupleType (expr.get_mappings ().get_hirid ()); | ||
else | ||
infered = new TyTy::NeverType (expr.get_mappings ().get_hirid ()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we saying the end of every block is a NeverType now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It says if the block's tail is unreachable (for example, there is a return
/continue
/break
), its type is !
, otherwise its type is ()
. I don't think there is a problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is abusing the !
type while assuming that the type analysis works in a specific way. This is too much technical debt IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, rustc can compile this. I don't think it is abusing the !
type?
fn foo() -> ! {
loop {};
let mut a = 1;
a = a + a;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but only because the types unify successfully in the unreachable code. If the unreachable code produces an error the compilation will fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is nothing to do with this PR. After all, the problem already exists before this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move forward by splitting this PR up?
The first commit is about tracking the semi-colon like rustc in the AST. That is a single simpler PR to merge.
That work could be built upon to remove the unused code termination stuff.
Then that work will fix #317
Then that leaves the NeverType open
I'll consider this. At least I didn't realize there would be so many problems about the type system when introducing the NeverType, especially when the goal of the PR was not fully integrating the NeverType. ( |
The This should be done in a separate PR and not in conjunction with a bug fix of an unrelated issue. If the bug fix should depend on the |
But the original purpose of the PR is simply making the following code work. fn foo(c: i32) -> i32 {
let b = if c > 0 {
return 1;
} else {
c
};
b
} It should be a basic support and shouldn't require us to figure out the complicated |
Yes exactly. So this PR should not implement the |
OK, I got it. Then there is a question, which type should Indeed when I started to write the code, I just thought that it was necessary to add a new type that could represent the tail of block is unreachable. And therefore we can make our code cleaner (otherwise we have to check whether or not the tail of each block is reachable when checking the type of How about keeping the first three commits in this PR (thus adding a few changes, we can make the above example work properly) and making everything related to NeverType a separate PR? The termination check is still necessary in this PR. Do you think it is OK? EDIT: But it seems to leave the unreachable statements' type checking issue open. I am not clear whether or not it is acceptable. If it is, maybe I can deal with it in a new PR. If it is not, I'll split only the first commit into another PR and keep the last three commits here. |
This comment has been minimized.
This comment has been minimized.
I checked how rustc deals with NeverType. It is roughly like this: In the rustc's function So there is no problems in assignment expressions. As for Is rustc's way better and should we do similar things? |
It is not trivial. Consider the following code: fn foo(x: i32) -> i32 {
if x >= 0 {
return x;
} else {
return -x;
}
} Here the Then I think there are only two available options:
Now it looks like we have to make much more changes to the code if we choose the second option. I wonder if we can adopt the NeverType but treat it as an internal type (at least for now). In the last commit, I revert support for NeverType partially. Now only fairly limited support of NeverType is introduced. How do you think about it? @philberty @flip1995 And thanks for your review again! |
That test case currently works in master and there are plenty of test cases for it. The way it should work is is that the return expressions are of type and these unify with each block when its this type of if expression. This means the entire IfExpressions if of type integer. The awkward part is when we have an expected failure test case such as: fn foo(x: i32) -> i32 {
if x >= 0 {
x
} else {
-x
}
123
} GCCRs and rustc both identify that the if expression result in an i32 and should be of type (). The awkward part of this is Rustc allows for: fn foo(x: i32) -> i32 {
if x >= 0 {
return x;
} else {
return -x;
}
123
} GCCRS works the same as rustc but only because the final expression is marked as unreachable. This is where the internal never type comes in to fix it. As the PR has been updated you have fixed the unify rules and can_eq etc which looks good. I havent finished this 2nd review but something I was thinking though was the the notion of the fallback type similar as we have in Inference variables. So when we encounter a ReturnExpr without any expression we create a NeverType(hir_id) but when we do get an expression we can add in a HIRID TyVar reference for that such that we end up with:
That means we get everything we need. |
block_tyty = resolved; | ||
} | ||
else if (!resolved->is_unit ()) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need this check for unit type for the case of:
fn test() -> i32 {
if (...) { 123} else { 456 }
789
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now it is checked in
gccrs/gcc/rust/typecheck/rust-hir-type-check-stmt.h
Lines 46 to 50 in 3c251b9
if (stmt.is_unit_check_needed () && infered->get_kind () != TyTy::NEVER) | |
{ | |
auto unit = new TyTy::TupleType (stmt.get_mappings ().get_hirid ()); | |
infered = unit->unify (infered); | |
} |
since the following code is correct.
fn test() {
123;
}
@@ -45,7 +47,11 @@ class CompileExpr : public HIRCompileBase | |||
HIR::Expr *tuple_expr = expr.get_tuple_expr ().get (); | |||
TupleIndex index = expr.get_tuple_index (); | |||
|
|||
Bexpression *receiver_ref = CompileExpr::Compile (tuple_expr, ctx); | |||
Bexpression *receiver_ref | |||
= CompileExpr::Compile (tuple_expr, ctx, &terminated); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the unreachable code termination in GENERIC generation, GCC should be able to optimize this. It is worth while doing but it's not the right time to be adding this to the GENERIC backend yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason why I add it is that I'm afraid that the following code will mess up the GENERIC tree.
fn main() {
let a: (i32, i32, i32) = if true {
return;
// Here, we'll assign `()` to `(i32, i32, i32)`.
} else {
(1, 2, 3)
};
}
Are you sure there is no problems at all? If there are no problems, I'll remove the unreachable code termination in GENERIC generation. (Maybe I'll have a try later.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shouldn't cause issues you can do it in C/C++ FE and they directly create GENERIC tress. Even if it did cause issues it shouldn't be in this PR since this is an optimization outside of the scope of what this PR is about and it is something we should discuss/design first on zulip or a github iussue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it is OK now since only fairly limited part of NeverType is introduced. There is a different case when I was writing this code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it is OK now since only fairly limited part of NeverType is introduced. There is a different case when I was writing this code.
@@ -82,7 +82,8 @@ class TypeCheckItem : public TypeCheckBase | |||
|
|||
context->pop_return_type (); | |||
|
|||
expected_ret_tyty->unify (block_expr_ty); | |||
if (block_expr_ty->get_kind () != TyTy::NEVER) | |||
expected_ret_tyty->unify (block_expr_ty); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't afford to lose those checks against the expected return type. This is where i think the NeverType should use a fallback mechanism.
never_type->get_fallback()->unify(block_expr_ty)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it has been checked, in
infered = fn_return_tyty->unify (expr_ty); |
Hasn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I noticed if the expression is empty, it won't be checked.
gccrs/gcc/rust/typecheck/rust-hir-type-check-expr.h
Lines 148 to 152 in 3c251b9
if (!expr.has_return_expr ()) | |
{ | |
infered = new TyTy::NeverType (expr.get_mappings ().get_hirid ()); | |
return; | |
} |
But we can still fix it in that place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats only if its a return expression. What about the case where there is an empty block such as:
fn test() -> i32 {
}
Here the block will resolve to NeverType in this PR and not be checked against i32 which means we lose the existing check that will sa expected i32 got ().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I noticed if the expression is empty, it won't be checked.
gccrs/gcc/rust/typecheck/rust-hir-type-check-expr.h
Lines 148 to 152 in 3c251b9
if (!expr.has_return_expr ()) { infered = new TyTy::NeverType (expr.get_mappings ().get_hirid ()); return; } But we can still fix it in that place?
I don't think we can generally assume that if the expression resolves to Never it must be checked against the return type.
What about nested if-expressions where there is a missing empty block, since nested if statement need to be dependant on the context of their parent block not nessecarily the return type of a function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here the block will resolve to NeverType in this PR and not be checked against i32 which means we lose the existing check that will sa expected i32 got ().
It will resolve to ()
, since the tail is reachable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about nested if-expressions where there is a missing empty block, since nested if statement need to be dependant on the context of their parent block not nessecarily the return type of a function.
If there is a missing empty block, it means the tail is reachable so we won't get a NeverType.
It shouldn't. Besides what you have said, it also means the following code won't work. fn foo() -> f32 {
let a = if true {
return 3.0; // `<float>`
} else {
3 // `<integer>`
};
0.0
} And what's more, the semicolon after the return expression means it is a statement. I don't think it should affect the block expression's type in such a way as you described. In the following code, fn foo(x: i32) -> i32 {
if x >= 0 {
return x;
let a = 1;
} else {
return -x;
let b = -1;
}
} |
Yes, Let a = block_expr is a different case as to what i mentioned. This PR removes the unit type check for block-expr type resolution: fn foo(x: i32) -> i32 {
if x >= 0 {
x
} else {
-x
}
123
} The if the expression is resolving to integer but it should be (). |
No, I've explained in #364 (comment). |
Can we close this PR. The current state of the commit history is not mergeable as one big squash. This PR introduces many distinct changes which make it too difficult to review and agree on the behaviour:
There are already alot of comments mentioning that we wish this PR to be split up several times. |
OK, I'll try to split this PR into several ones. Sorry for that. |
380: Fix issues about block expression evaluation r=philberty a=lrh2000 I've tried my best to split #364. Its core part is here. All unrelated fixes should have been removed and I will make them separate PRs, but this PR still has to contain 2 commits and introduce limited support for the never type. https://github.com/Rust-GCC/gccrs/blob/935aff52a66a70e1e96f0a6aa7e0ad37eda3f93c/gcc/testsuite/rust.test/compile/deadcode1.rs#L7-L15 https://github.com/Rust-GCC/gccrs/blob/935aff52a66a70e1e96f0a6aa7e0ad37eda3f93c/gcc/testsuite/rust.test/compile/implicit_returns1.rs#L44-L50 The reason is that the above two tests failed to pass simultaneously after the first commit. The first test requires the `if/else` expression resolves to `()` but the second test requires the `if/else` expression resolves to `<integer>`. They are conflicted. I wonder whether or not this PR is OK, and anyway thanks for your review. Co-authored-by: lrh2000 <[email protected]>
387: Don't remove unreachable statements at HIR lowering r=philberty a=lrh2000 This is a legal code segment: ```rust fn foo() -> i32 { return 1; let a = 1; a } ``` And this is illegal: ```rust fn foo() -> i32 { return 1; let a = 1.5; a } ``` So we shouldn't remove unreachable statements at HIR lowering. We can issue warnings for them, but they need to be kept so that their types can be checked. *(Original in #364 Co-authored-by: lrh2000 <[email protected]>
390: Clean up the compilation of block expressions r=philberty a=lrh2000 The original implementation of compiling block expressions and function bodies is incorrect. Here is an example. ```rust fn foo() -> i32 { 1; 2; 0 } ``` The above Rust code will compile into the below GIMPLE code. ```c i32 foo () { i32 D.199; D.199 = 1; return D.199; D.199 = 2; return D.199; D.199 = 0; return D.199; } ``` It is obviously incorrect. *(Original in #364 Co-authored-by: lrh2000 <[email protected]>
This PR introduces the
!
type (named "never", see https://doc.rust-lang.org/std/primitive.never.html), so that the following code works.Note the code also works even without the PR, however, it is an accident because
return 1
resolves to<integer>
before this PR, which is not reasonable.This PR is roughly complete:
!
type (named "never")TODOs (I'll open separate issues or PRs for them):
!
type (example code:fn foo() -> ! { /* ...*/}
)!
)func() || return -1;
andfunc() && { bar(); false };
)