rigortype 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -7
  3. data/data/builtins/ruby_core/array.yml +1470 -0
  4. data/data/builtins/ruby_core/file.yml +501 -0
  5. data/data/builtins/ruby_core/hash.yml +936 -0
  6. data/data/builtins/ruby_core/io.yml +1594 -0
  7. data/data/builtins/ruby_core/numeric.yml +1809 -0
  8. data/data/builtins/ruby_core/range.yml +389 -0
  9. data/data/builtins/ruby_core/set.yml +594 -0
  10. data/data/builtins/ruby_core/string.yml +1850 -0
  11. data/data/builtins/ruby_core/time.yml +750 -0
  12. data/lib/rigor/analysis/check_rules.rb +97 -4
  13. data/lib/rigor/analysis/runner.rb +4 -0
  14. data/lib/rigor/builtins/imported_refinements.rb +251 -0
  15. data/lib/rigor/configuration.rb +6 -1
  16. data/lib/rigor/inference/acceptance.rb +324 -6
  17. data/lib/rigor/inference/builtins/array_catalog.rb +46 -0
  18. data/lib/rigor/inference/builtins/hash_catalog.rb +40 -0
  19. data/lib/rigor/inference/builtins/method_catalog.rb +90 -0
  20. data/lib/rigor/inference/builtins/numeric_catalog.rb +93 -0
  21. data/lib/rigor/inference/builtins/range_catalog.rb +46 -0
  22. data/lib/rigor/inference/builtins/set_catalog.rb +54 -0
  23. data/lib/rigor/inference/builtins/string_catalog.rb +39 -0
  24. data/lib/rigor/inference/builtins/time_catalog.rb +64 -0
  25. data/lib/rigor/inference/expression_typer.rb +48 -1
  26. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +670 -16
  27. data/lib/rigor/inference/method_dispatcher/file_folding.rb +144 -0
  28. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +215 -0
  29. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +23 -7
  30. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -0
  31. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +240 -4
  32. data/lib/rigor/inference/method_dispatcher.rb +28 -21
  33. data/lib/rigor/inference/method_parameter_binder.rb +29 -4
  34. data/lib/rigor/inference/narrowing.rb +376 -4
  35. data/lib/rigor/inference/scope_indexer.rb +10 -2
  36. data/lib/rigor/inference/statement_evaluator.rb +213 -2
  37. data/lib/rigor/rbs_extended.rb +230 -15
  38. data/lib/rigor/scope.rb +14 -0
  39. data/lib/rigor/type/combinator.rb +159 -1
  40. data/lib/rigor/type/difference.rb +155 -0
  41. data/lib/rigor/type/integer_range.rb +137 -0
  42. data/lib/rigor/type/intersection.rb +135 -0
  43. data/lib/rigor/type/refined.rb +174 -0
  44. data/lib/rigor/type.rb +4 -0
  45. data/lib/rigor/version.rb +1 -1
  46. data/sig/rigor/rbs_extended.rbs +14 -0
  47. data/sig/rigor/scope.rbs +1 -0
  48. data/sig/rigor/type.rbs +91 -1
  49. metadata +25 -1
@@ -155,6 +155,61 @@ module Rigor
155
155
  end
156
156
  end
157
157
 
