Skip to content

Commit 11f2dd9

Browse files
authored
Fix variables assigned inside && conditions with method calls incorrectly got Nil added to their type (#16512)
Variables assigned on the right-hand side of a `&&` operator were missing `TruthyFilters`. Because they were not present within `@type_filters`, `Nil` was being added to its type since the compiler thought they actually were nilable.
1 parent a06a5ea commit 11f2dd9

File tree

3 files changed

+58
-1
lines changed

3 files changed

+58
-1
lines changed

spec/compiler/semantic/if_spec.cr

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,47 @@ describe "Semantic: if" do
448448
CRYSTAL
449449
end
450450

451+
it "doesn't add Nil to var assigned in && condition with method call (#15739)" do
452+
assert_type(<<-CRYSTAL, inject_primitives: true) { int32 }
453+
def foo
454+
if true && (x = 1) == 1
455+
x
456+
else
457+
0
458+
end
459+
end
460+
foo
461+
CRYSTAL
462+
end
463+
464+
it "types var assigned in && condition correctly when used after (#15739)" do
465+
assert_type(<<-CRYSTAL, inject_primitives: true) { int32 }
466+
def bar : Int32
467+
1
468+
end
469+
470+
def foo
471+
if true && (x = bar) > 0
472+
x
473+
else
474+
0
475+
end
476+
end
477+
foo
478+
CRYSTAL
479+
end
480+
481+
it "allows chained comparisons of 4+ expressions (#16361)" do
482+
assert_type(<<-CRYSTAL, inject_primitives: true) { bool }
483+
def a; 1; end
484+
def b; 2; end
485+
def c; 3; end
486+
def d; 4; end
487+
488+
a < b < c < d
489+
CRYSTAL
490+
end
491+
451492
it "includes pointer types in falsey branch" do
452493
assert_type(<<-CRYSTAL) { nilable union_of bool, pointer_of(int32), int32 }
453494
def foo

src/compiler/crystal/semantic/filters.cr

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,14 @@ module Crystal
287287
new node, TruthyFilter.instance
288288
end
289289

290+
def self.truthy_var(name : String)
291+
new_filters = new
292+
filter = TruthyFilter.instance
293+
new_filters.pos[name] = filter
294+
new_filters.neg[name] = filter.not
295+
new_filters
296+
end
297+
290298
def self.and(filters1, filters2)
291299
return nil if filters1.nil? && filters2.nil?
292300

src/compiler/crystal/semantic/main_visitor.cr

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2017,7 +2017,15 @@ module Crystal
20172017
# `a && b` is expanded to `a ? b : a`
20182018
# We don't use `else_type_filters` because if `a` is a temp var
20192019
# assignment then `cond_type_filters` would contain more information
2020-
@type_filters = TypeFilters.and(cond_type_filters, then_type_filters)
2020+
and_filters = TypeFilters.and(cond_type_filters, then_type_filters)
2021+
# For variables assigned in the then branch but not in the condition,
2022+
# add truthy filters so they're properly narrowed when the && result
2023+
# is used as a condition (#15739)
2024+
then_vars.each do |name, _|
2025+
next if cond_vars.has_key?(name)
2026+
and_filters = TypeFilters.and(and_filters, TypeFilters.truthy_var(name))
2027+
end
2028+
@type_filters = and_filters
20212029
when .or?
20222030
# `a || b` is expanded to `a ? a : b`
20232031
@type_filters = TypeFilters.or(cond_type_filters, else_type_filters)

0 commit comments

Comments
 (0)