rigortype 0.0.1
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 +7 -0
- data/LICENSE +373 -0
- data/README.md +152 -0
- data/exe/rigor +9 -0
- data/lib/rigor/analysis/check_rules.rb +503 -0
- data/lib/rigor/analysis/diagnostic.rb +35 -0
- data/lib/rigor/analysis/fact_store.rb +133 -0
- data/lib/rigor/analysis/result.rb +29 -0
- data/lib/rigor/analysis/runner.rb +119 -0
- data/lib/rigor/ast/type_node.rb +41 -0
- data/lib/rigor/ast.rb +22 -0
- data/lib/rigor/cli/type_of_command.rb +160 -0
- data/lib/rigor/cli/type_of_renderer.rb +88 -0
- data/lib/rigor/cli/type_scan_command.rb +160 -0
- data/lib/rigor/cli/type_scan_renderer.rb +165 -0
- data/lib/rigor/cli/type_scan_report.rb +32 -0
- data/lib/rigor/cli.rb +195 -0
- data/lib/rigor/configuration.rb +49 -0
- data/lib/rigor/environment/class_registry.rb +141 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +64 -0
- data/lib/rigor/environment/rbs_loader.rb +244 -0
- data/lib/rigor/environment.rb +177 -0
- data/lib/rigor/inference/acceptance.rb +444 -0
- data/lib/rigor/inference/block_parameter_binder.rb +198 -0
- data/lib/rigor/inference/closure_escape_analyzer.rb +191 -0
- data/lib/rigor/inference/coverage_scanner.rb +85 -0
- data/lib/rigor/inference/expression_typer.rb +831 -0
- data/lib/rigor/inference/fallback.rb +35 -0
- data/lib/rigor/inference/fallback_tracer.rb +64 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +102 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +169 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +421 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +336 -0
- data/lib/rigor/inference/method_dispatcher.rb +213 -0
- data/lib/rigor/inference/method_parameter_binder.rb +257 -0
- data/lib/rigor/inference/multi_target_binder.rb +143 -0
- data/lib/rigor/inference/narrowing.rb +1008 -0
- data/lib/rigor/inference/rbs_type_translator.rb +219 -0
- data/lib/rigor/inference/scope_indexer.rb +468 -0
- data/lib/rigor/inference/statement_evaluator.rb +1017 -0
- data/lib/rigor/rbs_extended.rb +98 -0
- data/lib/rigor/scope.rb +340 -0
- data/lib/rigor/source/node_locator.rb +104 -0
- data/lib/rigor/source/node_walker.rb +37 -0
- data/lib/rigor/source.rb +15 -0
- data/lib/rigor/testing.rb +65 -0
- data/lib/rigor/trinary.rb +108 -0
- data/lib/rigor/type/accepts_result.rb +109 -0
- data/lib/rigor/type/bot.rb +57 -0
- data/lib/rigor/type/combinator.rb +148 -0
- data/lib/rigor/type/constant.rb +90 -0
- data/lib/rigor/type/dynamic.rb +60 -0
- data/lib/rigor/type/hash_shape.rb +246 -0
- data/lib/rigor/type/nominal.rb +83 -0
- data/lib/rigor/type/singleton.rb +65 -0
- data/lib/rigor/type/top.rb +56 -0
- data/lib/rigor/type/tuple.rb +84 -0
- data/lib/rigor/type/union.rb +65 -0
- data/lib/rigor/type.rb +23 -0
- data/lib/rigor/version.rb +5 -0
- data/lib/rigor.rb +29 -0
- data/sig/rigor/analysis/fact_store.rbs +51 -0
- data/sig/rigor/ast.rbs +11 -0
- data/sig/rigor/environment.rbs +59 -0
- data/sig/rigor/inference.rbs +151 -0
- data/sig/rigor/rbs_extended.rbs +22 -0
- data/sig/rigor/scope.rbs +49 -0
- data/sig/rigor/source.rbs +20 -0
- data/sig/rigor/testing.rbs +9 -0
- data/sig/rigor/trinary.rbs +29 -0
- data/sig/rigor/type.rbs +171 -0
- data/sig/rigor.rbs +70 -0
- metadata +260 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
# Three-valued logic value object shared by capability queries, relational
|
|
5
|
+
# queries, and any analyzer surface that distinguishes "proven yes",
|
|
6
|
+
# "proven no", and "cannot prove either".
|
|
7
|
+
#
|
|
8
|
+
# See docs/type-specification/relations-and-certainty.md for semantics and
|
|
9
|
+
# docs/internal-spec/internal-type-api.md for the contract.
|
|
10
|
+
class Trinary
|
|
11
|
+
VALUES = %i[yes no maybe].freeze
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def yes
|
|
15
|
+
@yes ||= new(:yes).freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def no
|
|
19
|
+
@no ||= new(:no).freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def maybe
|
|
23
|
+
@maybe ||= new(:maybe).freeze
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def from_symbol(symbol)
|
|
27
|
+
case symbol
|
|
28
|
+
when :yes then yes
|
|
29
|
+
when :no then no
|
|
30
|
+
when :maybe then maybe
|
|
31
|
+
else
|
|
32
|
+
raise ArgumentError, "unknown trinary value: #{symbol.inspect}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
attr_reader :value
|
|
38
|
+
|
|
39
|
+
def initialize(value)
|
|
40
|
+
raise ArgumentError, "unknown trinary value: #{value.inspect}" unless VALUES.include?(value)
|
|
41
|
+
|
|
42
|
+
@value = value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def yes?
|
|
46
|
+
value == :yes
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def no?
|
|
50
|
+
value == :no
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def maybe?
|
|
54
|
+
value == :maybe
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def negate
|
|
58
|
+
case value
|
|
59
|
+
when :yes then self.class.no
|
|
60
|
+
when :no then self.class.yes
|
|
61
|
+
when :maybe then self.class.maybe
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Conjunction. yes & yes = yes, no with anything = no, otherwise maybe.
|
|
66
|
+
def and(other)
|
|
67
|
+
coerced = coerce(other)
|
|
68
|
+
return self.class.no if no? || coerced.no?
|
|
69
|
+
return self.class.yes if yes? && coerced.yes?
|
|
70
|
+
|
|
71
|
+
self.class.maybe
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Disjunction. yes with anything = yes, no & no = no, otherwise maybe.
|
|
75
|
+
def or(other)
|
|
76
|
+
coerced = coerce(other)
|
|
77
|
+
return self.class.yes if yes? || coerced.yes?
|
|
78
|
+
return self.class.no if no? && coerced.no?
|
|
79
|
+
|
|
80
|
+
self.class.maybe
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def ==(other)
|
|
84
|
+
other.is_a?(Trinary) && value == other.value
|
|
85
|
+
end
|
|
86
|
+
alias eql? ==
|
|
87
|
+
|
|
88
|
+
def hash
|
|
89
|
+
value.hash
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def to_s
|
|
93
|
+
value.to_s
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def inspect
|
|
97
|
+
"#<Rigor::Trinary #{value}>"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def coerce(other)
|
|
103
|
+
return other if other.is_a?(Trinary)
|
|
104
|
+
|
|
105
|
+
raise TypeError, "expected Rigor::Trinary, got #{other.class}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../trinary"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Type
|
|
7
|
+
# Immutable value object returned by `Rigor::Type#accepts(other, mode:)`.
|
|
8
|
+
# Carries the three-valued answer alongside the boundary mode the answer
|
|
9
|
+
# was computed under and an ordered list of textual reasons describing
|
|
10
|
+
# which rules fired.
|
|
11
|
+
#
|
|
12
|
+
# AcceptsResult is the dual of `SubtypeResult` (Slice 5+). Acceptance
|
|
13
|
+
# answers "is `other` passable to `self` at a method-parameter or
|
|
14
|
+
# assignment boundary?", consulting the gradual-typing rules in
|
|
15
|
+
# docs/type-specification/value-lattice.md when `mode` is `:gradual`,
|
|
16
|
+
# and the strict subset relation when `mode` is `:strict`. Phase 2c
|
|
17
|
+
# ships full `:gradual` semantics; `:strict` is reserved for later
|
|
18
|
+
# slices and currently raises ArgumentError.
|
|
19
|
+
#
|
|
20
|
+
# Reasons are stored as plain strings for now. Slice 5+ MAY upgrade
|
|
21
|
+
# them to structured records (rule id, supporting facts, dynamic
|
|
22
|
+
# provenance); callers MUST treat the reasons array as opaque except
|
|
23
|
+
# for human-readable logging.
|
|
24
|
+
#
|
|
25
|
+
# See docs/internal-spec/internal-type-api.md ("Result Value Objects").
|
|
26
|
+
class AcceptsResult
|
|
27
|
+
MODES = %i[gradual strict].freeze
|
|
28
|
+
private_constant :MODES
|
|
29
|
+
|
|
30
|
+
attr_reader :trinary, :mode, :reasons
|
|
31
|
+
|
|
32
|
+
# @param trinary [Rigor::Trinary]
|
|
33
|
+
# @param mode [Symbol] currently `:gradual` (default) or `:strict`.
|
|
34
|
+
# @param reasons [Array<String>, String, nil] textual reasons; a
|
|
35
|
+
# single string is wrapped, `nil` becomes an empty array.
|
|
36
|
+
def initialize(trinary, mode: :gradual, reasons: nil)
|
|
37
|
+
raise ArgumentError, "trinary must be Rigor::Trinary, got #{trinary.class}" unless trinary.is_a?(Trinary)
|
|
38
|
+
raise ArgumentError, "mode must be one of #{MODES.inspect}, got #{mode.inspect}" unless MODES.include?(mode)
|
|
39
|
+
|
|
40
|
+
@trinary = trinary
|
|
41
|
+
@mode = mode
|
|
42
|
+
@reasons = normalize_reasons(reasons).freeze
|
|
43
|
+
freeze
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class << self
|
|
47
|
+
def yes(mode: :gradual, reasons: nil)
|
|
48
|
+
new(Trinary.yes, mode: mode, reasons: reasons)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def no(mode: :gradual, reasons: nil)
|
|
52
|
+
new(Trinary.no, mode: mode, reasons: reasons)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def maybe(mode: :gradual, reasons: nil)
|
|
56
|
+
new(Trinary.maybe, mode: mode, reasons: reasons)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def yes?
|
|
61
|
+
trinary.yes?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def no?
|
|
65
|
+
trinary.no?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def maybe?
|
|
69
|
+
trinary.maybe?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns a new AcceptsResult whose reasons list is `self.reasons`
|
|
73
|
+
# with `reason` appended. Used by combinator-style routing in
|
|
74
|
+
# {Rigor::Inference::Acceptance} to thread context through nested
|
|
75
|
+
# acceptance checks without mutating any object.
|
|
76
|
+
def with_reason(reason)
|
|
77
|
+
return self if reason.nil? || reason.empty?
|
|
78
|
+
|
|
79
|
+
self.class.new(trinary, mode: mode, reasons: reasons + [reason])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def ==(other)
|
|
83
|
+
other.is_a?(AcceptsResult) &&
|
|
84
|
+
trinary == other.trinary &&
|
|
85
|
+
mode == other.mode &&
|
|
86
|
+
reasons == other.reasons
|
|
87
|
+
end
|
|
88
|
+
alias eql? ==
|
|
89
|
+
|
|
90
|
+
def hash
|
|
91
|
+
[AcceptsResult, trinary, mode, reasons].hash
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def inspect
|
|
95
|
+
"#<Rigor::Type::AcceptsResult #{trinary.inspect} mode=#{mode}>"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def normalize_reasons(reasons)
|
|
101
|
+
case reasons
|
|
102
|
+
when nil then []
|
|
103
|
+
when Array then reasons.dup
|
|
104
|
+
else [reasons.to_s]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../trinary"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Type
|
|
7
|
+
# The bottom of the value lattice: contains no values. The result type of
|
|
8
|
+
# expressions that cannot terminate normally. See
|
|
9
|
+
# docs/type-specification/special-types.md.
|
|
10
|
+
class Bot
|
|
11
|
+
class << self
|
|
12
|
+
def instance
|
|
13
|
+
@instance ||= new.freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private :new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def describe(_verbosity = :short)
|
|
20
|
+
"bot"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def erase_to_rbs
|
|
24
|
+
"bot"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def top
|
|
28
|
+
Trinary.no
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def bot
|
|
32
|
+
Trinary.yes
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def dynamic
|
|
36
|
+
Trinary.no
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def accepts(other, mode: :gradual)
|
|
40
|
+
Inference::Acceptance.accepts(self, other, mode: mode)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def ==(other)
|
|
44
|
+
other.is_a?(Bot)
|
|
45
|
+
end
|
|
46
|
+
alias eql? ==
|
|
47
|
+
|
|
48
|
+
def hash
|
|
49
|
+
Bot.hash
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def inspect
|
|
53
|
+
"#<Rigor::Type::Bot>"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "top"
|
|
4
|
+
require_relative "bot"
|
|
5
|
+
require_relative "dynamic"
|
|
6
|
+
require_relative "nominal"
|
|
7
|
+
require_relative "singleton"
|
|
8
|
+
require_relative "constant"
|
|
9
|
+
require_relative "tuple"
|
|
10
|
+
require_relative "hash_shape"
|
|
11
|
+
require_relative "union"
|
|
12
|
+
|
|
13
|
+
module Rigor
|
|
14
|
+
module Type
|
|
15
|
+
# Factory entry point that routes every public construction through the
|
|
16
|
+
# deterministic normalization rules. Production code paths MUST go
|
|
17
|
+
# through Rigor::Type::Combinator. Direct constructor calls are an
|
|
18
|
+
# internal escape hatch for tests and for combinator's own
|
|
19
|
+
# implementation.
|
|
20
|
+
#
|
|
21
|
+
# See docs/internal-spec/internal-type-api.md and
|
|
22
|
+
# docs/type-specification/normalization.md.
|
|
23
|
+
module Combinator
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def top
|
|
27
|
+
Top.instance
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def bot
|
|
31
|
+
Bot.instance
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def untyped
|
|
35
|
+
@untyped ||= Dynamic.new(top)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Wraps the static facet in a Dynamic[T] carrier. Idempotent on the
|
|
39
|
+
# static facet so Dynamic[Dynamic[T]] collapses to Dynamic[T] per the
|
|
40
|
+
# value-lattice algebra.
|
|
41
|
+
def dynamic(static_facet)
|
|
42
|
+
return untyped if static_facet.equal?(top)
|
|
43
|
+
|
|
44
|
+
facet = static_facet.is_a?(Dynamic) ? static_facet.static_facet : static_facet
|
|
45
|
+
return untyped if facet.is_a?(Top)
|
|
46
|
+
|
|
47
|
+
Dynamic.new(facet)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Constructs a Nominal type. Slice 4 phase 2d accepts an optional
|
|
51
|
+
# `type_args:` array, an ordered list of Rigor::Type values that
|
|
52
|
+
# carry the receiver's generic instantiation (`Array[Integer]` is
|
|
53
|
+
# `Nominal["Array", [Nominal["Integer"]]]`). Omitting the keyword
|
|
54
|
+
# produces the raw form `Nominal["Array"]`, which is structurally
|
|
55
|
+
# distinct from any applied form.
|
|
56
|
+
def nominal_of(class_name_or_object, type_args: [])
|
|
57
|
+
Nominal.new(resolve_class_name(class_name_or_object), type_args)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def singleton_of(class_name_or_object)
|
|
61
|
+
Singleton.new(resolve_class_name(class_name_or_object))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def constant_of(value)
|
|
65
|
+
Constant.new(value)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Constructs a heterogeneous, fixed-arity Tuple from positional
|
|
69
|
+
# element types. `tuple_of()` produces the empty tuple `Tuple[]`,
|
|
70
|
+
# which is structurally distinct from the raw `Nominal[Array]`.
|
|
71
|
+
def tuple_of(*elements)
|
|
72
|
+
Tuple.new(elements)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Constructs a HashShape from an ordered (Symbol|String) -> type
|
|
76
|
+
# map. The argument is duped and frozen by the carrier; callers
|
|
77
|
+
# MUST NOT rely on later mutation.
|
|
78
|
+
def hash_shape_of(pairs = nil, **options)
|
|
79
|
+
if pairs.nil?
|
|
80
|
+
pairs = options
|
|
81
|
+
options = {}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
HashShape.new(pairs, **options)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Normalized union. Flattens nested Unions, deduplicates structurally
|
|
88
|
+
# equal members, drops Bot, and collapses 0/1-member results.
|
|
89
|
+
def union(*types)
|
|
90
|
+
collapse_union(normalized_union_members(types))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
class << self
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def normalized_union_members(types)
|
|
97
|
+
flattened = []
|
|
98
|
+
types.each { |t| flatten_into(flattened, t) }
|
|
99
|
+
flattened.reject! { |t| t.is_a?(Bot) }
|
|
100
|
+
|
|
101
|
+
return [top] if flattened.any?(Top)
|
|
102
|
+
|
|
103
|
+
unique_members(flattened)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def unique_members(types)
|
|
107
|
+
types.each_with_object([]) do |type, unique|
|
|
108
|
+
unique << type unless unique.any? { |member| member == type }
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def collapse_union(types)
|
|
113
|
+
case types.size
|
|
114
|
+
when 0 then bot
|
|
115
|
+
when 1 then types.first
|
|
116
|
+
else Union.new(sort_members(types))
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def resolve_class_name(class_name_or_object)
|
|
121
|
+
name =
|
|
122
|
+
case class_name_or_object
|
|
123
|
+
when Module then class_name_or_object.name
|
|
124
|
+
when String then class_name_or_object
|
|
125
|
+
else
|
|
126
|
+
raise ArgumentError, "expected Class/Module or String, got #{class_name_or_object.class}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
raise ArgumentError, "anonymous class has no name" if name.nil? || name.empty?
|
|
130
|
+
|
|
131
|
+
name
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def flatten_into(acc, type)
|
|
135
|
+
if type.is_a?(Union)
|
|
136
|
+
type.members.each { |m| flatten_into(acc, m) }
|
|
137
|
+
else
|
|
138
|
+
acc << type
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def sort_members(members)
|
|
143
|
+
members.sort_by { |m| m.describe(:short) }
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../trinary"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Type
|
|
7
|
+
# A literal carrier under ADR-3 OQ1 Option C (Hybrid). Wraps a Ruby
|
|
8
|
+
# literal value of one of the supported immutable-ish classes. Compound
|
|
9
|
+
# literal shapes (Tuple, HashShape, Record) get dedicated classes in
|
|
10
|
+
# later slices; Range is carried only when both static endpoints are
|
|
11
|
+
# known enough for tuple slicing.
|
|
12
|
+
#
|
|
13
|
+
# See docs/adr/4-type-inference-engine.md for the tentative answer to
|
|
14
|
+
# the open question and docs/type-specification/rigor-extensions.md for
|
|
15
|
+
# the refinement neighbourhood this carrier lives in.
|
|
16
|
+
class Constant
|
|
17
|
+
SCALAR_CLASSES = [
|
|
18
|
+
Integer,
|
|
19
|
+
Float,
|
|
20
|
+
String,
|
|
21
|
+
Symbol,
|
|
22
|
+
Range,
|
|
23
|
+
Rational,
|
|
24
|
+
Complex,
|
|
25
|
+
TrueClass,
|
|
26
|
+
FalseClass,
|
|
27
|
+
NilClass
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
RBS_LITERAL_CLASSES = {
|
|
31
|
+
TrueClass => "true",
|
|
32
|
+
FalseClass => "false",
|
|
33
|
+
NilClass => "nil"
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
36
|
+
attr_reader :value
|
|
37
|
+
|
|
38
|
+
def initialize(value)
|
|
39
|
+
unless SCALAR_CLASSES.any? { |klass| value.is_a?(klass) }
|
|
40
|
+
raise ArgumentError, "Rigor::Type::Constant only carries scalar literals; got #{value.class}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@value = value.is_a?(String) ? value.dup.freeze : value
|
|
44
|
+
freeze
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def describe(_verbosity = :short)
|
|
48
|
+
value.inspect
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def erase_to_rbs
|
|
52
|
+
case value
|
|
53
|
+
when true then "true"
|
|
54
|
+
when false then "false"
|
|
55
|
+
when nil then "nil"
|
|
56
|
+
else value.class.name
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def top
|
|
61
|
+
Trinary.no
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def bot
|
|
65
|
+
Trinary.no
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def dynamic
|
|
69
|
+
Trinary.no
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def accepts(other, mode: :gradual)
|
|
73
|
+
Inference::Acceptance.accepts(self, other, mode: mode)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def ==(other)
|
|
77
|
+
other.is_a?(Constant) && value.class == other.value.class && value == other.value
|
|
78
|
+
end
|
|
79
|
+
alias eql? ==
|
|
80
|
+
|
|
81
|
+
def hash
|
|
82
|
+
[Constant, value.class, value].hash
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def inspect
|
|
86
|
+
"#<Rigor::Type::Constant #{describe(:short)}>"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../trinary"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Type
|
|
7
|
+
# The dynamic-origin wrapper: marks values whose type came from an
|
|
8
|
+
# unchecked source. Carries a static facet that records the analyzer's
|
|
9
|
+
# best static knowledge. See docs/type-specification/value-lattice.md
|
|
10
|
+
# for the algebra and docs/type-specification/special-types.md for the
|
|
11
|
+
# untyped/Dynamic[T] relationship.
|
|
12
|
+
#
|
|
13
|
+
# Construct via Rigor::Type::Combinator.dynamic(static_facet).
|
|
14
|
+
class Dynamic
|
|
15
|
+
attr_reader :static_facet
|
|
16
|
+
|
|
17
|
+
def initialize(static_facet)
|
|
18
|
+
@static_facet = static_facet
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def describe(verbosity = :short)
|
|
23
|
+
"Dynamic[#{static_facet.describe(verbosity)}]"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def erase_to_rbs
|
|
27
|
+
"untyped"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def top
|
|
31
|
+
Trinary.no
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def bot
|
|
35
|
+
Trinary.no
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def dynamic
|
|
39
|
+
Trinary.yes
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def accepts(other, mode: :gradual)
|
|
43
|
+
Inference::Acceptance.accepts(self, other, mode: mode)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ==(other)
|
|
47
|
+
other.is_a?(Dynamic) && static_facet == other.static_facet
|
|
48
|
+
end
|
|
49
|
+
alias eql? ==
|
|
50
|
+
|
|
51
|
+
def hash
|
|
52
|
+
[Dynamic, static_facet].hash
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def inspect
|
|
56
|
+
"#<Rigor::Type::Dynamic #{describe(:short)}>"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|