158
+ # Integer-comparison fragment of `type` against an Integer
159
+ # literal `bound`. Narrows the receiver of `x < n`, `x <= n`,
160
+ # `x > n`, `x >= n` (and the reversed forms) to the subset of
161
+ # the existing domain that satisfies the comparison. Hooks in:
162
+ # - `Constant<Integer>` is preserved when it satisfies the
163
+ # comparison, otherwise collapsed to `Bot`.
164
+ # - `IntegerRange[a..b]` becomes the intersection with the
165
+ # half-line implied by the comparison; an empty intersection
166
+ # collapses to `Bot`, a single-point intersection collapses
167
+ # to `Constant<Integer>`.
168
+ # - `Nominal[Integer]` becomes the half-line itself (e.g.
169
+ # `x > 0` on `Nominal[Integer]` is `positive_int`).
170
+ # - `Union` narrows each member independently.
171
+ # - Other carriers (Float, String, Top, Dynamic) flow through
172
+ # unchanged: the analyzer does not have a Float-range carrier
173
+ # today, and no other carrier participates in numeric ordering.
174
+ def narrow_integer_comparison(type, comparator, bound)
175
+ return type unless bound.is_a?(Integer) && %i[< <= > >=].include?(comparator)
176
+
177
+ narrow_integer_comparison_dispatch(type, comparator, bound)
178
+ end
179
+
180
+ # Equality fragment of `type` against an Integer `value`.
181
+ # `Constant<Integer>` is preserved when it equals `value`,
182
+ # otherwise collapses to `Bot`. `IntegerRange` covers? `value`
183
+ # narrows to `Constant[value]`; an out-of-range comparison
184
+ # collapses to `Bot`. `Nominal[Integer]` narrows to
185
+ # `Constant[value]`. `Union` narrows each member.
186
+ def narrow_integer_equal(type, value)
187
+ return type unless value.is_a?(Integer)
188
+
189
+ narrow_integer_equal_dispatch(type, value)
190
+ end
191
+
192
+ # Complement of {.narrow_integer_equal}. Removes a single
193
+ # integer value from the domain when one endpoint of an
194
+ # `IntegerRange` is exactly that value (so the result stays a
195
+ # contiguous range). Domains where the value sits strictly
196
+ # between the endpoints stay unchanged: punching a hole would
197
+ # require a two-piece carrier the lattice does not yet model.
198
+ def narrow_integer_not_equal(type, value)
199
+ return type unless value.is_a?(Integer)
200
+
201
+ case type
202
+ when Type::Constant
203
+ type.value == value ? Type::Combinator.bot : type
204
+ when Type::IntegerRange
205
+ narrow_integer_range_not_equal(type, value)
206
+ when Type::Union
207
+ Type::Combinator.union(*type.members.map { |m| narrow_integer_not_equal(m, value) })
208
+ else
209
+ type
210
+ end
211
+ end
212
+
158
213
  # Class-membership fragment of `type`: the subset whose
159
214
  # inhabitants are instances of `class_name` (or its subclasses
160
215
  # when `exact: false`). `class_name` is the qualified name of
@@ -452,7 +507,21 @@ module Rigor
452
507
  end
453
508
  end
454
509
 
510
+ ZERO_CLASS_PREDICATES = %i[positive? negative? zero? nonzero?].freeze
511
+ COMPARISON_OPERATORS = %i[< <= > >=].freeze
512
+ private_constant :ZERO_CLASS_PREDICATES, :COMPARISON_OPERATORS
513
+
455
514
  def dispatch_call(node, scope, name)
515
+ return dispatch_call_simple(node, scope, name) if simple_dispatch_name?(name)
516
+
517
+ dispatch_call_numeric(node, scope, name)
518
+ end
519
+
520
+ def simple_dispatch_name?(name)
521
+ %i[nil? ! is_a? kind_of? instance_of? == != ===].include?(name)
522
+ end
523
+
524
+ def dispatch_call_simple(node, scope, name)
456
525
  case name
457
526
  when :nil?, :! then dispatch_unary_predicate(node, scope, name)
458
527
  when :is_a?, :kind_of? then analyse_class_predicate(node, scope, exact: false)
@@ -462,6 +531,92 @@ module Rigor
462
531
  end
463
532
  end
464
533
 
