Module: Rigor::RbsExtended
- Defined in:
- lib/rigor/rbs_extended.rb,
lib/rigor/rbs_extended/reporter.rb,
lib/rigor/rbs_extended/hkt_directives.rb,
lib/rigor/rbs_extended/envelope_scanner.rb,
lib/rigor/rbs_extended/conformance_checker.rb,
sig/rigor/rbs_extended.rbs
Overview
Reader for the RBS::Extended annotation surface described in docs/type-specification/rbs-extended.md.
Reads %a{rigor:v1:<directive> <payload>} annotations off RBS method definitions and returns well-typed
effect objects the inference engine can consume. Implemented directives:
rigor:v1:predicate-if-true <target> is <ClassName|refinement>rigor:v1:predicate-if-false <target> is <ClassName|refinement>rigor:v1:assert <target> is <ClassName|refinement>rigor:v1:assert-if-true <target> is <ClassName|refinement>rigor:v1:assert-if-false <target> is <ClassName|refinement>rigor:v1:param <name> <type-expr>— per-call param narrowingrigor:v1:return <type-expr>— per-call return overriderigor:v1:conforms-to <InterfaceName>— structural conformance
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. Negation (~T) is supported for both class-name and
refinement right-hand sides. Parameterised refinements (non-empty-array[T]) are also recognised. Annotations
whose directive is unrecognised are silently ignored per the spec's "unsupported metadata" guidance.
Defined Under Namespace
Modules: ConformanceChecker, EnvelopeScanner, HktDirectives Classes: AssertEffect, EffectAnnotation, ParamOverride, PredicateEffect, Reporter
Constant Summary collapse
- DIRECTIVE_PREFIX =
rubocop:disable Metrics/ModuleLength
"rigor:v1:"- RBS_EXTENDED_PROVENANCE =
The shared FlowContribution::Provenance for every bundle this module produces.
source_family: :rbs_extendedso consumers (today the documentation surface; v0.1.0 the plugin contribution merger) can attribute facts back to the RBS::Extended layer. FlowContribution::Provenance.new( source_family: :rbs_extended, plugin_id: nil, node: nil, descriptor: nil ).freeze
Class Method Summary collapse
-
.annotation_line(annotation, location, buffer, path) ⇒ Object
The line a reader can open.
- .annotation_location(annotation) ⇒ Object
- .build_effect_envelope(annotation, parsed, owner_key:, source:) ⇒ Object
- .build_flow_contribution(predicate_effects, assert_effects, return_override) ⇒ Object
- .build_pure_envelope(annotation, owner_key:, source:) ⇒ Object
- .nilable_slot(facts) ⇒ Object
-
.param_type_override_map(method_def, environment: nil) ⇒ Object
Convenience reader for call sites that want to look up a single override by parameter name.
-
.parse_app_payload(payload, name_scope: nil, reporter: nil, source_location: nil, hkt_registry: nil) ⇒ Object
ADR-20 slice 2d.
- .parse_assert_annotation(string, name_scope: nil, reporter: nil, source_location: nil) ⇒ Object
-
.parse_conforms_to_annotation(string) ⇒ Object
Returns the interface name (leading
::stripped) for arigor:v1:conforms-to <Interface>annotation, ornilwhen the string is not a conforms-to directive (so callers can walk an annotation list without pre-filtering). -
.parse_effect_annotation(string, registry: nil, reporter: nil, source_location: nil) ⇒ Object
Reads one annotation string as an effect-envelope payload.
- .parse_param_annotation(string, name_scope: nil, reporter: nil, source_location: nil) ⇒ Object
- .parse_predicate_annotation(string, name_scope: nil, reporter: nil, source_location: nil) ⇒ Object
- .parse_return_type_override(string, name_scope: nil, reporter: nil, source_location: nil, hkt_registry: nil) ⇒ Object
-
.pure_annotation?(string) ⇒ Boolean
Whether
stringis the bare%a{pure}annotation. -
.read_assert_effects(method_def, environment: nil) ⇒ Object
Reads RBS::Extended assertion effects (
assert,assert-if-true,assert-if-false) offRBS::Definition::Method#annotations. -
.read_effect_envelope(annotations, owner_key:, source: :effect_annotation, registry: nil, reporter: nil) ⇒ Object
Reads the effect envelope off an annotation list —
RBS::Definition::Method#annotations, or the#annotationsof anRBS::AST::Members::MethodDefinition/ class declaration, which carry the same(string, location)shape. -
.read_flow_contribution(method_def, environment: nil) ⇒ Object
Rolls up every recognised RBS::Extended directive on
method_definto a single FlowContribution with the canonical FlowContribution::Fact payload (see ADR-7 § "Slice 4-A"):. -
.read_param_type_overrides(method_def, environment: nil) ⇒ Object
Reads every
rigor:v1:param: <name> <refinement>directive offRBS::Definition::Method#annotationsand returns the resolvedParamOverridelist. -
.read_predicate_effects(method_def, environment: nil) ⇒ Object
Reads RBS::Extended predicate effects off
RBS::Definition::Method#annotations. -
.read_return_type_override(method_def, environment: nil) ⇒ Object
Reads the
rigor:v1:return: <kebab-name>directive offRBS::Definition::Method#annotations. -
.record_unresolved(reporter, payload, source_location) ⇒ Object
ADR-13 slice 3b — guards every reporter call so the in-RbsExtended-module call sites can record events uniformly without nil-checking each time.
-
.relative_annotation_path(name) ⇒ Object
The buffer-name → readable-path rule lives with the walk that produces the buffers (Effects::SignatureSources), so a
virtual:name is stripped identically wherever it surfaces. -
.render_annotation_location(annotation) ⇒ Object
path:linefor the annotation, project-relative when it sits under the working directory, so a diagnostic message namessig/foo.rbs:12rather than an absolute path. - .resolve_app_arg(class_name, name_scope: nil) ⇒ Object
-
.resolve_directive_rhs(match, name_scope: nil, reporter: nil, source_location: nil) ⇒ Object
Resolves the
class_name/refinementalternation in the assert / predicate directive patterns. - .target_fields(target) ⇒ Object
Instance Method Summary collapse
- #self?.param_type_override_map ⇒ Hash[Symbol, Type::t]
- #self?.parse_assert_annotation ⇒ AssertEffect?
- #self?.parse_conforms_to_annotation ⇒ String?
- #self?.parse_param_annotation ⇒ ParamOverride?
- #self?.parse_predicate_annotation ⇒ PredicateEffect?
- #self?.parse_return_type_override ⇒ Type::t?
-
#self?.pure_annotation? ⇒ Boolean
ADR-103 #383 — the effect-envelope reader.
- #self?.read_assert_effects ⇒ Array[AssertEffect]
- #self?.read_effect_envelope ⇒ Object?
- #self?.read_flow_contribution ⇒ Object?
- #self?.read_param_type_overrides ⇒ Array[ParamOverride]
- #self?.read_predicate_effects ⇒ Array[PredicateEffect]
- #self?.read_return_type_override ⇒ Type::t?
Class Method Details
.annotation_line(annotation, location, buffer, path) ⇒ Object
The line a reader can open. For a real .rbs that is the parser's own answer; for the
virtual:rbs-inline:…rb buffer the writer produced, the annotation is found again in the Ruby
source by its own spelling.
796 797 798 799 800 801 802 803 804 |
# File 'lib/rigor/rbs_extended.rb', line 796 def annotation_line(annotation, location, buffer, path) line = location.respond_to?(:start_line) ? location.start_line : 1 content = buffer.respond_to?(:content) ? buffer.content : nil return line if content.nil? Effects::InlineAnchor.ruby_line( path: path, buffer: content, buffer_line: line, spelling: "%a{#{annotation.string}}" ) end |
.annotation_location(annotation) ⇒ Object
767 768 769 |
# File 'lib/rigor/rbs_extended.rb', line 767 def annotation_location(annotation) annotation.respond_to?(:location) ? annotation.location : nil end |
.build_effect_envelope(annotation, parsed, owner_key:, source:) ⇒ Object
755 756 757 758 759 760 761 762 763 764 765 |
# File 'lib/rigor/rbs_extended.rb', line 755 def build_effect_envelope(annotation, parsed, owner_key:, source:) Effects::Envelope.build( owner_key: owner_key, bound: parsed.bound, source: source, location: render_annotation_location(annotation), spelling: "%a{#{annotation.string}}", unknown_labels: parsed.unknown_labels, declared_labels: parsed.labels ) end |
.build_flow_contribution(predicate_effects, assert_effects, return_override) ⇒ Object
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 |
# File 'lib/rigor/rbs_extended.rb', line 603 def build_flow_contribution(predicate_effects, assert_effects, return_override) truthy = predicate_effects.select(&:truthy_only?).map(&:to_fact) falsey = predicate_effects.select(&:falsey_only?).map(&:to_fact) post_return = [] assert_effects.each do |effect| case effect.condition when :if_truthy_return then truthy << effect.to_fact when :if_falsey_return then falsey << effect.to_fact else post_return << effect.to_fact end end FlowContribution.new( return_type: return_override, truthy_facts: nilable_slot(truthy), falsey_facts: nilable_slot(falsey), post_return_facts: nilable_slot(post_return), provenance: RBS_EXTENDED_PROVENANCE ) end |
.build_pure_envelope(annotation, owner_key:, source:) ⇒ Object
745 746 747 748 749 750 751 752 753 |
# File 'lib/rigor/rbs_extended.rb', line 745 def build_pure_envelope(annotation, owner_key:, source:) Effects::Envelope.build( owner_key: owner_key, bound: Effects::LabelSet::EMPTY, source: source == :class_annotation ? :class_annotation : :pure_annotation, location: render_annotation_location(annotation), spelling: "%a{#{annotation.string}}" ) end |
.nilable_slot(facts) ⇒ Object
625 626 627 |
# File 'lib/rigor/rbs_extended.rb', line 625 def nilable_slot(facts) facts.empty? ? nil : facts end |
.param_type_override_map(method_def, environment: nil) ⇒ Object
Convenience reader for call sites that want to look up a single override by parameter name. Returns a frozen Hash<Symbol, Rigor::Type>; missing keys mean "use the RBS-declared type". Callers MUST treat the hash as read-only.
493 494 495 496 497 |
# File 'lib/rigor/rbs_extended.rb', line 493 def param_type_override_map(method_def, environment: nil) read_param_type_overrides(method_def, environment: environment) .to_h { |o| [o.param_name, o.type] } .freeze end |
.parse_app_payload(payload, name_scope: nil, reporter: nil, source_location: nil, hkt_registry: nil) ⇒ Object
ADR-20 slice 2d. Parses App[<uri>, <ClassName>, ...] syntax into a Rigor::Type::App. When
hkt_registry is supplied and the URI is registered with a body_tree, the App is reduced eagerly via
Inference::HktRegistry#reduce so call sites observe the unfolded form (e.g.
Union[nil, true, false, ..., Array[App[json::value, String]], Hash[String, App[json::value, String]]])
rather than the opaque carrier. When the registry is absent or the URI is unregistered, the carrier with
its registry-supplied bound (or untyped as a last-resort fallback) is returned as-is.
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 |
# File 'lib/rigor/rbs_extended.rb', line 414 def parse_app_payload(payload, name_scope: nil, reporter: nil, source_location: nil, hkt_registry: nil) match = APP_PAYLOAD_PATTERN.match(payload) return nil if match.nil? uri = match[:uri].to_sym arg_classes = match[:args].split(",").map(&:strip) args = arg_classes.map { |name| resolve_app_arg(name, name_scope: name_scope) } if args.any?(&:nil?) record_unresolved(reporter, "App payload `#{payload}`: unresolved arg class name", source_location) return nil end registration = hkt_registry&.registration(uri) bound = registration&.bound || Type::Combinator.untyped app = Type::App.new(uri, args, bound: bound) return app if hkt_registry.nil? || !hkt_registry.defined?(uri) reduced = hkt_registry.reduce(app) reduced || app end |
.parse_assert_annotation(string, name_scope: nil, reporter: nil, source_location: nil) ⇒ Object
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/rigor/rbs_extended.rb', line 231 def parse_assert_annotation(string, name_scope: nil, reporter: nil, source_location: nil) 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 target_kind, target_name = target_fields(target) class_name, refinement_type, negative = resolve_directive_rhs( match, name_scope: name_scope, reporter: reporter, source_location: source_location ) if class_name.nil? && refinement_type.nil? record_unresolved(reporter, string, source_location) return nil end AssertEffect.new( condition: condition, target_kind: target_kind, target_name: target_name, class_name: class_name, negative: negative, refinement_type: refinement_type ) end |
.parse_conforms_to_annotation(string) ⇒ Object
Returns the interface name (leading :: stripped) for a rigor:v1:conforms-to <Interface> annotation, or
nil when the string is not a conforms-to directive (so callers can walk an annotation list without
pre-filtering). The name is returned verbatim otherwise — namespace resolution happens at the loader
boundary when the interface is built.
555 556 557 558 559 560 561 562 |
# File 'lib/rigor/rbs_extended.rb', line 555 def parse_conforms_to_annotation(string) return nil if string.nil? match = CONFORMS_TO_DIRECTIVE_PATTERN.match(string) return nil if match.nil? match[:interface].to_s.sub(/\A::/, "") end |
.parse_effect_annotation(string, registry: nil, reporter: nil, source_location: nil) ⇒ Object
Reads one annotation string as an effect-envelope payload.
Returns nil when the string is not a rigor:v1:effect directive at all (so a caller can walk
an annotation list without pre-filtering). Otherwise an EffectAnnotation:
- well-formed and fully recognised → the declared bound;
- malformed (empty list, a token outside the label grammar) → Effects::LabelSet::TOP, and a
record_unresolvedevent onreporter; - well-formed but carrying a spelling
registrydoes not know → Effects::LabelSet::TOP and the unrecognised spellings, because an unknown label makes the WHOLE tag ⊤ rather than the subset that happened to parse. Narrowing to the recognised subset would turn a typo into findings on correct code; widening suppresses them, which is the direction the false-positive budget runs (ADR-5).
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 |
# File 'lib/rigor/rbs_extended.rb', line 682 def parse_effect_annotation(string, registry: nil, reporter: nil, source_location: nil) match = EFFECT_DIRECTIVE_PATTERN.match(string.to_s) return nil if match.nil? tokens = match[:labels].to_s.split(",", -1).map(&:strip) if tokens.empty? || tokens.any? { |token| !Effects::Label.valid?(token) } record_unresolved(reporter, string, source_location) return EffectAnnotation.new( bound: Effects::LabelSet::TOP, labels: NO_EFFECT_LABELS, unknown_labels: NO_EFFECT_LABELS, malformed: true ) end tokens = tokens.freeze unknown = registry.nil? ? [] : tokens.reject { |token| registry.known?(token) } EffectAnnotation.new( bound: unknown.empty? ? Effects::LabelSet.new(tokens) : Effects::LabelSet::TOP, labels: tokens, unknown_labels: unknown.uniq.sort.freeze, malformed: false ) end |
.parse_param_annotation(string, name_scope: nil, reporter: nil, source_location: nil) ⇒ Object
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 |
# File 'lib/rigor/rbs_extended.rb', line 515 def parse_param_annotation(string, name_scope: nil, reporter: nil, source_location: nil) match = PARAM_DIRECTIVE_PATTERN.match(string) return nil if match.nil? type = Builtins::ImportedRefinements.parse( match[:payload], name_scope: name_scope, reporter: reporter, source_location: source_location ) if type.nil? record_unresolved(reporter, string, source_location) return nil end ParamOverride.new(param_name: match[:param].to_sym, type: type) end |
.parse_predicate_annotation(string, name_scope: nil, reporter: nil, source_location: nil) ⇒ Object
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/rigor/rbs_extended.rb', line 151 def parse_predicate_annotation(string, name_scope: nil, reporter: nil, source_location: nil) match = PREDICATE_DIRECTIVE_PATTERN.match(string) return nil if match.nil? directive = match[:directive].to_s target = match[:target].to_s edge = directive == "predicate-if-true" ? :truthy_only : :falsey_only target_kind, target_name = target_fields(target) class_name, refinement_type, negative = resolve_directive_rhs( match, name_scope: name_scope, reporter: reporter, source_location: source_location ) if class_name.nil? && refinement_type.nil? record_unresolved(reporter, string, source_location) return nil end PredicateEffect.new( edge: edge, target_kind: target_kind, target_name: target_name, class_name: class_name, negative: negative, refinement_type: refinement_type ) end |
.parse_return_type_override(string, name_scope: nil, reporter: nil, source_location: nil, hkt_registry: nil) ⇒ Object
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
# File 'lib/rigor/rbs_extended.rb', line 385 def parse_return_type_override(string, name_scope: nil, reporter: nil, source_location: nil, hkt_registry: nil) match = RETURN_DIRECTIVE_PATTERN.match(string) return nil if match.nil? app_type = parse_app_payload( match[:payload], name_scope: name_scope, reporter: reporter, source_location: source_location, hkt_registry: hkt_registry ) return app_type if app_type type = Builtins::ImportedRefinements.parse( match[:payload], name_scope: name_scope, reporter: reporter, source_location: source_location ) record_unresolved(reporter, string, source_location) if type.nil? type end |
.pure_annotation?(string) ⇒ Boolean
Whether string is the bare %a{pure} annotation.
665 666 667 |
# File 'lib/rigor/rbs_extended.rb', line 665 def pure_annotation?(string) !string.nil? && PURE_ANNOTATION_PATTERN.match?(string) end |
.read_assert_effects(method_def, environment: nil) ⇒ 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.
See read_predicate_effects for the environment: keyword contract.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/rigor/rbs_extended.rb', line 185 def read_assert_effects(method_def, environment: nil) return [] if method_def.nil? annotations = method_def.annotations return [] if annotations.nil? || annotations.empty? name_scope = environment&.name_scope reporter = environment&.rbs_extended_reporter effects = [] annotations.each do |annotation| effect = parse_assert_annotation( annotation.string, name_scope: name_scope, reporter: reporter, source_location: annotation.location ) effects << effect if effect end effects.uniq end |
.read_effect_envelope(annotations, owner_key:, source: :effect_annotation, registry: nil, reporter: nil) ⇒ Object
Reads the effect envelope off an annotation list — RBS::Definition::Method#annotations, or the
#annotations of an RBS::AST::Members::MethodDefinition / class declaration, which carry the
same (string, location) shape.
Precedence, per the spec: %a{pure} and %a{rigor:v1:effect …} on ONE declaration are
contradictory and pure wins; the contradiction is recorded on reporter (the existing
RBS::Extended conflict channel) rather than silently resolved. Returns nil when the list
carries neither spelling.
715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 |
# File 'lib/rigor/rbs_extended.rb', line 715 def read_effect_envelope(annotations, owner_key:, source: :effect_annotation, registry: nil, reporter: nil) return nil if annotations.nil? || annotations.empty? pure = annotations.find { |annotation| pure_annotation?(annotation.string) } labelled = nil parsed = nil annotations.each do |annotation| result = parse_effect_annotation( annotation.string, registry: registry, reporter: reporter, source_location: annotation_location(annotation) ) next if result.nil? labelled = annotation parsed = result break end return nil if pure.nil? && labelled.nil? if pure && labelled record_unresolved( reporter, "`%a{pure}` and `%a{#{labelled.string}}` on one declaration are contradictory; `pure` wins", annotation_location(pure) ) end return build_pure_envelope(pure, owner_key: owner_key, source: source) if pure build_effect_envelope(labelled, parsed, owner_key: owner_key, source: source) end |
.read_flow_contribution(method_def, environment: nil) ⇒ Object
Rolls up every recognised RBS::Extended directive on method_def into a single FlowContribution
with the canonical FlowContribution::Fact payload (see ADR-7 § "Slice 4-A"):
predicate-if-true→truthy_factspredicate-if-false→falsey_factsassert→post_return_factsassert-if-true→truthy_factsassert-if-false→falsey_factsreturn:override →return_type(Rigor::Type)
Param overrides are intentionally NOT included — they refine the call's signature contract rather than its flow facts and do not fit ADR-2 § "Flow Contribution Bundle" slot semantics. Callers that care about parameter contracts keep using read_param_type_overrides / param_type_override_map.
Returns nil when the method carries no recognised contribution directives (callers can skip the merge
step without iterating an empty bundle).
See read_predicate_effects for the environment: keyword contract.
592 593 594 595 596 597 598 599 600 601 |
# File 'lib/rigor/rbs_extended.rb', line 592 def read_flow_contribution(method_def, environment: nil) return nil if method_def.nil? predicate_effects = read_predicate_effects(method_def, environment: environment) assert_effects = read_assert_effects(method_def, environment: environment) return_override = read_return_type_override(method_def, environment: environment) return nil if predicate_effects.empty? && assert_effects.empty? && return_override.nil? build_flow_contribution(predicate_effects, assert_effects, return_override) end |
.read_param_type_overrides(method_def, environment: nil) ⇒ Object
Reads every rigor:v1:param: <name> <refinement> directive off RBS::Definition::Method#annotations and
returns the resolved ParamOverride list. Annotations the parser cannot resolve (typo, unknown refinement,
no param: directive at all) are silently dropped — the call site keeps the RBS-declared parameter type
for those parameters. The reader accepts a nil method definition so call sites can pass through optional
method lookups without a guard.
Example annotation in an RBS file:
class Slug
%a{rigor:v1:param: id is non-empty-string}
def normalise: (::String id) -> String
end
The RBS-declared type of id is String. The override tightens it to non-empty-string for
argument-check purposes; passing a too-wide Nominal[String] argument is flagged as an argument-type
mismatch at the call site.
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 |
# File 'lib/rigor/rbs_extended.rb', line 471 def read_param_type_overrides(method_def, environment: nil) return [] if method_def.nil? annotations = method_def.annotations return [] if annotations.nil? || annotations.empty? name_scope = environment&.name_scope reporter = environment&.rbs_extended_reporter annotations.filter_map do |annotation| parse_param_annotation( annotation.string, name_scope: name_scope, reporter: reporter, source_location: annotation.location ) end end |
.read_predicate_effects(method_def, environment: nil) ⇒ 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.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/rigor/rbs_extended.rb', line 107 def read_predicate_effects(method_def, environment: nil) return [] if method_def.nil? annotations = method_def.annotations return [] if annotations.nil? || annotations.empty? name_scope = environment&.name_scope reporter = environment&.rbs_extended_reporter effects = [] annotations.each do |annotation| effect = parse_predicate_annotation( annotation.string, name_scope: name_scope, reporter: reporter, source_location: annotation.location ) effects << effect if effect end effects.uniq end |
.read_return_type_override(method_def, environment: nil) ⇒ 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; ADR-13 slice 3b surfaces the miss as adynamic.rbs-extended.unresolved:infodiagnostic when anenvironment:is supplied).
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/rigor/rbs_extended.rb', line 330 def read_return_type_override(method_def, environment: nil) return nil if method_def.nil? annotations = method_def.annotations return nil if annotations.nil? || annotations.empty? name_scope = environment&.name_scope reporter = environment&.rbs_extended_reporter hkt_registry = environment&.hkt_registry annotations.each do |annotation| type = parse_return_type_override( annotation.string, name_scope: name_scope, reporter: reporter, source_location: annotation.location, hkt_registry: hkt_registry ) return type if type end nil end |
.record_unresolved(reporter, payload, source_location) ⇒ Object
ADR-13 slice 3b — guards every reporter call so the in-RbsExtended-module call sites can record events
uniformly without nil-checking each time. When the reporter is nil (the v0.1.0 → v0.1.3 default for call
sites that do not yet thread environment:), the call is a no-op and the parser stays fail-soft.
815 816 817 818 819 |
# File 'lib/rigor/rbs_extended.rb', line 815 def record_unresolved(reporter, payload, source_location) return if reporter.nil? reporter.record_unresolved(payload: payload, source_location: source_location) end |
.relative_annotation_path(name) ⇒ Object
The buffer-name → readable-path rule lives with the walk that produces the buffers
(Effects::SignatureSources), so a virtual: name is stripped identically wherever it surfaces.
808 809 810 |
# File 'lib/rigor/rbs_extended.rb', line 808 def relative_annotation_path(name) Effects::SignatureSources.source_path(name) end |
.render_annotation_location(annotation) ⇒ Object
path:line for the annotation, project-relative when it sits under the working directory, so a
diagnostic message names sig/foo.rbs:12 rather than an absolute path.
A synthesized buffer is re-anchored here rather than at any of the surfaces that render it
(Effects::InlineAnchor; #432). The buffer's own line numbers describe a document the author
never saw, so an envelope carrying one is wrong for every consumer at once — the diagnostic, the
effect.unknown-label position, rigor explain, the JSON formatter, LSP hover. Correcting it
where the value is built is what makes them agree without each learning the mapping.
779 780 781 782 783 784 785 786 787 788 789 790 791 |
# File 'lib/rigor/rbs_extended.rb', line 779 def render_annotation_location(annotation) location = annotation_location(annotation) return nil if location.nil? buffer = location.respond_to?(:buffer) ? location.buffer : nil name = buffer.respond_to?(:name) ? buffer.name.to_s : nil return nil if name.nil? || name.empty? path = relative_annotation_path(name) "#{path}:#{annotation_line(annotation, location, buffer, path)}" rescue StandardError nil end |
.resolve_app_arg(class_name, name_scope: nil) ⇒ Object
437 438 439 440 441 442 443 444 445 446 447 448 |
# File 'lib/rigor/rbs_extended.rb', line 437 def resolve_app_arg(class_name, name_scope: nil) return nil unless /\A(?:::)?(?:[A-Z]\w*)(?:::[A-Z]\w*)*\z/.match?(class_name) normalized = class_name.sub(/\A::/, "") return Type::Nominal.new(normalized) if name_scope.nil? if name_scope.respond_to?(:nominal_for_name) resolved = name_scope.nominal_for_name(normalized) return resolved if resolved end Type::Nominal.new(normalized) end |
.resolve_directive_rhs(match, name_scope: nil, reporter: nil, source_location: nil) ⇒ Object
Resolves the class_name / refinement alternation in
the assert / predicate directive patterns. Returns
[class_name, refinement_type, negative]:
- Class-name arm matched:
class_nameis the resolved string (leading::stripped),refinement_typeis nil,negativereflects the optional~prefix. - Refinement arm matched:
class_nameis nil,refinement_typeis the resolvedRigor::Type,negativereflects the~prefix. v0.0.5 supports refinement-form negation for theDifference[base, Constant]shape (the narrowing tier computes the complement decomposition); other refinement carriers under negation fall back to the conservative "current_type unchanged" answer. - Refinement payload unparseable: returns
[nil, nil, false]so callers can drop the directive silently (fail-soft policy).
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/rigor/rbs_extended.rb', line 280 def resolve_directive_rhs(match, name_scope: nil, reporter: nil, source_location: nil) negative = match[:negation].to_s == "~" class_capture = match[:class_name] return [class_capture.to_s.sub(/\A::/, ""), nil, negative] if class_capture refinement_capture = match[:refinement] return [nil, nil, false] if refinement_capture.nil? type = Builtins::ImportedRefinements.parse( refinement_capture, name_scope: name_scope, reporter: reporter, source_location: source_location ) return [nil, nil, false] if type.nil? [nil, type, negative] end |
.target_fields(target) ⇒ Object
299 300 301 302 303 304 305 |
# File 'lib/rigor/rbs_extended.rb', line 299 def target_fields(target) if target == "self" %i[self self] else [:parameter, target.to_sym] end end |
Instance Method Details
#self?.param_type_override_map ⇒ Hash[Symbol, Type::t]
64 |
# File 'sig/rigor/rbs_extended.rbs', line 64
def self?.param_type_override_map: (untyped method_def) -> Hash[Symbol, Type::t]
|
#self?.parse_assert_annotation ⇒ AssertEffect?
41 |
# File 'sig/rigor/rbs_extended.rbs', line 41
def self?.parse_assert_annotation: (String string) -> AssertEffect?
|
#self?.parse_conforms_to_annotation ⇒ String?
48 |
# File 'sig/rigor/rbs_extended.rbs', line 48
def self?.parse_conforms_to_annotation: (String? string) -> String?
|
#self?.parse_param_annotation ⇒ ParamOverride?
65 |
# File 'sig/rigor/rbs_extended.rbs', line 65
def self?.parse_param_annotation: (String string) -> ParamOverride?
|
#self?.parse_predicate_annotation ⇒ PredicateEffect?
22 |
# File 'sig/rigor/rbs_extended.rbs', line 22
def self?.parse_predicate_annotation: (String string) -> PredicateEffect?
|
#self?.parse_return_type_override ⇒ Type::t?
44 |
# File 'sig/rigor/rbs_extended.rbs', line 44
def self?.parse_return_type_override: (String string) -> Type::t?
|
#self?.pure_annotation? ⇒ Boolean
ADR-103 #383 — the effect-envelope reader. annotations is any list of RBS annotation nodes
(RBS::Definition::Method#annotations, or a declaration / member's own #annotations); the result
is a Rigor::Effects::Envelope, or nil when the list carries neither purity spelling.
53 |
# File 'sig/rigor/rbs_extended.rbs', line 53
def self?.pure_annotation?: (String? string) -> bool
|
#self?.read_assert_effects ⇒ Array[AssertEffect]
40 |
# File 'sig/rigor/rbs_extended.rbs', line 40
def self?.read_assert_effects: (untyped method_def) -> Array[AssertEffect]
|
#self?.read_effect_envelope ⇒ Object?
54 |
# File 'sig/rigor/rbs_extended.rbs', line 54
def self?.read_effect_envelope: (untyped annotations, owner_key: String, ?source: Symbol, ?registry: untyped, ?reporter: untyped) -> untyped?
|
#self?.read_flow_contribution ⇒ Object?
46 |
# File 'sig/rigor/rbs_extended.rbs', line 46
def self?.read_flow_contribution: (untyped method_def) -> untyped?
|
#self?.read_param_type_overrides ⇒ Array[ParamOverride]
63 |
# File 'sig/rigor/rbs_extended.rbs', line 63
def self?.read_param_type_overrides: (untyped method_def) -> Array[ParamOverride]
|
#self?.read_predicate_effects ⇒ Array[PredicateEffect]
21 |
# File 'sig/rigor/rbs_extended.rbs', line 21
def self?.read_predicate_effects: (untyped method_def) -> Array[PredicateEffect]
|
#self?.read_return_type_override ⇒ Type::t?
43 |
# File 'sig/rigor/rbs_extended.rbs', line 43
def self?.read_return_type_override: (untyped method_def) -> Type::t?
|