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
Extend type filtering of conditional clauses to arbitrary logical connectives #10147
Extend type filtering of conditional clauses to arbitrary logical connectives #10147
Conversation
About this test (and the one immediately below): it "doesn't restrict || else in sub && (right)" do
assert_type(%(
def foo
a = 1 || nil
if false || (!a && false)
return 1
end
a
end
foo
)) { nilable int32 }
end The negation of the condition is |
This is great!! And the code ends up being even simpler than before. I don't have time right now to review this, but I will maybe in two weeks or a bit more. |
Co-authored-by: Sijawusz Pur Rahnama <sija@sija.pl>
keys.concat(filters2.pos.keys) | ||
keys.concat(filters2.neg.keys) | ||
end | ||
keys.uniq! |
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 wonder if using a Set
here for keys is more performant. In any case we can optimize this later on.
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 expect the size to be typically rather small so uniq!
's small size optimization applies.
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 totally forgot about that optimization!
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 a game changer. Thank you so much! ❤️ ❤️ ❤️
Fixes #8864. This PR makes the following cases work:
The fix comes in 3 parts:
TypeFilters
now always keeps track of the positive and the negative constraints that can be applied to variables, because the negative constraint may provide information to further filter compositions that are unavailable in the positive constraint, or vice versa. (PreviouslyMainVisitor
was hardcoded to handle one layer of||
expressions.) Consider the conditionx.is_a?(T) || f
, wherex
is a variable andf
is a call; nothing can be said about the type ofx
in the then-branch, but we know that!x.is_a?(T)
in the else-branch, so we can rely on this fact if we add a||
,&&
, or!
to that condition. Negating aTypeFilters
is as simple as swapping its positive and negative constraints, whereas for conjunction and disjunction the two constraints are combined separately using de Morgan's laws.TypeFilters
are complementary to each other, but there is one exception: if the condition is of the formx = f
, the truthiness ofx
andf
must be the same, i.e.!!x == !!f
. Currently, Crystal assumes the then-branch's constraint is!!x && !!f
, so the else-branch's becomes!x || !f
; but for assignments inside the condition, we can strengthen the latter constraint to!x && !f
. It doesn't matter ifx
is a temporary variable; this PR makes no distinction of temporary variables generated by the literal expander.x || y || z
wherex
is a variable, Crystal expands it to(__temp_1 = (x ? x : y)) ? __temp_1 : z
. Before this PR, the resulting positive constraint would be(!!x || !!y) || !!__temp_1 || !!z
, where the presence of__temp_1
, coming from the expanded then-branch, means thatx
,y
, andz
can all be falsey based on this constraint alone. This PR eliminates the then-branch in the filter composition, because for||
it is always equivalent to the condition, but less elaborated. (A similar argument can be made about the else-branch generated from&&
expressions.)This patch also refactors@type_filters
so that it is non-nilable (an empty constraint is handled the same asnil
and we are already resetting this ivar frequently enough).