534
+ def dispatch_call_numeric(node, scope, name)
535
+ if COMPARISON_OPERATORS.include?(name)
536
+ analyse_comparison_predicate(node, scope, comparator: name)
537
+ elsif ZERO_CLASS_PREDICATES.include?(name)
538
+ analyse_zero_class_predicate(node, scope, predicate: name)
539
+ elsif name == :between?
540
+ analyse_between_predicate(node, scope)
541
+ end
542
+ end
543
+
544
+ # `:positive?` / `:negative?` / `:zero?` / `:nonzero?` are
545
+ # zero-arg predicates on `Numeric`. We model them as
546
+ # comparisons against the literal 0 so the existing range
547
+ # narrowing handles them uniformly.
548
+ ZERO_CLASS_PREDICATE_RULES = {
549
+ positive?: { truthy: [:>, 0], falsey: [:<=, 0] },
550
+ negative?: { truthy: [:<, 0], falsey: [:>=, 0] },
551
+ zero?: { truthy: [:eq, 0], falsey: [:ne, 0] },
552
+ nonzero?: { truthy: [:ne, 0], falsey: [:eq, 0] }
553
+ }.freeze
554
+ private_constant :ZERO_CLASS_PREDICATE_RULES
555
+
556
+ def analyse_zero_class_predicate(node, scope, predicate:)
557
+ return nil unless argument_free?(node)
558
+ return nil unless node.receiver.is_a?(Prism::LocalVariableReadNode)
559
+
560
+ local_name = node.receiver.name
561
+ current = scope.local(local_name)
562
+ return nil if current.nil?
563
+
564
+ rules = ZERO_CLASS_PREDICATE_RULES[predicate]
565
+ truthy = apply_zero_rule(current, rules[:truthy])
566
+ falsey = apply_zero_rule(current, rules[:falsey])
567
+ [scope.with_local(local_name, truthy), scope.with_local(local_name, falsey)]
568
+ end
569
+
570
+ def apply_zero_rule(type, rule)
571
+ case rule[0]
572
+ when :eq then narrow_integer_equal(type, rule[1])
573
+ when :ne then narrow_integer_not_equal(type, rule[1])
574
+ else
575
+ narrow_integer_comparison(type, rule[0], rule[1])
576
+ end
577
+ end
578
+
579
+ # `x.between?(a, b)` truthy edge narrows to
580
+ # `narrow_integer_comparison(>=, a)` ∩
581
+ # `narrow_integer_comparison(<=, b)`. The falsey edge is left
582
+ # unchanged because the complement is a two-piece domain
583
+ # (`x < a || x > b`) that the lattice cannot express
584
+ # precisely. `a` and `b` MUST both be integer literals.
585
+ def analyse_between_predicate(node, scope)
586
+ return nil unless node.receiver.is_a?(Prism::LocalVariableReadNode)
587
+ return nil if node.arguments.nil?
588
+ return nil unless node.arguments.arguments.size == 2
589
+
590
+ low, high = node.arguments.arguments
591
+ return nil unless low.is_a?(Prism::IntegerNode) && high.is_a?(Prism::IntegerNode)
592
+
593
+ local_name = node.receiver.name
594
+ current = scope.local(local_name)
595
+ return nil if current.nil?
596
+
597
+ truthy = narrow_integer_comparison(
598
+ narrow_integer_comparison(current, :>=, low.value),
599
+ :<=, high.value
600
+ )
601
+ [scope.with_local(local_name, truthy), scope]
602
+ end
603
+
604
+ # Helper for {.narrow_integer_not_equal}. Only adjusts when the
605
+ # value sits exactly on one endpoint, so the result stays
606
+ # contiguous; otherwise the input range is preserved.
607
+ def narrow_integer_range_not_equal(range, value)
608
+ return range if range.lower > value || range.upper < value
609
+ return Type::Combinator.bot if single_point_range_equal?(range, value)
610
+ return build_narrowing_integer_range(value + 1, range.upper) if range.lower == value
611
+ return build_narrowing_integer_range(range.lower, value - 1) if range.upper == value
612
+
613
+ range
614
+ end
615
+
616
+ def single_point_range_equal?(range, value)
617
+ range.finite? && range.min == value && range.max == value
618
+ end
619
+
465
620
  def dispatch_unary_predicate(node, scope, name)
466
621
  return nil unless argument_free?(node)
467
622
 
@@ -491,6 +646,126 @@ module Rigor
491
646
  equality == :== ? [positive, negative] : [negative, positive]
492
647
  end
493
648
 
