rigortype 0.0.1 → 0.0.3
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/data/builtins/ruby_core/array.yml +1470 -0
- data/data/builtins/ruby_core/file.yml +501 -0
- data/data/builtins/ruby_core/io.yml +1594 -0
- data/data/builtins/ruby_core/numeric.yml +1809 -0
- data/data/builtins/ruby_core/string.yml +1850 -0
- data/lib/rigor/analysis/check_rules.rb +297 -5
- data/lib/rigor/analysis/diagnostic.rb +13 -2
- data/lib/rigor/analysis/runner.rb +52 -5
- data/lib/rigor/builtins/imported_refinements.rb +69 -0
- data/lib/rigor/cli/type_of_command.rb +11 -5
- data/lib/rigor/cli/type_scan_command.rb +13 -8
- data/lib/rigor/cli.rb +26 -6
- data/lib/rigor/configuration.rb +18 -2
- data/lib/rigor/environment.rb +3 -1
- data/lib/rigor/inference/acceptance.rb +180 -0
- data/lib/rigor/inference/builtins/array_catalog.rb +46 -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/string_catalog.rb +39 -0
- data/lib/rigor/inference/expression_typer.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +650 -16
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +144 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +113 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +107 -0
- data/lib/rigor/inference/method_dispatcher.rb +28 -21
- data/lib/rigor/inference/narrowing.rb +471 -10
- data/lib/rigor/inference/scope_indexer.rb +66 -0
- data/lib/rigor/inference/statement_evaluator.rb +305 -2
- data/lib/rigor/rbs_extended.rb +174 -14
- data/lib/rigor/scope.rb +44 -5
- data/lib/rigor/type/combinator.rb +69 -1
- data/lib/rigor/type/difference.rb +155 -0
- data/lib/rigor/type/integer_range.rb +137 -0
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/sig/rigor/inference.rbs +5 -2
- data/sig/rigor/rbs_extended.rbs +25 -1
- data/sig/rigor/scope.rbs +4 -0
- data/sig/rigor/type.rbs +51 -1
- metadata +15 -1
|
@@ -6,9 +6,11 @@ require_relative "dynamic"
|
|
|
6
6
|
require_relative "nominal"
|
|
7
7
|
require_relative "singleton"
|
|
8
8
|
require_relative "constant"
|
|
9
|
+
require_relative "integer_range"
|
|
9
10
|
require_relative "tuple"
|
|
10
11
|
require_relative "hash_shape"
|
|
11
12
|
require_relative "union"
|
|
13
|
+
require_relative "difference"
|
|
12
14
|
|
|
13
15
|
module Rigor
|
|
14
16
|
module Type
|
|
@@ -20,7 +22,7 @@ module Rigor
|
|
|
20
22
|
#
|
|
21
23
|
# See docs/internal-spec/internal-type-api.md and
|
|
22
24
|
# docs/type-specification/normalization.md.
|
|
23
|
-
module Combinator
|
|
25
|
+
module Combinator # rubocop:disable Metrics/ModuleLength
|
|
24
26
|
module_function
|
|
25
27
|
|
|
26
28
|
def top
|
|
@@ -65,6 +67,72 @@ module Rigor
|
|
|
65
67
|
Constant.new(value)
|
|
66
68
|
end
|
|
67
69
|
|
|
70
|
+
# Bounded-integer carrier. Each bound is either an `Integer` or
|
|
71
|
+
# one of `:neg_infinity` / `:pos_infinity` (sentinels exposed as
|
|
72
|
+
# `IntegerRange::NEG_INFINITY` / `POS_INFINITY`).
|
|
73
|
+
def integer_range(min, max)
|
|
74
|
+
IntegerRange.new(min, max)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Convenience aliases for the most common bounded shapes. The
|
|
78
|
+
# named alias survives roundtrip through `describe` for nicer
|
|
79
|
+
# human-facing output.
|
|
80
|
+
def positive_int
|
|
81
|
+
IntegerRange.new(1, IntegerRange::POS_INFINITY)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def non_negative_int
|
|
85
|
+
IntegerRange.new(0, IntegerRange::POS_INFINITY)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def negative_int
|
|
89
|
+
IntegerRange.new(IntegerRange::NEG_INFINITY, -1)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def non_positive_int
|
|
93
|
+
IntegerRange.new(IntegerRange::NEG_INFINITY, 0)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def universal_int
|
|
97
|
+
IntegerRange.new(IntegerRange::NEG_INFINITY, IntegerRange::POS_INFINITY)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Point-removal refinement carrier (ADR-3 OQ3 Option C). Use
|
|
101
|
+
# `non_empty_string` / `non_zero_int` / `non_empty_array` /
|
|
102
|
+
# `non_empty_hash` for the imported built-in shapes; raw
|
|
103
|
+
# `difference(base, removed)` for ad-hoc refinements an
|
|
104
|
+
# `RBS::Extended` annotation introduces.
|
|
105
|
+
def difference(base, removed)
|
|
106
|
+
Difference.new(base, removed)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def non_empty_string
|
|
110
|
+
Difference.new(nominal_of("String"), constant_of(""))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def non_zero_int
|
|
114
|
+
Difference.new(nominal_of("Integer"), constant_of(0))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# `non-empty-array[T]` requires the element type so the
|
|
118
|
+
# `Nominal[Array, [T]]` projection through Array#first /
|
|
119
|
+
# #last keeps element precision intact. The default
|
|
120
|
+
# `Top` admits any array element when the caller does
|
|
121
|
+
# not have a more specific element type.
|
|
122
|
+
def non_empty_array(element = top)
|
|
123
|
+
Difference.new(
|
|
124
|
+
nominal_of("Array", type_args: [element]),
|
|
125
|
+
tuple_of
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def non_empty_hash(key = top, value = top)
|
|
130
|
+
Difference.new(
|
|
131
|
+
nominal_of("Hash", type_args: [key, value]),
|
|
132
|
+
hash_shape_of({})
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
|
|
68
136
|
# Constructs a heterogeneous, fixed-arity Tuple from positional
|
|
69
137
|
# element types. `tuple_of()` produces the empty tuple `Tuple[]`,
|
|
70
138
|
# which is structurally distinct from the raw `Nominal[Array]`.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../trinary"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Type
|
|
7
|
+
# `Difference[base, removed]` — the value set of `base` minus
|
|
8
|
+
# the value set of `removed`. Implements the point-removal
|
|
9
|
+
# half of the OQ3 refinement-carrier strategy
|
|
10
|
+
# ([ADR-3](docs/adr/3-type-representation.md), Working
|
|
11
|
+
# Decision Option C):
|
|
12
|
+
#
|
|
13
|
+
# non-empty-string = Difference[Nominal[String], Constant[""]]
|
|
14
|
+
# non-zero-int = Difference[Nominal[Integer], Constant[0]]
|
|
15
|
+
# non-empty-array[T] = Difference[Nominal[Array, [T]], Tuple[]]
|
|
16
|
+
# non-empty-hash[K,V] = Difference[Nominal[Hash, [K,V]], HashShape{}]
|
|
17
|
+
#
|
|
18
|
+
# The carrier itself is structural: it stores `base` and
|
|
19
|
+
# `removed` as inner `Type` references and answers projection
|
|
20
|
+
# / acceptance / display questions by composing those inner
|
|
21
|
+
# answers per the lattice algebra in
|
|
22
|
+
# [`value-lattice.md`](docs/type-specification/value-lattice.md).
|
|
23
|
+
# The canonical-name registry (display side) lives in
|
|
24
|
+
# `Rigor::Type::Combinator` and prints kebab-case names like
|
|
25
|
+
# `non-empty-string` for the recognised shapes; unrecognised
|
|
26
|
+
# differences fall back to the raw `base - removed`
|
|
27
|
+
# operator form per [`type-operators.md`](docs/type-specification/type-operators.md).
|
|
28
|
+
#
|
|
29
|
+
# Construction goes through `Type::Combinator.difference` /
|
|
30
|
+
# `Combinator.non_empty_string` etc. — direct `.new` calls
|
|
31
|
+
# are an internal contract; callers MUST ensure both bounds
|
|
32
|
+
# are valid `Rigor::Type` values and that `removed` is a
|
|
33
|
+
# subtype-or-equal of `base` (otherwise the difference does
|
|
34
|
+
# not narrow anything and a normalisation upstream should
|
|
35
|
+
# collapse to `base`).
|
|
36
|
+
class Difference
|
|
37
|
+
attr_reader :base, :removed
|
|
38
|
+
|
|
39
|
+
def initialize(base, removed)
|
|
40
|
+
@base = base
|
|
41
|
+
@removed = removed
|
|
42
|
+
freeze
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def describe(verbosity = :short)
|
|
46
|
+
named = canonical_name
|
|
47
|
+
return named if named
|
|
48
|
+
|
|
49
|
+
"#{base.describe(verbosity)} - #{removed.describe(verbosity)}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Erases to the base nominal: every refinement MUST erase
|
|
53
|
+
# to its base per [`rbs-erasure.md`](docs/type-specification/rbs-erasure.md).
|
|
54
|
+
def erase_to_rbs
|
|
55
|
+
base.erase_to_rbs
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def top
|
|
59
|
+
Trinary.no
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def bot
|
|
63
|
+
Trinary.no
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def dynamic
|
|
67
|
+
base.respond_to?(:dynamic) ? base.dynamic : Trinary.no
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def accepts(other, mode: :gradual)
|
|
71
|
+
Inference::Acceptance.accepts(self, other, mode: mode)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def ==(other)
|
|
75
|
+
other.is_a?(Difference) && base == other.base && removed == other.removed
|
|
76
|
+
end
|
|
77
|
+
alias eql? ==
|
|
78
|
+
|
|
79
|
+
def hash
|
|
80
|
+
[Difference, base, removed].hash
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def inspect
|
|
84
|
+
"#<Rigor::Type::Difference #{describe(:short)}>"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# Renders the kebab-case shorthand for recognised
|
|
90
|
+
# imported-built-in shapes. Parameterised bases keep their
|
|
91
|
+
# type-args in the canonical form (`non-empty-array[T]`,
|
|
92
|
+
# `non-empty-hash[K, V]`) so element-precision survives the
|
|
93
|
+
# display round-trip. Unrecognised shapes fall back to the
|
|
94
|
+
# raw `base - removed` operator form.
|
|
95
|
+
#
|
|
96
|
+
# The recognised set is kept in sync with the imported-built-in
|
|
97
|
+
# catalogue ([`imported-built-in-types.md`](docs/type-specification/imported-built-in-types.md)).
|
|
98
|
+
def canonical_name
|
|
99
|
+
return nil unless base.is_a?(Nominal)
|
|
100
|
+
|
|
101
|
+
send(CANONICAL_HANDLERS[base.class_name] || :no_canonical_name)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
CANONICAL_HANDLERS = {
|
|
105
|
+
"String" => :string_canonical_name,
|
|
106
|
+
"Integer" => :integer_canonical_name,
|
|
107
|
+
"Array" => :array_canonical_name_if_empty,
|
|
108
|
+
"Hash" => :hash_canonical_name_if_empty
|
|
109
|
+
}.freeze
|
|
110
|
+
private_constant :CANONICAL_HANDLERS
|
|
111
|
+
|
|
112
|
+
def no_canonical_name
|
|
113
|
+
nil
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def string_canonical_name
|
|
117
|
+
return nil unless removed.is_a?(Constant) && removed.value == ""
|
|
118
|
+
|
|
119
|
+
"non-empty-string"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def integer_canonical_name
|
|
123
|
+
return nil unless removed.is_a?(Constant) && removed.value.is_a?(Integer) && removed.value.zero?
|
|
124
|
+
|
|
125
|
+
"non-zero-int"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def array_canonical_name_if_empty
|
|
129
|
+
return nil unless removed.is_a?(Tuple) && removed.elements.empty?
|
|
130
|
+
|
|
131
|
+
array_canonical_name
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def hash_canonical_name_if_empty
|
|
135
|
+
return nil unless removed.is_a?(HashShape) && removed.pairs.empty?
|
|
136
|
+
|
|
137
|
+
hash_canonical_name
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def array_canonical_name
|
|
141
|
+
elem = base.type_args.first
|
|
142
|
+
return "non-empty-array" if elem.nil?
|
|
143
|
+
|
|
144
|
+
"non-empty-array[#{elem.describe}]"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def hash_canonical_name
|
|
148
|
+
key, value = base.type_args
|
|
149
|
+
return "non-empty-hash" if key.nil? || value.nil?
|
|
150
|
+
|
|
151
|
+
"non-empty-hash[#{key.describe}, #{value.describe}]"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../trinary"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Type
|
|
7
|
+
# A bounded integer range carrier. Each bound is either an `Integer`
|
|
8
|
+
# or one of the symbolic infinities `:neg_infinity` / `:pos_infinity`.
|
|
9
|
+
# Inspired by PHPStan's `int<min, max>` family — the named aliases
|
|
10
|
+
# `positive-int` (1..), `non-negative-int` (0..), `negative-int`
|
|
11
|
+
# (..-1), `non-positive-int` (..0) all surface through this single
|
|
12
|
+
# carrier and are recovered in `describe` for human-friendly output.
|
|
13
|
+
#
|
|
14
|
+
# Constraints on construction:
|
|
15
|
+
# - both bounds must be either `Integer` or one of the two infinity
|
|
16
|
+
# sentinels;
|
|
17
|
+
# - if both bounds are concrete, `min <= max` must hold;
|
|
18
|
+
# - the universal case `(-∞, +∞)` is structurally distinct from
|
|
19
|
+
# `Nominal[Integer]` — it carries no extra information today but
|
|
20
|
+
# keeps the carrier closed under range narrowing.
|
|
21
|
+
#
|
|
22
|
+
# Erasure to RBS is always "Integer": RBS itself does not natively
|
|
23
|
+
# express bounded integer ranges.
|
|
24
|
+
class IntegerRange
|
|
25
|
+
NEG_INFINITY = :neg_infinity
|
|
26
|
+
POS_INFINITY = :pos_infinity
|
|
27
|
+
INFINITIES = [NEG_INFINITY, POS_INFINITY].freeze
|
|
28
|
+
|
|
29
|
+
attr_reader :min, :max
|
|
30
|
+
|
|
31
|
+
def initialize(min, max)
|
|
32
|
+
validate_bound!(min, "min")
|
|
33
|
+
validate_bound!(max, "max")
|
|
34
|
+
if min.is_a?(Integer) && max.is_a?(Integer) && min > max
|
|
35
|
+
raise ArgumentError, "IntegerRange requires min (#{min}) <= max (#{max})"
|
|
36
|
+
end
|
|
37
|
+
if min == POS_INFINITY || max == NEG_INFINITY
|
|
38
|
+
raise ArgumentError, "IntegerRange bounds out of order: min=#{min.inspect}, max=#{max.inspect}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@min = min
|
|
42
|
+
@max = max
|
|
43
|
+
freeze
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def universal?
|
|
47
|
+
min == NEG_INFINITY && max == POS_INFINITY
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def finite?
|
|
51
|
+
min.is_a?(Integer) && max.is_a?(Integer)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cardinality
|
|
55
|
+
finite? ? (max - min + 1) : Float::INFINITY
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def covers?(int)
|
|
59
|
+
return false unless int.is_a?(Integer)
|
|
60
|
+
|
|
61
|
+
int.between?(lower, upper)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns the lower bound as a numeric (with `-Float::INFINITY` for
|
|
65
|
+
# `:neg_infinity`). Use this in arithmetic comparisons; never compare
|
|
66
|
+
# `:neg_infinity` directly with an `Integer`.
|
|
67
|
+
def lower
|
|
68
|
+
min == NEG_INFINITY ? -Float::INFINITY : min
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def upper
|
|
72
|
+
max == POS_INFINITY ? Float::INFINITY : max
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
ALIAS_NAMES = {
|
|
76
|
+
[NEG_INFINITY, POS_INFINITY] => "int",
|
|
77
|
+
[1, POS_INFINITY] => "positive-int",
|
|
78
|
+
[0, POS_INFINITY] => "non-negative-int",
|
|
79
|
+
[NEG_INFINITY, -1] => "negative-int",
|
|
80
|
+
[NEG_INFINITY, 0] => "non-positive-int"
|
|
81
|
+
}.freeze
|
|
82
|
+
|
|
83
|
+
def describe(_verbosity = :short)
|
|
84
|
+
ALIAS_NAMES[[min, max]] || generic_description
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def generic_description
|
|
88
|
+
return "int<#{min}, max>" if max == POS_INFINITY
|
|
89
|
+
return "int<min, #{max}>" if min == NEG_INFINITY
|
|
90
|
+
|
|
91
|
+
"int<#{min}, #{max}>"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def erase_to_rbs
|
|
95
|
+
"Integer"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def top
|
|
99
|
+
Trinary.no
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def bot
|
|
103
|
+
Trinary.no
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def dynamic
|
|
107
|
+
Trinary.no
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def accepts(other, mode: :gradual)
|
|
111
|
+
Inference::Acceptance.accepts(self, other, mode: mode)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def ==(other)
|
|
115
|
+
other.is_a?(IntegerRange) && min == other.min && max == other.max
|
|
116
|
+
end
|
|
117
|
+
alias eql? ==
|
|
118
|
+
|
|
119
|
+
def hash
|
|
120
|
+
[IntegerRange, min, max].hash
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def inspect
|
|
124
|
+
"#<Rigor::Type::IntegerRange #{describe(:short)}>"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def validate_bound!(bound, label)
|
|
130
|
+
return if bound.is_a?(Integer) || INFINITIES.include?(bound)
|
|
131
|
+
|
|
132
|
+
raise ArgumentError,
|
|
133
|
+
"IntegerRange #{label} must be Integer or :neg_infinity/:pos_infinity, got #{bound.inspect}"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
data/lib/rigor/type.rb
CHANGED
|
@@ -16,8 +16,10 @@ require_relative "type/dynamic"
|
|
|
16
16
|
require_relative "type/nominal"
|
|
17
17
|
require_relative "type/singleton"
|
|
18
18
|
require_relative "type/constant"
|
|
19
|
+
require_relative "type/integer_range"
|
|
19
20
|
require_relative "type/tuple"
|
|
20
21
|
require_relative "type/hash_shape"
|
|
21
22
|
require_relative "type/union"
|
|
23
|
+
require_relative "type/difference"
|
|
22
24
|
require_relative "type/accepts_result"
|
|
23
25
|
require_relative "type/combinator"
|
data/lib/rigor/version.rb
CHANGED
data/sig/rigor/inference.rbs
CHANGED
|
@@ -138,14 +138,17 @@ module Rigor
|
|
|
138
138
|
end
|
|
139
139
|
|
|
140
140
|
class CoverageScanner
|
|
141
|
-
class
|
|
141
|
+
class Result
|
|
142
|
+
attr_reader visits: Hash[Class, Integer]
|
|
143
|
+
attr_reader unrecognized: Hash[Class, Integer]
|
|
144
|
+
attr_reader events: Array[Fallback]
|
|
142
145
|
def visited_count: () -> Integer
|
|
143
146
|
def unrecognized_count: () -> Integer
|
|
144
147
|
def unrecognized_ratio: () -> Float
|
|
145
148
|
end
|
|
146
149
|
|
|
147
150
|
def initialize: (?scope: Scope?) -> void
|
|
148
|
-
def scan: (untyped root) ->
|
|
151
|
+
def scan: (untyped root) -> Result
|
|
149
152
|
end
|
|
150
153
|
end
|
|
151
154
|
end
|
data/sig/rigor/rbs_extended.rbs
CHANGED
|
@@ -10,13 +10,37 @@ module Rigor
|
|
|
10
10
|
attr_reader target_kind: target_kind
|
|
11
11
|
attr_reader target_name: Symbol
|
|
12
12
|
attr_reader class_name: String
|
|
13
|
+
attr_reader negative: bool
|
|
13
14
|
|
|
14
|
-
def self.new: (edge: predicate_edge, target_kind: target_kind, target_name: Symbol, class_name: String) -> PredicateEffect
|
|
15
|
+
def self.new: (edge: predicate_edge, target_kind: target_kind, target_name: Symbol, class_name: String, negative: bool) -> PredicateEffect
|
|
15
16
|
def truthy_only?: () -> bool
|
|
16
17
|
def falsey_only?: () -> bool
|
|
18
|
+
def negative?: () -> bool
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def self?.read_predicate_effects: (untyped method_def) -> Array[PredicateEffect]
|
|
20
22
|
def self?.parse_predicate_annotation: (String string) -> PredicateEffect?
|
|
23
|
+
|
|
24
|
+
type assert_condition = :always | :if_truthy_return | :if_falsey_return
|
|
25
|
+
|
|
26
|
+
class AssertEffect
|
|
27
|
+
attr_reader condition: assert_condition
|
|
28
|
+
attr_reader target_kind: target_kind
|
|
29
|
+
attr_reader target_name: Symbol
|
|
30
|
+
attr_reader class_name: String
|
|
31
|
+
attr_reader negative: bool
|
|
32
|
+
|
|
33
|
+
def self.new: (condition: assert_condition, target_kind: target_kind, target_name: Symbol, class_name: String, negative: bool) -> AssertEffect
|
|
34
|
+
def always?: () -> bool
|
|
35
|
+
def if_truthy_return?: () -> bool
|
|
36
|
+
def if_falsey_return?: () -> bool
|
|
37
|
+
def negative?: () -> bool
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self?.read_assert_effects: (untyped method_def) -> Array[AssertEffect]
|
|
41
|
+
def self?.parse_assert_annotation: (String string) -> AssertEffect?
|
|
42
|
+
|
|
43
|
+
def self?.read_return_type_override: (untyped method_def) -> Type::t?
|
|
44
|
+
def self?.parse_return_type_override: (String string) -> Type::t?
|
|
21
45
|
end
|
|
22
46
|
end
|
data/sig/rigor/scope.rbs
CHANGED
|
@@ -14,6 +14,7 @@ module Rigor
|
|
|
14
14
|
attr_reader discovered_classes: Hash[String, Type::Singleton]
|
|
15
15
|
attr_reader in_source_constants: Hash[String, Type::t]
|
|
16
16
|
attr_reader discovered_methods: Hash[String, Hash[Symbol, Symbol]]
|
|
17
|
+
attr_reader discovered_def_nodes: Hash[String, Hash[Symbol, untyped]]
|
|
17
18
|
|
|
18
19
|
def self.empty: (?environment: Environment) -> Scope
|
|
19
20
|
|
|
@@ -35,6 +36,9 @@ module Rigor
|
|
|
35
36
|
def with_in_source_constants: (Hash[String, Type::t] table) -> Scope
|
|
36
37
|
def with_discovered_methods: (Hash[String, Hash[Symbol, Symbol]] table) -> Scope
|
|
37
38
|
def discovered_method?: (String | Symbol class_name, String | Symbol method_name, Symbol kind) -> bool
|
|
39
|
+
def with_discovered_def_nodes: (Hash[String, Hash[Symbol, untyped]] table) -> Scope
|
|
40
|
+
def user_def_for: (String | Symbol class_name, String | Symbol method_name) -> untyped?
|
|
41
|
+
def top_level_def_for: (String | Symbol method_name) -> untyped?
|
|
38
42
|
def with_fact: (Analysis::FactStore::Fact fact) -> Scope
|
|
39
43
|
def with_self_type: (Type::t? type) -> Scope
|
|
40
44
|
def with_declared_types: (Hash[untyped, Type::t] table) -> Scope
|
data/sig/rigor/type.rbs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Rigor
|
|
2
2
|
module Type
|
|
3
|
-
type t = Top | Bot | Dynamic | Constant | Nominal | Singleton | Union | Tuple | HashShape
|
|
3
|
+
type t = Top | Bot | Dynamic | Constant | IntegerRange | Nominal | Singleton | Union | Difference | Tuple | HashShape
|
|
4
4
|
|
|
5
5
|
type accepts_mode = :strict | :gradual | :loose
|
|
6
6
|
|
|
@@ -58,6 +58,30 @@ module Rigor
|
|
|
58
58
|
def inspect: () -> String
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
class IntegerRange
|
|
62
|
+
NEG_INFINITY: Symbol
|
|
63
|
+
POS_INFINITY: Symbol
|
|
64
|
+
attr_reader min: (Integer | Symbol)
|
|
65
|
+
attr_reader max: (Integer | Symbol)
|
|
66
|
+
def initialize: ((Integer | Symbol) min, (Integer | Symbol) max) -> void
|
|
67
|
+
def universal?: () -> bool
|
|
68
|
+
def finite?: () -> bool
|
|
69
|
+
def cardinality: () -> (Integer | Float)
|
|
70
|
+
def covers?: (untyped value) -> bool
|
|
71
|
+
def lower: () -> Numeric
|
|
72
|
+
def upper: () -> Numeric
|
|
73
|
+
def describe: (?Symbol verbosity) -> String
|
|
74
|
+
def generic_description: () -> String
|
|
75
|
+
def erase_to_rbs: () -> String
|
|
76
|
+
def top: () -> Top
|
|
77
|
+
def bot: () -> Bot
|
|
78
|
+
def dynamic: () -> Dynamic
|
|
79
|
+
def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
|
|
80
|
+
def ==: (untyped other) -> bool
|
|
81
|
+
def hash: () -> Integer
|
|
82
|
+
def inspect: () -> String
|
|
83
|
+
end
|
|
84
|
+
|
|
61
85
|
class Nominal
|
|
62
86
|
attr_reader class_name: String
|
|
63
87
|
attr_reader type_args: Array[Type::t]
|
|
@@ -101,6 +125,21 @@ module Rigor
|
|
|
101
125
|
def inspect: () -> String
|
|
102
126
|
end
|
|
103
127
|
|
|
128
|
+
class Difference
|
|
129
|
+
attr_reader base: Type::t
|
|
130
|
+
attr_reader removed: Type::t
|
|
131
|
+
def initialize: (Type::t base, Type::t removed) -> void
|
|
132
|
+
def describe: (?Symbol verbosity) -> String
|
|
133
|
+
def erase_to_rbs: () -> String
|
|
134
|
+
def top: () -> Top
|
|
135
|
+
def bot: () -> Bot
|
|
136
|
+
def dynamic: () -> Dynamic
|
|
137
|
+
def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
|
|
138
|
+
def ==: (untyped other) -> bool
|
|
139
|
+
def hash: () -> Integer
|
|
140
|
+
def inspect: () -> String
|
|
141
|
+
end
|
|
142
|
+
|
|
104
143
|
class Tuple
|
|
105
144
|
attr_reader elements: Array[Type::t]
|
|
106
145
|
def initialize: (Array[Type::t] elements) -> void
|
|
@@ -163,6 +202,17 @@ module Rigor
|
|
|
163
202
|
def self?.nominal_of: (Module | String class_name_or_object, ?type_args: Array[Type::t]) -> Nominal
|
|
164
203
|
def self?.singleton_of: (Module | String class_name_or_object) -> Singleton
|
|
165
204
|
def self?.constant_of: (untyped value) -> Constant
|
|
205
|
+
def self?.difference: (Type::t base, Type::t removed) -> Difference
|
|
206
|
+
def self?.non_empty_string: () -> Difference
|
|
207
|
+
def self?.non_zero_int: () -> Difference
|
|
208
|
+
def self?.non_empty_array: (?Type::t element) -> Difference
|
|
209
|
+
def self?.non_empty_hash: (?Type::t key, ?Type::t value) -> Difference
|
|
210
|
+
def self?.integer_range: ((Integer | Symbol) min, (Integer | Symbol) max) -> IntegerRange
|
|
211
|
+
def self?.positive_int: () -> IntegerRange
|
|
212
|
+
def self?.non_negative_int: () -> IntegerRange
|
|
213
|
+
def self?.negative_int: () -> IntegerRange
|
|
214
|
+
def self?.non_positive_int: () -> IntegerRange
|
|
215
|
+
def self?.universal_int: () -> IntegerRange
|
|
166
216
|
def self?.tuple_of: (*Type::t elements) -> Tuple
|
|
167
217
|
def self?.hash_shape_of: (?Hash[untyped, Type::t]? pairs, **untyped options) -> HashShape
|
|
168
218
|
def self?.union: (*Type::t types) -> Type::t
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rigortype
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rigor contributors
|
|
@@ -160,6 +160,11 @@ extra_rdoc_files: []
|
|
|
160
160
|
files:
|
|
161
161
|
- LICENSE
|
|
162
162
|
- README.md
|
|
163
|
+
- data/builtins/ruby_core/array.yml
|
|
164
|
+
- data/builtins/ruby_core/file.yml
|
|
165
|
+
- data/builtins/ruby_core/io.yml
|
|
166
|
+
- data/builtins/ruby_core/numeric.yml
|
|
167
|
+
- data/builtins/ruby_core/string.yml
|
|
163
168
|
- exe/rigor
|
|
164
169
|
- lib/rigor.rb
|
|
165
170
|
- lib/rigor/analysis/check_rules.rb
|
|
@@ -169,6 +174,7 @@ files:
|
|
|
169
174
|
- lib/rigor/analysis/runner.rb
|
|
170
175
|
- lib/rigor/ast.rb
|
|
171
176
|
- lib/rigor/ast/type_node.rb
|
|
177
|
+
- lib/rigor/builtins/imported_refinements.rb
|
|
172
178
|
- lib/rigor/cli.rb
|
|
173
179
|
- lib/rigor/cli/type_of_command.rb
|
|
174
180
|
- lib/rigor/cli/type_of_renderer.rb
|
|
@@ -182,6 +188,10 @@ files:
|
|
|
182
188
|
- lib/rigor/environment/rbs_loader.rb
|
|
183
189
|
- lib/rigor/inference/acceptance.rb
|
|
184
190
|
- lib/rigor/inference/block_parameter_binder.rb
|
|
191
|
+
- lib/rigor/inference/builtins/array_catalog.rb
|
|
192
|
+
- lib/rigor/inference/builtins/method_catalog.rb
|
|
193
|
+
- lib/rigor/inference/builtins/numeric_catalog.rb
|
|
194
|
+
- lib/rigor/inference/builtins/string_catalog.rb
|
|
185
195
|
- lib/rigor/inference/closure_escape_analyzer.rb
|
|
186
196
|
- lib/rigor/inference/coverage_scanner.rb
|
|
187
197
|
- lib/rigor/inference/expression_typer.rb
|
|
@@ -189,6 +199,8 @@ files:
|
|
|
189
199
|
- lib/rigor/inference/fallback_tracer.rb
|
|
190
200
|
- lib/rigor/inference/method_dispatcher.rb
|
|
191
201
|
- lib/rigor/inference/method_dispatcher/constant_folding.rb
|
|
202
|
+
- lib/rigor/inference/method_dispatcher/file_folding.rb
|
|
203
|
+
- lib/rigor/inference/method_dispatcher/iterator_dispatch.rb
|
|
192
204
|
- lib/rigor/inference/method_dispatcher/overload_selector.rb
|
|
193
205
|
- lib/rigor/inference/method_dispatcher/rbs_dispatch.rb
|
|
194
206
|
- lib/rigor/inference/method_dispatcher/shape_dispatch.rb
|
|
@@ -210,8 +222,10 @@ files:
|
|
|
210
222
|
- lib/rigor/type/bot.rb
|
|
211
223
|
- lib/rigor/type/combinator.rb
|
|
212
224
|
- lib/rigor/type/constant.rb
|
|
225
|
+
- lib/rigor/type/difference.rb
|
|
213
226
|
- lib/rigor/type/dynamic.rb
|
|
214
227
|
- lib/rigor/type/hash_shape.rb
|
|
228
|
+
- lib/rigor/type/integer_range.rb
|
|
215
229
|
- lib/rigor/type/nominal.rb
|
|
216
230
|
- lib/rigor/type/singleton.rb
|
|
217
231
|
- lib/rigor/type/top.rb
|