Module: Rigor::RbsExtended
- Defined in:
- lib/rigor/rbs_extended.rb
Overview
Slice 7 phase 15 — first-preview reader for the ‘RBS::Extended` annotation surface described in `docs/type-specification/rbs-extended.md`.
This module reads ‘%a<payload>` annotations off RBS method definitions and returns well-typed effect objects the inference engine can consume. v0.0.2 recognises:
-
‘rigor:v1:predicate-if-true <target> is <ClassName>`
-
‘rigor:v1:predicate-if-false <target> is <ClassName>`
-
‘rigor:v1:assert <target> is <ClassName>`
-
‘rigor:v1:assert-if-true <target> is <ClassName>`
-
‘rigor:v1:assert-if-false <target> is <ClassName>`
‘predicate-if-*` fires when the call is used as an `if` / `unless` condition; `assert` fires unconditionally at the call’s post-scope; ‘assert-if-true` / `assert-if-false` fire at the post-scope only when the call’s return value can be observed as truthy / falsey (currently: when the call is the predicate of a subsequent ‘if` / `unless`). Other directives in the spec (`param`, `return`, `conforms-to`, negation `~T`, `target: self` narrowing, …) remain on the v0.0.x roadmap. Annotations whose key is in the `rigor:v1:` namespace but whose directive is unrecognised are silently ignored at first-preview quality (a future slice MAY surface them as diagnostics-on-Rigor-itself per the spec’s “unsupported metadata” guidance).
The parser is minimal: it accepts a strict shape ‘<target> is <ClassName>` where `<target>` is a Ruby identifier (parameter name) or `self`, and `<ClassName>` is a single non-namespaced class identifier or a `::Foo::Bar` style constant path. Negative refinements (`~T`), intersections, and unions are deferred to the next iteration.
Defined Under Namespace
Classes: AssertEffect, PredicateEffect
Constant Summary collapse
- DIRECTIVE_PREFIX =
rubocop:disable Metrics/ModuleLength
"rigor:v1:"
Class Method Summary collapse
- .parse_assert_annotation(string) ⇒ Object
- .parse_predicate_annotation(string) ⇒ Object
- .parse_return_type_override(string) ⇒ Object
-
.read_assert_effects(method_def) ⇒ Object
Reads RBS::Extended assertion effects (‘assert`, `assert-if-true`, `assert-if-false`) off `RBS::Definition::Method#annotations`.
-
.read_predicate_effects(method_def) ⇒ Object
Reads RBS::Extended predicate effects off ‘RBS::Definition::Method#annotations`.
-
.read_return_type_override(method_def) ⇒ Object
Reads the ‘rigor:v1:return: <kebab-name>` directive off `RBS::Definition::Method#annotations`.
Class Method Details
.parse_assert_annotation(string) ⇒ Object
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/rigor/rbs_extended.rb', line 174 def parse_assert_annotation(string) match = ASSERT_DIRECTIVE_PATTERN.match(string) return nil if match.nil? directive = match[:directive].to_s condition = ASSERT_CONDITIONS[directive] return nil if condition.nil? target = match[:target].to_s class_name = match[:class_name].to_s.sub(/\A::/, "") target_kind = target == "self" ? :self : :parameter target_name = target == "self" ? :self : target.to_sym AssertEffect.new( condition: condition, target_kind: target_kind, target_name: target_name, class_name: class_name, negative: match[:negation].to_s == "~" ) end |
.parse_predicate_annotation(string) ⇒ Object
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/rigor/rbs_extended.rb', line 116 def parse_predicate_annotation(string) match = PREDICATE_DIRECTIVE_PATTERN.match(string) return nil if match.nil? directive = match[:directive].to_s target = match[:target].to_s class_name = match[:class_name].to_s.sub(/\A::/, "") edge = directive == "predicate-if-true" ? :truthy_only : :falsey_only target_kind = target == "self" ? :self : :parameter target_name = target == "self" ? :self : target.to_sym PredicateEffect.new( edge: edge, target_kind: target_kind, target_name: target_name, class_name: class_name, negative: match[:negation].to_s == "~" ) end |
.parse_return_type_override(string) ⇒ Object
251 252 253 254 255 256 |
# File 'lib/rigor/rbs_extended.rb', line 251 def parse_return_type_override(string) match = RETURN_DIRECTIVE_PATTERN.match(string) return nil if match.nil? Builtins::ImportedRefinements.lookup(match[:refinement]) end |
.read_assert_effects(method_def) ⇒ Object
Reads RBS::Extended assertion effects (‘assert`, `assert-if-true`, `assert-if-false`) off `RBS::Definition::Method#annotations`. Returns an empty array when no recognised assertion directives are attached to the method.
140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/rigor/rbs_extended.rb', line 140 def read_assert_effects(method_def) return [] if method_def.nil? annotations = method_def.annotations return [] if annotations.nil? || annotations.empty? effects = [] annotations.each do |annotation| effect = parse_assert_annotation(annotation.string) effects << effect if effect end effects.uniq end |
.read_predicate_effects(method_def) ⇒ Object
Reads RBS::Extended predicate effects off ‘RBS::Definition::Method#annotations`. Returns the effects in source order; duplicates and unrecognised `rigor:v1:` directives are dropped. Returns an empty array (NEVER `nil`) for a method with no recognised annotations so callers can iterate unconditionally.
89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/rigor/rbs_extended.rb', line 89 def read_predicate_effects(method_def) return [] if method_def.nil? annotations = method_def.annotations return [] if annotations.nil? || annotations.empty? effects = [] annotations.each do |annotation| effect = parse_predicate_annotation(annotation.string) effects << effect if effect end effects.uniq end |
.read_return_type_override(method_def) ⇒ Object
Reads the ‘rigor:v1:return: <kebab-name>` directive off `RBS::Definition::Method#annotations`. The directive overrides a method’s RBS-declared return type with one of the imported-built-in refinements registered in ‘Rigor::Builtins::ImportedRefinements`. The override is the primary integration path for refinement carriers (`non-empty-string`, `positive-int`, `non-empty-array`, …) in v0.0 — annotation-driven, opt-in per method, and never silently rewrites a hand-authored RBS signature outside the annotation.
Example annotation in an RBS file:
class User
%a{rigor:v1:return: non-empty-string}
def name: () -> String
end
The RBS-declared return is ‘String`. The override tightens it to `non-empty-string` (i.e. `Difference[String, “”]`) for callers; RBS erasure of the tightened return goes back to `String` so the round-trip to ordinary RBS is unaffected.
Returns the resolved ‘Rigor::Type` value, or `nil` when:
-
the method has no annotations,
-
none of the annotations match the ‘rigor:v1:return:` directive,
-
the directive’s payload names a refinement not registered in ‘Rigor::Builtins::ImportedRefinements` (the analyzer prefers a silent miss over crashing on a typo; future slices MAY surface the miss as a `:warning` self-diagnostic).
228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/rigor/rbs_extended.rb', line 228 def read_return_type_override(method_def) return nil if method_def.nil? annotations = method_def.annotations return nil if annotations.nil? || annotations.empty? annotations.each do |annotation| type = parse_return_type_override(annotation.string) return type if type end nil end |