649
+ # Comparison predicate analyser. Recognised shapes:
650
+ # x < Int x <= Int x > Int x >= Int
651
+ # Int < x Int <= x Int > x Int >= x
652
+ # The reversed (literal-on-left) form is normalised by
653
+ # transposing the operator so the receiver-local always
654
+ # appears on the left of the rule.
655
+ INVERT_COMPARISON_OP = { :< => :>=, :<= => :>, :> => :<=, :>= => :< }.freeze
656
+ REVERSE_COMPARISON_OP = { :< => :>, :<= => :>=, :> => :<, :>= => :<= }.freeze
657
+ private_constant :INVERT_COMPARISON_OP, :REVERSE_COMPARISON_OP
658
+
659
+ def analyse_comparison_predicate(node, scope, comparator:)
660
+ return nil if node.arguments.nil?
661
+ return nil unless node.arguments.arguments.size == 1
662
+
663
+ match = comparison_local_literal(node.receiver, node.arguments.arguments.first, comparator)
664
+ return nil if match.nil?
665
+
666
+ local_name, normalised_op, bound = match
667
+ current = scope.local(local_name)
668
+ return nil if current.nil?
669
+
670
+ truthy = narrow_integer_comparison(current, normalised_op, bound)
671
+ falsey = narrow_integer_comparison(current, INVERT_COMPARISON_OP[normalised_op], bound)
672
+ [scope.with_local(local_name, truthy), scope.with_local(local_name, falsey)]
673
+ end
674
+
675
+ def comparison_local_literal(left, right, comparator)
676
+ if left.is_a?(Prism::LocalVariableReadNode) && right.is_a?(Prism::IntegerNode)
677
+ return [left.name, comparator, right.value]
678
+ end
679
+ return nil unless right.is_a?(Prism::LocalVariableReadNode) && left.is_a?(Prism::IntegerNode)
680
+
681
+ [right.name, REVERSE_COMPARISON_OP[comparator], left.value]
682
+ end
683
+
684
+ def narrow_integer_equal_dispatch(type, value)
685
+ case type
686
+ when Type::Constant then narrow_integer_equal_constant(type, value)
687
+ when Type::IntegerRange then narrow_integer_equal_range(type, value)
688
+ when Type::Nominal then narrow_integer_equal_nominal(type, value)
689
+ when Type::Union
690
+ Type::Combinator.union(*type.members.map { |m| narrow_integer_equal(m, value) })
691
+ else type
692
+ end
693
+ end
694
+
695
+ def narrow_integer_equal_constant(constant, value)
696
+ constant.value == value ? constant : Type::Combinator.bot
697
+ end
698
+
699
+ def narrow_integer_equal_range(range, value)
700
+ range.covers?(value) ? Type::Combinator.constant_of(value) : Type::Combinator.bot
701
+ end
702
+
703
+ def narrow_integer_equal_nominal(nominal, value)
704
+ return nominal unless nominal.class_name == "Integer" && nominal.type_args.empty?
705
+
706
+ Type::Combinator.constant_of(value)
707
+ end
708
+
709
+ def narrow_integer_comparison_dispatch(type, comparator, bound)
710
+ case type
711
+ when Type::Constant
712
+ integer_constant_satisfies?(type.value, comparator, bound) ? type : Type::Combinator.bot
713
+ when Type::IntegerRange
714
+ intersect_integer_range(type, comparator, bound)
715
+ when Type::Nominal
716
+ narrow_integer_comparison_nominal(type, comparator, bound)
717
+ when Type::Union
718
+ Type::Combinator.union(
719
+ *type.members.map { |m| narrow_integer_comparison(m, comparator, bound) }
720
+ )
721
+ else
722
+ type
723
+ end
724
+ end
725
+
726
+ def narrow_integer_comparison_nominal(nominal, comparator, bound)
727
+ return nominal unless nominal.class_name == "Integer" && nominal.type_args.empty?
728
+
729
+ intersect_integer_range(Type::Combinator.universal_int, comparator, bound)
730
+ end
731
+
732
+ def integer_constant_satisfies?(value, comparator, bound)
733
+ return false unless value.is_a?(Integer)
734
+
735
+ case comparator
736
+ when :< then value < bound
737
+ when :<= then value <= bound
738
+ when :> then value > bound
739
+ when :>= then value >= bound
740
+ end
741
+ end
742
+
743
+ def intersect_integer_range(range, comparator, bound)
744
+ new_lower, new_upper = comparison_endpoints(range, comparator, bound)
745
+ return Type::Combinator.bot if new_lower > new_upper
746
+
747
+ build_narrowing_integer_range(new_lower, new_upper)
748
+ end
749
+
750
+ def comparison_endpoints(range, comparator, bound)
751
+ case comparator
752
+ when :< then [range.lower, [range.upper, bound - 1].min]
753
+ when :<= then [range.lower, [range.upper, bound].min]
754
+ when :> then [[range.lower, bound + 1].max, range.upper]
755
+ when :>= then [[range.lower, bound].max, range.upper]
756
+ end
757
+ end
758
+
759
+ def build_narrowing_integer_range(lower, upper)
760
+ min = lower == -Float::INFINITY ? Type::IntegerRange::NEG_INFINITY : Integer(lower)
761
+ max = upper == Float::INFINITY ? Type::IntegerRange::POS_INFINITY : Integer(upper)
762
+ if min.is_a?(Integer) && max.is_a?(Integer) && min == max
763
+ Type::Combinator.constant_of(min)
764
+ else
765
+ Type::Combinator.integer_range(min, max)
766
+ end
767
+ end
768
+
494
769
  def equality_local_literal(left, right, scope)
