-
Notifications
You must be signed in to change notification settings - Fork 34
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
throw and return statements should affect flow sensitive typing #891
Comments
I like this, but it might be undecidable. What do you think, @RossTate? |
Might be linked with #536 |
It's pretty easy to decide. At any branch point in the CFG you refine the types of variables as appropriate given the branch condition. At any merge point in the CFG, you join the types of variables. If there's a problem, it's because of loops, but I think for this application we'd be fine. Note that I'm saying "in the CFG" rather than in the syntax, which addresses all the Ceylon issues I've seen around on this topic. |
It's recently come to my attention that there is a whole class of people who vehemently prefer to write: void fun(String? str, Integer int) {
if (!exists str) { return; }
if (str.empty) { return; }
if (int<0) { return; }
//do the real work here
} Instead of, for example: void fun(String? str, Integer int) {
if (exists str, !str.empty) {
if (int>=0) {
//do the real work here
}
}
} There's even a whole name for former style: "guards". I almost never write code like this and in fact I actively dislike it. Nevertheless, I don't think it's a good thing that Ceylon actually prevents you from using this style. So we should make stuff like |
Yahoo!!!! :) |
I emphatically vote Yes! :) |
So even though this looks not much more difficult than #74, sadly, it's actually significantly harder. It would require doing control flow analysis and type assignment at once in the same visitor, essentially merging |
Why does control flow depend on type assignment? |
@FroMage: I tried swapping the phases in the typechecker, and it seems the following cause problems: Anything returnsDefinitely1() {
while (true) {
return "";
}
}
Anything returnsDefinitely2() {
assert (false);
} (possibly amongst other things) |
Because control flow analysis looks at things like |
But that sounds like a very special case. Perhaps the part of flow-control that deals with special cases like this can be split into a later phase? Or are these affecting flow-sensitive typing? |
No, it has to all be done at once. For example if I have:
The only way I can tell that this function definitely returns is by knowing the type of |
Yes, of course, but what I meant to ask is: do we need to know if something definitely returns for flow typing? Or just a subset of flow visitor? |
Well, yes, we do, that's the whole point of this issue! |
|
Well, no, the point of the issue is that we handle returns, like: void fun(String? str){
if(is Null str){ return; }
print(str);
} The case of String fun(String? str) {
assert (!is Null str);
return str;
} I don't care that |
I don’t think it needs the type of shared Anything f() {
value myTrue = true;
if (myTrue) {
return nothing;
}
// error: does not definitely return
} It seems to depend on the identity of the
Perhaps this will change with #865, when we can denote the types |
@lucaswerkmeister resolving references and assigning types is something that has to happen at the same time as part of one phase. We can't resolve references and then later assign types to things. |
But we wouldn’t be resolving arbitrary references in that phase – we would just track two names and check if they’re shadowed by anything else (import alias, other import, toplevel value in package, value in surrounding scope). We don’t need to know any types, because only the original Or does flow control need more type information than “always satisfied” and “never satisfied”? |
I also have to take into account inherited members. It's nowhere near as simple as you're making out. |
Ohh, the superclass can also have a member |
Given how difficult this would be to implement, we should consider an alternative, which would be to add a block to Hell, I'm not even sure if the assert (nonempty list) { return null; } assert (is Foo arg) { throw IllegalTypeException(); } assert (!name.empty, length>0) { return ""; } Or, if you guys think it's necessary for readability, we could require an assert (nonempty list) else { return null; } assert (is Foo arg) else { throw IllegalTypeException(); } assert (!name.empty, length>0) else { return ""; } This is much, much easier to implement, and doesn't actually seem worse to me. |
I think the |
Well I'm certainly not against it because https://groups.google.com/d/msg/ceylon-users/AbcLpWNrlCY/9Aa7XLXhPcQJ 😄 |
I agree with @lucaswerkmeister's comment. It may also make sense to allow |
FWIW, I vote for the else version. |
@jvasileff you have a condition and a true-block and a false-block, to me that's the definition of an @sgalles well, first I don't find that it makes code "super clear", but I'll admit it's useful. But an assert is an assert and an if is an if, one is used to throw an exception for the highly unanticipated situation where the condition isn't true while an if is used to make choices. The moment you give an assert an else-block and use it to make decisions it's become an if and you should be using an if in my opinion.
Well you can say that to us, but you just can't prevent the rest of the world from doing that, it's the closest thing Ceylon permits to what is an "early-exit" code style. |
I have reimplemented this in a slightly less-crappy way. |
The second `if` no longer narrows the type, so use an else instead.
Blocks may now include synthetic `GuardedVariable`s, which should be ignored.
So this works for what sort of conditions? Due to the semantics of if (!exists int) {
return 0;
}
return int+int; In fact, ATM with my implementation it's evaluated twice instead of the once that would be possible. I assume that's not a problem? |
|
Right. Exactly. @tombentley totally misunderstood the point I was making about what was wrong with the previous implementation. It does not matter if you re-evaluate the variable. It does matter if you re-evaluate the condition. Which was a problem with the previous implementation. |
OK thanks. |
Mmm, does that also work with destructuring? |
@FroMage no, I don't think so. |
OK. |
Just keep in mind that I won't implement anything for what you say is not supported ;) |
Done. |
Sorry I meant to close the backend issue ;) |
Well, it's done anyway, and moved to #1434. |
Shouldn’t we have a tracking issue for making this work even with |
Fallout from ceylon/ceylon-spec#891 that only occurs in the IDE (since the CLI doesn’t run a typechecker).
throw
andreturn
statements should affect flow sensitive typing :Same behavior expected if
return
is used instead ofthrow
The text was updated successfully, but these errors were encountered: