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.
- checksums.yaml +4 -4
- data/README.md +24 -7
- data/data/builtins/ruby_core/array.yml +1470 -0
- data/data/builtins/ruby_core/file.yml +501 -0
- data/data/builtins/ruby_core/hash.yml +936 -0
- data/data/builtins/ruby_core/io.yml +1594 -0
- data/data/builtins/ruby_core/numeric.yml +1809 -0
- data/data/builtins/ruby_core/range.yml +389 -0
- data/data/builtins/ruby_core/set.yml +594 -0
- data/data/builtins/ruby_core/string.yml +1850 -0
- data/data/builtins/ruby_core/time.yml +750 -0
- data/lib/rigor/analysis/check_rules.rb +97 -4
- data/lib/rigor/analysis/runner.rb +4 -0
- data/lib/rigor/builtins/imported_refinements.rb +251 -0
- data/lib/rigor/configuration.rb +6 -1
- data/lib/rigor/inference/acceptance.rb +324 -6
- data/lib/rigor/inference/builtins/array_catalog.rb +46 -0
- data/lib/rigor/inference/builtins/hash_catalog.rb +40 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +90 -0
- data/lib/rigor/inference/builtins/numeric_catalog.rb +93 -0
- data/lib/rigor/inference/builtins/range_catalog.rb +46 -0
- data/lib/rigor/inference/builtins/set_catalog.rb +54 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +39 -0
- data/lib/rigor/inference/builtins/time_catalog.rb +64 -0
- data/lib/rigor/inference/expression_typer.rb +48 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +670 -16
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +144 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +215 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +23 -7
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +240 -4
- data/lib/rigor/inference/method_dispatcher.rb +28 -21
- data/lib/rigor/inference/method_parameter_binder.rb +29 -4
- data/lib/rigor/inference/narrowing.rb +376 -4
- data/lib/rigor/inference/scope_indexer.rb +10 -2
- data/lib/rigor/inference/statement_evaluator.rb +213 -2
- data/lib/rigor/rbs_extended.rb +230 -15
- data/lib/rigor/scope.rb +14 -0
- data/lib/rigor/type/combinator.rb +159 -1
- data/lib/rigor/type/difference.rb +155 -0
- data/lib/rigor/type/integer_range.rb +137 -0
- data/lib/rigor/type/intersection.rb +135 -0
- data/lib/rigor/type/refined.rb +174 -0
- data/lib/rigor/type.rb +4 -0
- data/lib/rigor/version.rb +1 -1
- data/sig/rigor/rbs_extended.rbs +14 -0
- data/sig/rigor/scope.rbs +1 -0
- data/sig/rigor/type.rbs +91 -1
- metadata +25 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "method_catalog"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module Builtins
|
|
8
|
+
# `Range` catalog. Singleton — load once, consult during
|
|
9
|
+
# dispatch.
|
|
10
|
+
#
|
|
11
|
+
# Range is largely immutable: `begin`, `end`, and `excl` are
|
|
12
|
+
# set at construction by `range_initialize` and never mutated
|
|
13
|
+
# afterwards. The blocklist below therefore stays small. The
|
|
14
|
+
# entries we DO need are the iteration methods whose C body
|
|
15
|
+
# routes through a helper the block/yield regex does not
|
|
16
|
+
# recognise, so the classifier mis-flags them as `:leaf`
|
|
17
|
+
# despite yielding to a block.
|
|
18
|
+
RANGE_CATALOG = MethodCatalog.new(
|
|
19
|
+
path: File.expand_path(
|
|
20
|
+
"../../../../data/builtins/ruby_core/range.yml",
|
|
21
|
+
__dir__
|
|
22
|
+
),
|
|
23
|
+
mutating_selectors: {
|
|
24
|
+
"Range" => Set[
|
|
25
|
+
# `range_initialize` / `range_initialize_copy` write
|
|
26
|
+
# `begin`/`end`/`excl` slots on the receiver; classed
|
|
27
|
+
# `:leaf` because the writes go through the struct
|
|
28
|
+
# accessor not `rb_check_frozen`. Blocked for symmetry
|
|
29
|
+
# with String / Array.
|
|
30
|
+
:initialize, :initialize_copy,
|
|
31
|
+
# `range_reverse_each` yields to its block via
|
|
32
|
+
# `range_each_func` -> caller's block; the regex
|
|
33
|
+
# classifier follows direct `rb_yield*` calls only.
|
|
34
|
+
:reverse_each,
|
|
35
|
+
# `range_percent_step` returns an Enumerator unless a
|
|
36
|
+
# block is supplied, in which case it yields. Treated
|
|
37
|
+
# as block-dependent so the fold tier never invokes it
|
|
38
|
+
# against a literal Range and tries to materialise an
|
|
39
|
+
# Enumerator into a Constant.
|
|
40
|
+
:%
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "method_catalog"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module Builtins
|
|
8
|
+
# `Set` catalog. Singleton — load once, consult during dispatch.
|
|
9
|
+
#
|
|
10
|
+
# Set was rewritten in C and folded into CRuby for Ruby 3.2+;
|
|
11
|
+
# the reference branch (`ruby_4_0`) ships the implementation in
|
|
12
|
+
# `references/ruby/set.c` with `Init_Set` registering every
|
|
13
|
+
# method directly. There is no `set.rb` prelude — the trailing
|
|
14
|
+
# `rb_provide("set.rb")` makes `require "set"` a no-op against
|
|
15
|
+
# the built-in.
|
|
16
|
+
#
|
|
17
|
+
# The blocklist below catches the catalog `:leaf` entries the
|
|
18
|
+
# C-body classifier mis-attributes. Set's iteration helpers
|
|
19
|
+
# (`set_iter`, `RETURN_SIZED_ENUMERATOR`) and its identity-
|
|
20
|
+
# mode and reset paths drive into helpers the regex classifier
|
|
21
|
+
# does not yet recognise as block-yielding or mutating.
|
|
22
|
+
SET_CATALOG = MethodCatalog.new(
|
|
23
|
+
path: File.expand_path(
|
|
24
|
+
"../../../../data/builtins/ruby_core/set.yml",
|
|
25
|
+
__dir__
|
|
26
|
+
),
|
|
27
|
+
mutating_selectors: {
|
|
28
|
+
"Set" => Set[
|
|
29
|
+
# Indirect mutators classified `:leaf` because the C
|
|
30
|
+
# classifier did not follow the helper functions:
|
|
31
|
+
#
|
|
32
|
+
# - `initialize_copy` calls `set_copy` to overwrite the
|
|
33
|
+
# receiver's table.
|
|
34
|
+
# - `compare_by_identity` swaps the internal hash type
|
|
35
|
+
# via `set_reset_table_with_type`.
|
|
36
|
+
# - `reset` rebuilds the internal table to dedup after
|
|
37
|
+
# element mutation.
|
|
38
|
+
:initialize_copy, :compare_by_identity, :reset,
|
|
39
|
+
# Block-dependent methods classified `:leaf` because the
|
|
40
|
+
# C body uses `set_iter` / `RETURN_SIZED_ENUMERATOR`
|
|
41
|
+
# rather than calling `rb_yield` directly:
|
|
42
|
+
:each, :classify, :divide,
|
|
43
|
+
# `disjoint?` delegates into `set_i_intersect`, which
|
|
44
|
+
# for non-Set enumerables uses `rb_funcall(other,
|
|
45
|
+
# :any?, ...)` — that is user-redefinable dispatch the
|
|
46
|
+
# classifier missed because the call site is in a
|
|
47
|
+
# sibling function.
|
|
48
|
+
:disjoint?
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "method_catalog"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module Builtins
|
|
8
|
+
# `String` and `Symbol` catalog. Singleton — load once,
|
|
9
|
+
# consult during dispatch.
|
|
10
|
+
#
|
|
11
|
+
# The blocklist below is the curated set of catalog `:leaf`
|
|
12
|
+
# entries the C-body classifier mis-attributes (the body of
|
|
13
|
+
# `rb_str_replace` calls `str_modifiable` / `str_discard`
|
|
14
|
+
# which the regex-based classifier does not recognise as
|
|
15
|
+
# mutation primitives). Adding to the blocklist is the
|
|
16
|
+
# corrective surface for false positives until the
|
|
17
|
+
# classifier learns the helper functions.
|
|
18
|
+
STRING_CATALOG = MethodCatalog.new(
|
|
19
|
+
path: File.expand_path(
|
|
20
|
+
"../../../../data/builtins/ruby_core/string.yml",
|
|
21
|
+
__dir__
|
|
22
|
+
),
|
|
23
|
+
mutating_selectors: {
|
|
24
|
+
"String" => Set[
|
|
25
|
+
:replace, :initialize, :initialize_copy, :clear, :<<, :concat, :insert,
|
|
26
|
+
:prepend, :force_encoding, :encode, :scrub, :unicode_normalize, :"[]=",
|
|
27
|
+
:upto, :each_byte, :each_char, :each_codepoint,
|
|
28
|
+
:each_grapheme_cluster, :each_line, :bytesplice
|
|
29
|
+
],
|
|
30
|
+
"Symbol" => Set[
|
|
31
|
+
# Symbol is immutable in Ruby; the classifier mis-flags
|
|
32
|
+
# `inspect` because `rb_sym_inspect` builds a temporary
|
|
33
|
+
# mutable buffer. Allow it.
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "method_catalog"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module Builtins
|
|
8
|
+
# `Time` catalog. Singleton — load once, consult during dispatch.
|
|
9
|
+
#
|
|
10
|
+
# Time is a pure-C built-in: the Init block in
|
|
11
|
+
# `references/ruby/time.c` registers the bulk of the surface,
|
|
12
|
+
# and the Ruby-side prelude `references/ruby/timev.rb`
|
|
13
|
+
# contributes the class-side constructors (`Time.now`,
|
|
14
|
+
# `Time.at`, `Time.new`) through Primitive cexpr stubs.
|
|
15
|
+
#
|
|
16
|
+
# Time receivers are not lifted to a `Constant` carrier today
|
|
17
|
+
# (there is no `Time` literal node — the closest is
|
|
18
|
+
# `Time.now` / `Time.new(...)`, which produce `Nominal[Time]`).
|
|
19
|
+
# The catalog wiring therefore mostly governs:
|
|
20
|
+
#
|
|
21
|
+
# 1. The size-projection-equivalent reader surface (`#year`,
|
|
22
|
+
# `#month`, `#hour`, `#sec`, `#wday`, …) — RBS-declared
|
|
23
|
+
# `Integer` is preserved through dispatch.
|
|
24
|
+
# 2. The blocklist below, which keeps the indirect-mutator
|
|
25
|
+
# methods that the C-body classifier mis-flagged as
|
|
26
|
+
# `:leaf` from ever folding through a hypothetical future
|
|
27
|
+
# `Constant<Time>` carrier.
|
|
28
|
+
#
|
|
29
|
+
# The blocklist captures the false-positive `:leaf` entries
|
|
30
|
+
# whose helper functions the regex classifier did not
|
|
31
|
+
# recognise as mutators.
|
|
32
|
+
TIME_CATALOG = MethodCatalog.new(
|
|
33
|
+
path: File.expand_path(
|
|
34
|
+
"../../../../data/builtins/ruby_core/time.yml",
|
|
35
|
+
__dir__
|
|
36
|
+
),
|
|
37
|
+
mutating_selectors: {
|
|
38
|
+
"Time" => Set[
|
|
39
|
+
# `time_init_copy` writes the `timew` and `vtm` slots on
|
|
40
|
+
# the receiver via `time_set_timew` / `time_set_vtm`.
|
|
41
|
+
# Classed `:leaf` because those setters are not in the
|
|
42
|
+
# mutator regex's helper list. Blocked for symmetry with
|
|
43
|
+
# String / Array / Range / Set initialize_copy entries.
|
|
44
|
+
:initialize_copy,
|
|
45
|
+
# `time_localtime_m` -> `time_localtime` calls
|
|
46
|
+
# `time_modify(time)` to mark the receiver mutable
|
|
47
|
+
# before rewriting its `vtm` cache and `tzmode`. The
|
|
48
|
+
# docstring is explicit ("converts time to local time
|
|
49
|
+
# in place"). The C-body classifier mis-flagged it as
|
|
50
|
+
# `:leaf` because `time_modify` is not in its mutator
|
|
51
|
+
# regex.
|
|
52
|
+
:localtime,
|
|
53
|
+
# `time_gmtime` (registered as both `gmtime` and `utc`
|
|
54
|
+
# against `rb_cTime`) follows the same in-place pattern
|
|
55
|
+
# as `time_localtime`: `time_modify(time)` then a
|
|
56
|
+
# `time_set_vtm` write and `TZMODE_SET_UTC`. Both
|
|
57
|
+
# selectors share the cfunc, so both must be blocked.
|
|
58
|
+
:gmtime, :utc
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -744,6 +744,33 @@ module Rigor
|
|
|
744
744
|
arg_types = call_arg_types(node)
|
|
745
745
|
block_type = block_return_type_for(node, receiver, arg_types)
|
|
746
746
|
|
|
747
|
+
# v0.0.3 A — implicit-self calls prefer a same-named
|
|
748
|
+
# top-level `def` over RBS dispatch. Without this,
|
|
749
|
+
# a helper like `def select(...)` defined inside an
|
|
750
|
+
# `RSpec.describe ... do ... end` block mis-routes
|
|
751
|
+
# through `Enumerable#select` / `Object#select` and
|
|
752
|
+
# the caller observes `Array[Elem]` instead of the
|
|
753
|
+
# helper's actual return type. The check fires only
|
|
754
|
+
# for `node.receiver.nil?` (true implicit self), so
|
|
755
|
+
# explicit-receiver dispatch is unaffected.
|
|
756
|
+
local_def = node.receiver.nil? ? scope.top_level_def_for(node.name) : nil
|
|
757
|
+
if local_def
|
|
758
|
+
local_inference = infer_top_level_user_method(local_def, receiver, arg_types)
|
|
759
|
+
return local_inference if local_inference
|
|
760
|
+
|
|
761
|
+
# The local def matches by name but the
|
|
762
|
+
# parameter shape is too complex for the first-
|
|
763
|
+
# iteration binder (kwargs / optionals / rest).
|
|
764
|
+
# Returning `Dynamic[Top]` is the safest answer:
|
|
765
|
+
# we know RBS dispatch would be wrong (the
|
|
766
|
+
# method is user-defined and shadows whatever
|
|
767
|
+
# ancestor method the dispatch would find), and
|
|
768
|
+
# `Dynamic[Top]` propagates correctly through
|
|
769
|
+
# downstream call chains without surfacing
|
|
770
|
+
# misleading false-positive diagnostics.
|
|
771
|
+
return dynamic_top
|
|
772
|
+
end
|
|
773
|
+
|
|
747
774
|
result = MethodDispatcher.dispatch(
|
|
748
775
|
receiver_type: receiver,
|
|
749
776
|
method_name: node.name,
|
|
@@ -786,6 +813,22 @@ module Rigor
|
|
|
786
813
|
# - the inference is already in progress for this
|
|
787
814
|
# (class, method, signature) tuple — recursion
|
|
788
815
|
# safety net.
|
|
816
|
+
# v0.0.3 A — re-types a top-level (or DSL-block-nested)
|
|
817
|
+
# `def` discovered by `ScopeIndexer` under the
|
|
818
|
+
# `TOP_LEVEL_DEF_KEY` sentinel. Mirrors the
|
|
819
|
+
# `infer_user_method_return` shape but uses the
|
|
820
|
+
# current `scope.self_type` (or implicit `Object`)
|
|
821
|
+
# as the receiver carrier so the body's own self is
|
|
822
|
+
# consistent with the call site's. Returns nil when
|
|
823
|
+
# the parameter shape disqualifies the def, when the
|
|
824
|
+
# body is empty, or when a recursion cycle is
|
|
825
|
+
# detected.
|
|
826
|
+
def infer_top_level_user_method(def_node, receiver, arg_types)
|
|
827
|
+
infer_user_method_return(def_node, receiver, arg_types)
|
|
828
|
+
rescue StandardError
|
|
829
|
+
nil
|
|
830
|
+
end
|
|
831
|
+
|
|
789
832
|
def try_user_method_inference(receiver, call_node, arg_types)
|
|
790
833
|
return nil unless receiver.is_a?(Type::Nominal)
|
|
791
834
|
|
|
@@ -806,7 +849,11 @@ module Rigor
|
|
|
806
849
|
body_scope = build_user_method_body_scope(def_node, receiver, arg_types)
|
|
807
850
|
return nil if body_scope.nil?
|
|
808
851
|
|
|
809
|
-
|
|
852
|
+
# Recursion-guard signature. Uses `describe(:short)`
|
|
853
|
+
# so non-Nominal receivers (e.g. the implicit
|
|
854
|
+
# `Object` carrier used for top-level / DSL-block
|
|
855
|
+
# defs in v0.0.3 A) can participate without raising.
|
|
856
|
+
signature = [receiver.describe(:short), def_node.name, arg_types.map { |t| t.describe(:short) }]
|
|
810
857
|
stack = (Thread.current[INFERENCE_GUARD_KEY] ||= [])
|
|
811
858
|
return Type::Combinator.untyped if stack.include?(signature)
|
|
812
859
|
|