495
770
  if left.is_a?(Prism::LocalVariableReadNode)
496
771
  literal = static_literal_type(right, scope)
@@ -754,6 +1029,8 @@ module Rigor
754
1029
  # the effect's `negative?` flag. Shared between
755
1030
  # predicate-if-* and assert-if-* application paths.
756
1031
  def narrow_for_effect(current, effect, environment)
1032
+ return effect.refinement_type if effect.respond_to?(:refinement?) && effect.refinement?
1033
+
757
1034
  if effect.negative?
758
1035
  narrow_not_class(current, effect.class_name, exact: false, environment: environment)
759
1036
  else
@@ -850,10 +1127,11 @@ module Rigor
850
1127
  fully_narrowable = true
851
1128
 
852
1129
  conditions.each do |condition|
853
- target = static_class_name(condition) || case_equality_target_class(condition)
854
- if target
855
- truthy_members << narrow_class(current, target, exact: false, environment: scope.environment)
856
- falsey_type = narrow_not_class(falsey_type, target, exact: false, environment: scope.environment)
1130
+ applied = apply_case_when_condition(scope, current, condition, falsey_type)
1131
+ if applied
1132
+ truthy_members << applied[:truthy]
1133
+ falsey_type = applied[:falsey]
1134
+ fully_narrowable &&= applied[:fully_narrowable]
857
1135
  else
858
1136
  fully_narrowable = false
859
1137
  end
@@ -866,6 +1144,100 @@ module Rigor
866
1144
  ]
867
1145
  end
868
1146
 
1147
+ # Per-condition rule. Returns `nil` when the condition shape
1148
+ # is not recognised (caller marks `fully_narrowable = false`),
1149
+ # or `{truthy:, falsey:, fully_narrowable:}` when it is.
1150
+ def apply_case_when_condition(scope, current, condition, falsey_acc)
1151
+ int_range = case_equality_integer_range(condition)
1152
+ return integer_range_when_result(current, int_range, falsey_acc) if int_range && integer_rooted_type?(current)
1153
+
1154
+ int_literal = case_equality_integer_literal(condition)
1155
+ if int_literal && integer_rooted_type?(current)
1156
+ return integer_literal_when_result(current, int_literal, falsey_acc)
1157
+ end
1158
+
1159
+ target = static_class_name(condition) || case_equality_target_class(condition)
1160
+ return class_when_result(scope, current, target, falsey_acc) if target
1161
+
1162
+ nil
1163
+ end
1164
+
1165
+ def case_equality_integer_literal(condition)
1166
+ condition = unwrap_parens(condition)
1167
+ condition.is_a?(Prism::IntegerNode) ? condition.value : nil
1168
+ end
1169
+
1170
+ def integer_literal_when_result(current, value, falsey_acc)
1171
+ # `case n when k` is `k === n` which for Integer is value
1172
+ # equality. The truthy edge collapses the local to
1173
+ # `Constant[k]`; the falsey edge tightens via
1174
+ # `narrow_integer_not_equal` (only effective when k sits
1175
+ # at one endpoint of the current range).
1176
+ {
1177
+ truthy: narrow_integer_equal(current, value),
1178
+ falsey: narrow_integer_not_equal(falsey_acc, value),
1179
+ fully_narrowable: true
1180
+ }
1181
+ end
1182
+
1183
+ def integer_range_when_result(current, range_pair, falsey_acc)
1184
+ low, high = range_pair
1185
+ truthy = narrow_integer_comparison(
1186
+ narrow_integer_comparison(current, :>=, low),
1187
+ :<=, high
1188
+ )
1189
+ # The falsey edge of `n in [a, b]` is two-piece; we cannot
1190
+ # express the complement precisely with a single carrier,
1191
+ # so keep the accumulator unchanged. `fully_narrowable: false`
1192
+ # forces the else-branch to see `current` (the unmodified
1193
+ # entry type), which mirrors `between?` falsey behaviour.
1194
+ { truthy: truthy, falsey: falsey_acc, fully_narrowable: false }
1195
+ end
1196
+
1197
+ def class_when_result(scope, current, target, falsey_acc)
1198
+ {
1199
+ truthy: narrow_class(current, target, exact: false, environment: scope.environment),
1200
+ falsey: narrow_not_class(falsey_acc, target, exact: false, environment: scope.environment),
1201
+ fully_narrowable: true
1202
+ }
1203
+ end
1204
+
1205
+ # Returns `[low, high]` for a `Prism::RangeNode` whose
1206
+ # endpoints are both `Prism::IntegerNode` literals, with
1207
+ # `..`/`...` exclusivity respected. Open-ended ranges use
1208
+ # the symbolic infinities so the existing comparison
1209
+ # narrowing tier handles them. Returns `nil` for any other
1210
+ # shape (Float endpoints, String endpoints, dynamic
1211
+ # expressions).
1212
+ def case_equality_integer_range(condition)
1213
+ condition = unwrap_parens(condition)
1214
+ return nil unless condition.is_a?(Prism::RangeNode)
1215
+
1216
+ low = integer_range_endpoint(condition.left, default: Type::IntegerRange::NEG_INFINITY)
1217
+ high = integer_range_endpoint(condition.right, default: Type::IntegerRange::POS_INFINITY)
1218
+ return nil if low.nil? || high.nil?
1219
+
1220
+ high -= 1 if condition.exclude_end? && high.is_a?(Integer)
1221
+ [low, high]
1222
+ end
1223
+
1224
+ def integer_range_endpoint(node, default:)
1225
+ return default if node.nil?
1226
+ return node.value if node.is_a?(Prism::IntegerNode)
1227
+
1228
+ nil
1229
+ end
1230
+
1231
+ def integer_rooted_type?(type)
1232
+ case type
1233
+ when Type::Constant then type.value.is_a?(Integer)
1234
+ when Type::IntegerRange then true
1235
+ when Type::Nominal then type.class_name == "Integer" && type.type_args.empty?
1236
+ when Type::Union then type.members.all? { |m| integer_rooted_type?(m) }
1237
+ else false
1238
+ end
1239
+ end
1240
+
869
1241
  def range_target_class(range_node)
870
1242
  left = range_node.left
871
1243
  right = range_node.right
@@ -418,11 +418,19 @@ module Rigor
418
418
  end
419
419
  end
420
420
 
421
+ # v0.0.3 A — sentinel key under which `record_def_node`
422
+ # files DefNodes that live outside any class / module
423
+ # body (top-level helpers, `def`s nested inside DSL
424
+ # blocks like `RSpec.describe ... do; def helper; end`).
425
+ # Looked up by `Scope#top_level_def_for` to give
426
+ # implicit-self calls priority over RBS dispatch when
427
+ # the file defines a same-named local method.
428
+ TOP_LEVEL_DEF_KEY = "<toplevel>"
429
+
421
430
  def record_def_node(def_node, qualified_prefix, in_singleton_class, accumulator)
422
- return if qualified_prefix.empty?
423
431
  return if def_node.receiver.is_a?(Prism::SelfNode) || in_singleton_class
424
432
 
425
- class_name = qualified_prefix.join("::")
433
+ class_name = qualified_prefix.empty? ? TOP_LEVEL_DEF_KEY : qualified_prefix.join("::")
426
434
  accumulator[class_name] ||= {}
427
435
  accumulator[class_name][def_node.name] = def_node
428
436
  end