rigortype 0.4.0 → 0.4.2
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 +4 -3
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +2 -2
- data/docs/manual/03-configuration.md +6 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/18-removing-dead-code.md +32 -2
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
- data/lib/rigor/analysis/check_rules.rb +407 -144
- data/lib/rigor/analysis/dependency_recorder.rb +215 -18
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
- data/lib/rigor/analysis/incremental_session.rb +287 -50
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +43 -9
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +371 -49
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_paths.rb +6 -4
- data/lib/rigor/analysis/template_units.rb +15 -5
- data/lib/rigor/analysis/worker_session.rb +11 -7
- data/lib/rigor/cache/descriptor.rb +40 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +92 -8
- data/lib/rigor/cache/store.rb +63 -3
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +85 -14
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +16 -10
- data/lib/rigor/cli.rb +11 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +11 -0
- data/lib/rigor/configuration.rb +52 -7
- data/lib/rigor/effects/plugin_facts.rb +6 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/lockfile_resolver.rb +10 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +284 -15
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +34 -73
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/block_parameter_binder.rb +29 -13
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +7 -1
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +26 -3
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/expression_typer.rb +351 -61
- data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +54 -13
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +73 -14
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +214 -0
- data/lib/rigor/inference/multi_target_binder.rb +21 -16
- data/lib/rigor/inference/narrowing.rb +166 -22
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +2129 -354
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +149 -32
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/base.rb +28 -8
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +124 -7
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/discovery_seed.rb +18 -9
- data/lib/rigor/protection/mutation_cache.rb +4 -4
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +211 -46
- data/lib/rigor/reflection/constant_path.rb +33 -7
- data/lib/rigor/reflection.rb +41 -10
- data/lib/rigor/scope/discovery_index.rb +258 -3
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +1032 -0
- data/lib/rigor/scope.rb +480 -328
- data/lib/rigor/sig_gen/alias_index.rb +1 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
- data/lib/rigor/sig_gen/generator.rb +77 -21
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +59 -3
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +13 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/environment.rbs +2 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/scope.rbs +69 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- data/skills/rigor-project-init/SKILL.md +37 -11
- data/skills/rigor-project-init/references/02-configure.md +7 -2
- metadata +90 -1
|
@@ -9,6 +9,7 @@ require_relative "../source/node_walker"
|
|
|
9
9
|
require_relative "../source/node_children"
|
|
10
10
|
require_relative "../source/constant_path"
|
|
11
11
|
require_relative "anonymous_meta_class"
|
|
12
|
+
require_relative "definer_resolution"
|
|
12
13
|
require_relative "block_repetition"
|
|
13
14
|
require_relative "block_parameter_binder"
|
|
14
15
|
require_relative "body_fixpoint"
|
|
@@ -30,6 +31,7 @@ require_relative "match_rebinding"
|
|
|
30
31
|
require_relative "element_read_widening"
|
|
31
32
|
require_relative "hash_lookup_mutation"
|
|
32
33
|
require_relative "indexed_narrowing"
|
|
34
|
+
require_relative "key_presence_guard"
|
|
33
35
|
require_relative "index_write_widening"
|
|
34
36
|
require_relative "method_dispatcher"
|
|
35
37
|
require_relative "method_parameter_binder"
|
|
@@ -272,10 +274,16 @@ module Rigor
|
|
|
272
274
|
# {OperandWalk} rooted inside an operand still records its later operands ({#walk_recorder}).
|
|
273
275
|
# @param operand_types — the later operands' own values ({OperandWalk#types}) for the evaluator that runs a
|
|
274
276
|
# call from the scope its operands left, read by {#type_operand}.
|
|
277
|
+
# @param opaque_class — true while the walk is inside the body of a class nothing names (issue #1518:
|
|
278
|
+
# a declined `class self::X`, a parse-error header), where `class_context` is empty without the code
|
|
279
|
+
# being at the top level. A `def` (or `class << self`) there runs under an untyped `self` instead of
|
|
280
|
+
# the top level's nil one, so its implicit-self calls answer `Dynamic` and no top-level rule fires.
|
|
275
281
|
def initialize(scope:, tracer: nil, on_enter: nil, class_context: [].freeze, # rubocop:disable Metrics/ParameterLists
|
|
276
282
|
lexical_nesting: EMPTY_NESTING, converged_loop_recording: false, next_scope_sink: nil,
|
|
277
|
-
operand_scope: nil, in_operand: false, operand_recorder: nil, operand_types: nil
|
|
283
|
+
operand_scope: nil, in_operand: false, operand_recorder: nil, operand_types: nil,
|
|
284
|
+
opaque_class: false)
|
|
278
285
|
@scope = scope
|
|
286
|
+
@opaque_class = opaque_class
|
|
279
287
|
@tracer = tracer
|
|
280
288
|
@on_enter = on_enter
|
|
281
289
|
@class_context = class_context.freeze
|
|
@@ -345,7 +353,7 @@ module Rigor
|
|
|
345
353
|
@on_enter&.call(node, @scope)
|
|
346
354
|
|
|
347
355
|
handler = HANDLERS[node.class]
|
|
348
|
-
return forget_implicit_call_guards(node, send(handler, node)) if handler
|
|
356
|
+
return forget_aliased_key_guards(node, forget_implicit_call_guards(node, send(handler, node))) if handler
|
|
349
357
|
|
|
350
358
|
# Default: the node is treated as a pure expression. Type it through the existing expression typer (which
|
|
351
359
|
# observes the current scope's locals) and leave the scope unchanged, but for the match globals a call in it
|
|
@@ -366,6 +374,16 @@ module Rigor
|
|
|
366
374
|
[type, after.forget_guard_narrowings]
|
|
367
375
|
end
|
|
368
376
|
|
|
377
|
+
# Issue #1703 — a write that stores a `key?` guard's receiver or key root elsewhere (`g = h`) aliases it, so
|
|
378
|
+
# the guard ends ({KeyPresenceGuard.invalidate_after_write}).
|
|
379
|
+
def forget_aliased_key_guards(node, result)
|
|
380
|
+
type, after = result
|
|
381
|
+
return result unless after.is_a?(Scope)
|
|
382
|
+
|
|
383
|
+
guarded = KeyPresenceGuard.invalidate_after_write(node, after)
|
|
384
|
+
guarded.equal?(after) ? result : [type, guarded]
|
|
385
|
+
end
|
|
386
|
+
|
|
369
387
|
# Issue #1359 — the nodes whose handler runs their parts in order, each from the scope the parts before it
|
|
370
388
|
# left, so a `$_` reader among them forgets it where it runs ({LastLine}) and a read before it keeps the
|
|
371
389
|
# narrowing.
|
|
@@ -625,7 +643,8 @@ module Rigor
|
|
|
625
643
|
# lands.
|
|
626
644
|
def eval_ivar_write(node)
|
|
627
645
|
rhs_type, post_rhs = sub_eval(node.value, scope)
|
|
628
|
-
|
|
646
|
+
# Issue #1446 — a write ends a class guard's narrowing of the variable, as it does a global's.
|
|
647
|
+
bound = post_rhs.with_ivar(node.name, rhs_type).without_ivar_guard(node.name)
|
|
629
648
|
bound = bound.with_ivar_origin(node.name, rhs_origin(node.value, post_rhs, rhs_type))
|
|
630
649
|
cause = optimistic_rhs_origin(node.value, post_rhs)
|
|
631
650
|
bound = bound.with_optimistic_ivar(node.name, cause, miss: optimistic_rhs_miss(node.value, post_rhs)) if cause
|
|
@@ -806,7 +825,8 @@ module Rigor
|
|
|
806
825
|
end
|
|
807
826
|
|
|
808
827
|
def rebind_variable(target_scope, kind, name, type)
|
|
809
|
-
target_scope.public_send(VAR_KIND_BUILDERS.fetch(kind), name, type)
|
|
828
|
+
bound = target_scope.public_send(VAR_KIND_BUILDERS.fetch(kind), name, type)
|
|
829
|
+
kind == :ivar ? bound.without_ivar_guard(name) : bound
|
|
810
830
|
end
|
|
811
831
|
|
|
812
832
|
def compound_result_type(current, rhs, operator)
|
|
@@ -2431,6 +2451,12 @@ module Rigor
|
|
|
2431
2451
|
case index_node
|
|
2432
2452
|
when Prism::LocalVariableTargetNode
|
|
2433
2453
|
scope.with_local(index_node.name, element_type)
|
|
2454
|
+
when Prism::InstanceVariableTargetNode
|
|
2455
|
+
# Issue #1446 — `for @io in xs` writes the ivar each iteration, which ends a class guard's narrowing of it,
|
|
2456
|
+
# and `for $out in xs` the global, which ends any guard's narrowing of it.
|
|
2457
|
+
scope.with_ivar(index_node.name, element_type).without_ivar_guard(index_node.name)
|
|
2458
|
+
when Prism::GlobalVariableTargetNode
|
|
2459
|
+
scope.with_global(index_node.name, element_type)
|
|
2434
2460
|
when Prism::IndexTargetNode
|
|
2435
2461
|
widen_index_target(index_node, element_type, scope, type_scope: scope)
|
|
2436
2462
|
when Prism::MultiTargetNode
|
|
@@ -2698,13 +2724,31 @@ module Rigor
|
|
|
2698
2724
|
# that half rather than this one deriving it from the frames.
|
|
2699
2725
|
def eval_class_or_module(node)
|
|
2700
2726
|
path = node.constant_path
|
|
2701
|
-
|
|
2727
|
+
name = Source::ConstantPath.qualified_name(path)
|
|
2728
|
+
# A header Prism recovered without a constant (`class foo`, a parse error) names no class: its body
|
|
2729
|
+
# runs under an untyped `self` and no class frame, so nothing is filed under an empty name.
|
|
2730
|
+
return eval_unnameable_class_body(node) if name.nil?
|
|
2731
|
+
# `class self::X` the indexer declined (opaque `self`, e.g. `REGISTRY.first.class_eval`) is not the
|
|
2732
|
+
# lexical `X` that `qualified_name` renders; a nameable `self::` header is in the identity table.
|
|
2733
|
+
return eval_unnameable_class_body(node) if declined_self_header?(path)
|
|
2734
|
+
|
|
2735
|
+
frame = ClassFrame.new(name: name, singleton: false)
|
|
2702
2736
|
new_context = Source::ConstantPath.rooted?(path) ? [frame] : @class_context + [frame]
|
|
2703
2737
|
body_type, _body_scope = eval_class_body(node, new_context,
|
|
2704
2738
|
Source::ConstantPath.pushed_nesting(@lexical_nesting, path))
|
|
2705
2739
|
[body_type, scope]
|
|
2706
2740
|
end
|
|
2707
2741
|
|
|
2742
|
+
def declined_self_header?(path)
|
|
2743
|
+
path.is_a?(Prism::ConstantPathNode) && path.parent.is_a?(Prism::SelfNode) &&
|
|
2744
|
+
!scope.declared_types.key?(path)
|
|
2745
|
+
end
|
|
2746
|
+
|
|
2747
|
+
def eval_unnameable_class_body(node)
|
|
2748
|
+
body_type, _body_scope = eval_class_body(node, [], @lexical_nesting, opaque_self: true)
|
|
2749
|
+
[body_type, scope]
|
|
2750
|
+
end
|
|
2751
|
+
|
|
2708
2752
|
# `class << expr; body; end`. When `expr` is `self`, the body defines class methods on the immediate enclosing
|
|
2709
2753
|
# class — the innermost frame flips to `singleton: true` so a nested `def foo` resolves through `singleton_method`
|
|
2710
2754
|
# rather than `instance_method`. For non-`self` expressions we cannot statically resolve the receiver, so we keep
|
|
@@ -2961,6 +3005,8 @@ module Rigor
|
|
|
2961
3005
|
# outer receiver is itself a chain node (e.g. `x.last << y`) do NOT drop narrowings keyed on `x` — only direct
|
|
2962
3006
|
# calls against the root variable invalidate the chain.
|
|
2963
3007
|
post_scope = IndexedNarrowing.invalidate_chain_after_call(call_node: node, current_scope: post_scope)
|
|
3008
|
+
# Issue #1703 — a non-literal-key `key?` guard the call may break ({KeyPresenceGuard.breaks?}).
|
|
3009
|
+
post_scope = KeyPresenceGuard.invalidate_after_call(node, post_scope)
|
|
2964
3010
|
# B2.2 — intervening method call ivar invalidation. An implicit-self / self-receiver call could mutate any ivar
|
|
2965
3011
|
# of the enclosing class (we cannot prove purity without an effect system). Reset each ivar whose current local
|
|
2966
3012
|
# binding has narrowed below the class-ivar seed back to the seed itself, so a subsequent `if @flag` predicate
|
|
@@ -3526,15 +3572,19 @@ module Rigor
|
|
|
3526
3572
|
# does not type as a class object (a gem class with no RBS) keeps the name as written: the body is still some
|
|
3527
3573
|
# class's body, and leaving it on the enclosing `self` made a `refine` at the file's top level report every
|
|
3528
3574
|
# implicit-self call in it as `call.unresolved-toplevel`.
|
|
3575
|
+
#
|
|
3576
|
+
# Issue #1689 — a `refine` call the file's {InEffectRefinements} places in a class body is the class's own
|
|
3577
|
+
# method, not `Module#refine`, so its block is entered as any other block.
|
|
3529
3578
|
def refined_class_context(node)
|
|
3530
3579
|
target = ScopeIndexer.refine_target(node)
|
|
3531
3580
|
return nil if target.nil?
|
|
3532
3581
|
|
|
3533
|
-
|
|
3534
|
-
|
|
3582
|
+
return nil if InEffectRefinements.class_body_refine?(scope, node)
|
|
3583
|
+
|
|
3584
|
+
name = ScopeIndexer.refined_class_name(target, scope)
|
|
3535
3585
|
return nil if name.nil?
|
|
3536
3586
|
|
|
3537
|
-
[ClassFrame.new(name: name
|
|
3587
|
+
[ClassFrame.new(name: name, singleton: false, refinement: true)]
|
|
3538
3588
|
end
|
|
3539
3589
|
|
|
3540
3590
|
# The block calls whose body `return` leaves only the block ({ReturnBarrier.block_call?}). Like a `->` body
|
|
@@ -3568,7 +3618,9 @@ module Rigor
|
|
|
3568
3618
|
narrowed = DefineMethodBlockSelf.narrow_self_type_for(
|
|
3569
3619
|
scope: scope, call_node: call_node
|
|
3570
3620
|
)
|
|
3571
|
-
|
|
3621
|
+
return block_entry unless narrowed
|
|
3622
|
+
|
|
3623
|
+
block_entry.with_block_self_type(narrowed, keeps_unknown: scope.block_self_narrowing_unknown?(call_node))
|
|
3572
3624
|
end
|
|
3573
3625
|
|
|
3574
3626
|
# Enters a meta-new `block` as the body of the class `class_context` names: `self_type` is that class's
|
|
@@ -3583,8 +3635,17 @@ module Rigor
|
|
|
3583
3635
|
# record a `Outer::K` entry: that is a rung Ruby's constant lookup does not have. The `class_context`
|
|
3584
3636
|
# frame is still pushed — it is what a nested `def` registers its method under — which is exactly the
|
|
3585
3637
|
# divergence that makes the chain a separate record rather than a view of the frame stack.
|
|
3638
|
+
#
|
|
3639
|
+
# Issue #1759 — a `refine X do … end` block runs on the refinement module, not on X, which Rigor does not
|
|
3640
|
+
# model: the body keeps X's frame and singleton so its `def`s and implicit-self calls resolve as before, but
|
|
3641
|
+
# carries the unknown-`self` mark ({Scope#block_self_unknown?}), so an explicit `self.import_methods(M)` is
|
|
3642
|
+
# not checked against `singleton(X)`. The indexer leaves a value-position refine block unknown the same way.
|
|
3586
3643
|
def enter_meta_class_body(block, block_entry, class_context)
|
|
3587
|
-
|
|
3644
|
+
# Issue #1717 — the body runs on the new class whatever encloses it, so its `self` is known.
|
|
3645
|
+
entry = block_entry.with_block_self_type(self_type_for_class_body(class_context),
|
|
3646
|
+
keeps_unknown: class_context.last.refinement &&
|
|
3647
|
+
Scope::REFINEMENT_BLOCK_SELF)
|
|
3648
|
+
.with_singleton_class_body(false)
|
|
3588
3649
|
sub_eval(block, stamp_nesting(entry, @lexical_nesting), class_context: class_context)
|
|
3589
3650
|
end
|
|
3590
3651
|
|
|
@@ -5107,27 +5168,31 @@ module Rigor
|
|
|
5107
5168
|
# ({ExpressionTyper#block_body_self_narrowing}) already applies the same contract to block-return
|
|
5108
5169
|
# typing; without it here the recorded per-node scopes — what `dump_type`/`assert_type` and the
|
|
5109
5170
|
# survey read — keep the enclosing `self_type` and every DSL call inside stays `Dynamic[top]`.
|
|
5110
|
-
|
|
5111
|
-
|
|
5171
|
+
# Issue #1667 — the entry's `refinements:` are put in effect in the body alongside.
|
|
5172
|
+
match = call_node && macro_block_match(call_node)
|
|
5173
|
+
if match
|
|
5174
|
+
keeps_unknown = scope.block_self_narrowing_unknown?(call_node)
|
|
5175
|
+
scope_with_params = MacroBlockSelfType.apply(scope_with_params, match, keeps_unknown: keeps_unknown)
|
|
5176
|
+
end
|
|
5112
5177
|
block_local_names(block_node).reduce(scope_with_params) do |acc, name|
|
|
5113
5178
|
acc.with_local(name, Type::Combinator.constant_of(nil))
|
|
5114
5179
|
end
|
|
5115
5180
|
end
|
|
5116
5181
|
|
|
5117
|
-
# The
|
|
5182
|
+
# The ADR-16 `block_as_methods:` match for `call_node`, keyed on the explicit receiver's type, or on the
|
|
5118
5183
|
# current `self_type` for an implicit-self DSL call (the `params do` / `namespace do` shapes, whose
|
|
5119
5184
|
# receiver is the enclosing `Singleton[X]`). A miss leaves the entry scope as built — the false-
|
|
5120
5185
|
# positive-safe direction.
|
|
5121
|
-
def
|
|
5186
|
+
def macro_block_match(call_node)
|
|
5122
5187
|
receiver_type =
|
|
5123
5188
|
if call_node.receiver
|
|
5124
5189
|
explicit_receiver_type(call_node)
|
|
5125
5190
|
else
|
|
5126
|
-
scope
|
|
5191
|
+
MacroBlockSelfType.implicit_receiver_type(scope)
|
|
5127
5192
|
end
|
|
5128
5193
|
return nil if receiver_type.nil?
|
|
5129
5194
|
|
|
5130
|
-
MacroBlockSelfType.
|
|
5195
|
+
MacroBlockSelfType.match_for(
|
|
5131
5196
|
scope: scope, call_node: call_node, receiver_type: receiver_type
|
|
5132
5197
|
)
|
|
5133
5198
|
rescue StandardError
|
|
@@ -5160,7 +5225,8 @@ module Rigor
|
|
|
5160
5225
|
method_name: call_node.name,
|
|
5161
5226
|
arg_types: arg_types,
|
|
5162
5227
|
environment: scope.environment,
|
|
5163
|
-
scope: scope
|
|
5228
|
+
scope: scope,
|
|
5229
|
+
call_node: call_node
|
|
5164
5230
|
)
|
|
5165
5231
|
rescue StandardError
|
|
5166
5232
|
[]
|
|
@@ -5175,7 +5241,7 @@ module Rigor
|
|
|
5175
5241
|
|
|
5176
5242
|
# ----- def/class helpers -----
|
|
5177
5243
|
|
|
5178
|
-
def eval_class_body(node, new_context, new_nesting = @lexical_nesting)
|
|
5244
|
+
def eval_class_body(node, new_context, new_nesting = @lexical_nesting, opaque_self: false)
|
|
5179
5245
|
return [Type::Combinator.constant_of(nil), scope] if node.body.nil?
|
|
5180
5246
|
|
|
5181
5247
|
# Class/module bodies run in a fresh scope: the outer scope's locals are NOT visible inside `class Foo; ...
|
|
@@ -5183,15 +5249,24 @@ module Rigor
|
|
|
5183
5249
|
# A-engine: `self` inside a class body is the class object itself, so we set `self_type` to
|
|
5184
5250
|
# `Singleton[<qualified>]`.
|
|
5185
5251
|
fresh = build_fresh_body_scope
|
|
5186
|
-
|
|
5252
|
+
# Issue #1518 — `class << self` inside an unnameable class body keeps that body's opacity: it opens the
|
|
5253
|
+
# singleton of a class nothing names, and its empty frame stack is not the top level.
|
|
5254
|
+
singleton_body = node.is_a?(Prism::SingletonClassNode)
|
|
5255
|
+
opaque_self ||= singleton_body && @opaque_class && new_context.empty?
|
|
5256
|
+
# Issue #1763 — a `class << self` in a `refine X` block (the refinement frame {#singleton_context_for} built)
|
|
5257
|
+
# opens the refinement module's singleton class, which Rigor does not model, as the `def`s in it already read
|
|
5258
|
+
# ({#self_type_for_method_body}); `Singleton[X]` there checked `self.m` and a local holding `self` against X's
|
|
5259
|
+
# class methods. A `class << X` names X's real singleton class and takes a frame without the flag.
|
|
5260
|
+
untyped_self = opaque_self || (singleton_body && new_context.last&.refinement)
|
|
5261
|
+
body_self = untyped_self ? Type::Combinator.untyped : self_type_for_class_body(new_context)
|
|
5187
5262
|
fresh = fresh.with_self_type(body_self) if body_self
|
|
5188
5263
|
# Issue #963 — `self` in a `class << ...` body is the SINGLETON class, which shares the `Singleton[X]`
|
|
5189
5264
|
# carrier a `class X` body gets. The mark is the only thing that tells the two apart downstream, and a
|
|
5190
5265
|
# `def` reached from this body clears it by starting from a fresh scope.
|
|
5191
|
-
fresh = fresh.with_singleton_class_body(
|
|
5266
|
+
fresh = fresh.with_singleton_class_body(singleton_body)
|
|
5192
5267
|
fresh = fresh.with_match_frame(node.body)
|
|
5193
5268
|
fresh = stamp_nesting(fresh, new_nesting)
|
|
5194
|
-
sub_eval(node.body, fresh, class_context: new_context, lexical_nesting: new_nesting)
|
|
5269
|
+
sub_eval(node.body, fresh, class_context: new_context, lexical_nesting: new_nesting, opaque_class: opaque_self)
|
|
5195
5270
|
end
|
|
5196
5271
|
|
|
5197
5272
|
def build_method_entry_scope(def_node) # rubocop:disable Metrics/AbcSize
|
|
@@ -5218,7 +5293,7 @@ module Rigor
|
|
|
5218
5293
|
# then receive the parameter bindings. Slice 7 phase 2: instance defs ALSO seed their `ivars` map from the
|
|
5219
5294
|
# class-level accumulator so `def get; @x; end` reads the type that a sibling `def init; @x = 1; end` wrote.
|
|
5220
5295
|
fresh = build_fresh_body_scope
|
|
5221
|
-
body_self = self_type_for_method_body(singleton: singleton)
|
|
5296
|
+
body_self = self_type_for_method_body(singleton: singleton, receiver: def_node.receiver)
|
|
5222
5297
|
fresh = fresh.with_self_type(body_self) if body_self
|
|
5223
5298
|
# A `def` opens no new `Module.nesting` entry — the body resolves constants against the chain of the
|
|
5224
5299
|
# declaration that encloses it, which is what the evaluator is already carrying (#652).
|
|
@@ -5353,12 +5428,16 @@ module Rigor
|
|
|
5353
5428
|
# chain's throwaway intermediate Scopes were a top `Scope#rebuild` source (ADR-44). Local-empty by design; the
|
|
5354
5429
|
# discovery index is inherited whole by reference (ADR-53 Track A), so a table added to the index can no longer
|
|
5355
5430
|
# be dropped here by a missed per-field copy.
|
|
5431
|
+
#
|
|
5432
|
+
# Issue #1667 — a plugin-declared refined block's modules carry into a `def` or class body written in it: CRuby
|
|
5433
|
+
# defines the method with the block's cref, and a class body's cref inherits the outer one's refinements.
|
|
5356
5434
|
Scope.new(
|
|
5357
5435
|
environment: scope.environment,
|
|
5358
5436
|
locals: {}.freeze,
|
|
5359
5437
|
source_path: scope.source_path,
|
|
5360
5438
|
discovery: scope.discovery,
|
|
5361
|
-
dynamic_origins: scope.dynamic_origins
|
|
5439
|
+
dynamic_origins: scope.dynamic_origins,
|
|
5440
|
+
declared_refinements: scope.declared_refinements
|
|
5362
5441
|
)
|
|
5363
5442
|
end
|
|
5364
5443
|
|
|
@@ -5408,9 +5487,15 @@ module Rigor
|
|
|
5408
5487
|
# `Nominal[Foo]`.
|
|
5409
5488
|
#
|
|
5410
5489
|
# Returns nil for top-level defs that have no enclosing class.
|
|
5411
|
-
def self_type_for_method_body(singleton:)
|
|
5490
|
+
def self_type_for_method_body(singleton:, receiver: nil)
|
|
5412
5491
|
path = current_class_path
|
|
5413
|
-
|
|
5492
|
+
# Issue #1518 — a `def` in a class nothing names has a receiver, just not one Rigor can type; nil would
|
|
5493
|
+
# read its body as the top level and its implicit-self calls as unresolved top-level calls.
|
|
5494
|
+
return (Type::Combinator.untyped if @opaque_class) if path.nil?
|
|
5495
|
+
# Issue #1759 — a `def self.m` in a `refine X` block (or a `def m` in a `class << self` there) is a method
|
|
5496
|
+
# of the refinement module, which Rigor does not model; reading it as `Singleton[X]` checked its
|
|
5497
|
+
# explicit-self calls against X's class methods. A `def X.m` there does define X's class method.
|
|
5498
|
+
return Type::Combinator.untyped if singleton && @class_context.last.refinement && refinement_self_def?(receiver)
|
|
5414
5499
|
|
|
5415
5500
|
if singleton
|
|
5416
5501
|
Type::Combinator.singleton_of(path)
|
|
@@ -5419,6 +5504,10 @@ module Rigor
|
|
|
5419
5504
|
end
|
|
5420
5505
|
end
|
|
5421
5506
|
|
|
5507
|
+
# Issue #1759 — whether a singleton `def` under a refinement frame defines a method on the refinement module
|
|
5508
|
+
# (`def self.m`, or `def m` in a `class << self`) rather than on an explicitly named receiver (`def X.m`).
|
|
5509
|
+
def refinement_self_def?(receiver) = receiver.nil? || receiver.is_a?(Prism::SelfNode)
|
|
5510
|
+
|
|
5422
5511
|
def singleton_context_for(node)
|
|
5423
5512
|
case node.expression
|
|
5424
5513
|
when Prism::SelfNode
|
|
@@ -5426,7 +5515,7 @@ module Rigor
|
|
|
5426
5515
|
|
|
5427
5516
|
outer = @class_context[0..-2]
|
|
5428
5517
|
last = @class_context.last
|
|
5429
|
-
outer + [ClassFrame.new(name: last.name, singleton: true)]
|
|
5518
|
+
outer + [ClassFrame.new(name: last.name, singleton: true, refinement: last.refinement)]
|
|
5430
5519
|
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
|
5431
5520
|
target = singleton_constant_target(node.expression)
|
|
5432
5521
|
return @class_context unless target
|
|
@@ -5549,10 +5638,14 @@ module Rigor
|
|
|
5549
5638
|
# `on_enter: nil` evaluates without recording into the per-node scope index — for a pass whose scopes are not
|
|
5550
5639
|
# the ones the index should keep. `next_scope_sink:` is replaced only by {#evaluate_invocation} and
|
|
5551
5640
|
# {#loop_iteration}.
|
|
5552
|
-
|
|
5553
|
-
|
|
5641
|
+
# `opaque_class:` survives only while the frame stack stays empty: a named class opened inside an
|
|
5642
|
+
# unnameable body gets a frame, and its `def`s a nominal `self`, as they did before #1518.
|
|
5643
|
+
def sub_eval(node, with_scope, class_context: @class_context, lexical_nesting: @lexical_nesting, # rubocop:disable Metrics/ParameterLists
|
|
5644
|
+
on_enter: @on_enter, next_scope_sink: @next_scope_sink, operand_recorder: @operand_recorder,
|
|
5645
|
+
opaque_class: @opaque_class && class_context.empty?)
|
|
5554
5646
|
evaluator_at(with_scope, class_context: class_context, lexical_nesting: lexical_nesting, on_enter: on_enter,
|
|
5555
|
-
next_scope_sink: next_scope_sink, operand_recorder: operand_recorder
|
|
5647
|
+
next_scope_sink: next_scope_sink, operand_recorder: operand_recorder,
|
|
5648
|
+
opaque_class: opaque_class).evaluate(node)
|
|
5556
5649
|
end
|
|
5557
5650
|
|
|
5558
5651
|
# An evaluator over `with_scope` that inherits everything else from this one. `operand_scope:` and
|
|
@@ -5560,7 +5653,8 @@ module Rigor
|
|
|
5560
5653
|
# operands left.
|
|
5561
5654
|
def evaluator_at(with_scope, class_context: @class_context, lexical_nesting: @lexical_nesting, # rubocop:disable Metrics/ParameterLists
|
|
5562
5655
|
on_enter: @on_enter, next_scope_sink: @next_scope_sink, operand_scope: nil,
|
|
5563
|
-
in_operand: @in_operand, operand_recorder: @operand_recorder, operand_types: nil
|
|
5656
|
+
in_operand: @in_operand, operand_recorder: @operand_recorder, operand_types: nil,
|
|
5657
|
+
opaque_class: @opaque_class)
|
|
5564
5658
|
StatementEvaluator.new(
|
|
5565
5659
|
scope: with_scope,
|
|
5566
5660
|
tracer: tracer,
|
|
@@ -5572,7 +5666,8 @@ module Rigor
|
|
|
5572
5666
|
operand_scope: operand_scope,
|
|
5573
5667
|
in_operand: in_operand,
|
|
5574
5668
|
operand_recorder: operand_recorder,
|
|
5575
|
-
operand_types: operand_types
|
|
5669
|
+
operand_types: operand_types,
|
|
5670
|
+
opaque_class: opaque_class
|
|
5576
5671
|
)
|
|
5577
5672
|
end
|
|
5578
5673
|
|
|
@@ -5675,6 +5770,12 @@ module Rigor
|
|
|
5675
5770
|
case ref
|
|
5676
5771
|
when Prism::LocalVariableTargetNode
|
|
5677
5772
|
scope.with_local(ref.name, exception_type)
|
|
5773
|
+
when Prism::InstanceVariableTargetNode
|
|
5774
|
+
# Issue #1446 — `rescue => @io` writes the ivar, which ends a class guard's narrowing of it, and `rescue =>
|
|
5775
|
+
# $out` the global, which ends any guard's narrowing of it.
|
|
5776
|
+
scope.with_ivar(ref.name, exception_type).without_ivar_guard(ref.name)
|
|
5777
|
+
when Prism::GlobalVariableTargetNode
|
|
5778
|
+
scope.with_global(ref.name, exception_type)
|
|
5678
5779
|
when Prism::IndexTargetNode
|
|
5679
5780
|
widen_index_target(ref, exception_type, scope, type_scope: scope)
|
|
5680
5781
|
else
|
|
@@ -5818,7 +5919,7 @@ module Rigor
|
|
|
5818
5919
|
def source_decomposition_projection(subject_type, method_name, arg_types, scope)
|
|
5819
5920
|
return nil unless subject_type.is_a?(Type::Nominal)
|
|
5820
5921
|
|
|
5821
|
-
def_node = scope
|
|
5922
|
+
def_node = own_definer_node(scope, subject_type.class_name, method_name)
|
|
5822
5923
|
return nil if def_node.nil?
|
|
5823
5924
|
|
|
5824
5925
|
result = scope.user_method_return(def_node, subject_type, arg_types)
|
|
@@ -5827,6 +5928,22 @@ module Rigor
|
|
|
5827
5928
|
result
|
|
5828
5929
|
end
|
|
5829
5930
|
|
|
5931
|
+
# The `def` node the receiver's OWN class defines `method_name` with, or nil. ADR-119 C2-e (#1615): asked of
|
|
5932
|
+
# `DefinerResolution`, so a slot rebuilt from an ADR-85 seed bundle is resolved to a node and the read files
|
|
5933
|
+
# its ADR-46 edge. UNKNOWN answers nil: an unsettled or forked chain, or a contested slot once ADR-119 C1d
|
|
5934
|
+
# fills the siblings. Conditional or reopened defs are not contested until C1d. A definer further up the
|
|
5935
|
+
# chain (a prepended or included module, a superclass) is not the own class's, so the owner check declines it.
|
|
5936
|
+
def own_definer_node(scope, class_name, method_name)
|
|
5937
|
+
case DefinerResolution.resolve(scope, class_name, method_name, :instance, question: :definer)
|
|
5938
|
+
in DefinerResolution::Known(answer: [node, owner])
|
|
5939
|
+
owner == class_name ? node : nil
|
|
5940
|
+
in DefinerResolution::UNKNOWN # the case/in contract needs one arm per answer
|
|
5941
|
+
nil
|
|
5942
|
+
in DefinerResolution::ABSENT # rubocop:disable Lint/DuplicateBranch -- see above
|
|
5943
|
+
nil
|
|
5944
|
+
end
|
|
5945
|
+
end
|
|
5946
|
+
|
|
5830
5947
|
# A `StructInstance`'s own projection — `Tuple` of its member values for `deconstruct`, `HashShape` of
|
|
5831
5948
|
# its members for `deconstruct_keys` — or nil when the subject is another carrier or the projection would
|
|
5832
5949
|
# be unsound.
|
|
@@ -22,6 +22,9 @@ module Rigor
|
|
|
22
22
|
# the effect classifier and the effect catalogue's `mutators: string` cite it rather than keep a list of their
|
|
23
23
|
# own (ADR-103 WD3). A spec holds it to every bang method `String` defines plus the non-bang mutators reflection
|
|
24
24
|
# cannot find by name.
|
|
25
|
+
#
|
|
26
|
+
# The last row is Ruby 4.1's bit operations (#1691), which `data/core_overlay/string.rbs` declares whatever Ruby
|
|
27
|
+
# runs the analysis; they rewrite bytes in place and keep the byte length.
|
|
25
28
|
MUTATORS = %i[
|
|
26
29
|
<< concat insert prepend replace clear []= slice!
|
|
27
30
|
setbyte bytesplice append_as_bytes force_encoding
|
|
@@ -29,18 +32,21 @@ module Rigor
|
|
|
29
32
|
upcase! downcase! capitalize! swapcase! reverse!
|
|
30
33
|
strip! lstrip! rstrip! chomp! chop!
|
|
31
34
|
delete_prefix! delete_suffix! encode! scrub! unicode_normalize!
|
|
35
|
+
bit_set bit_clear bit_flip bitwise_not! bitwise_and! bitwise_or! bitwise_xor!
|
|
32
36
|
].to_set.freeze
|
|
33
37
|
|
|
34
38
|
# The {MUTATORS} that CANNOT leave a non-empty receiver empty, and so keep a `non-empty-string` refinement:
|
|
35
39
|
# appenders (`<<`, `concat`, `insert`, `prepend`, `append_as_bytes`), and rewrites that keep at least one
|
|
36
40
|
# character (`setbyte`, `force_encoding`, the case mappings, `reverse!`, `succ!` / `next!`, `squeeze!`,
|
|
37
|
-
# `unicode_normalize
|
|
38
|
-
#
|
|
39
|
-
#
|
|
41
|
+
# `unicode_normalize!`, and the bit operations, which keep every byte). A whitelist, as
|
|
42
|
+
# `RefinementMutation::EMPTY_PRESERVING` is for Array and Hash: a mutator missing here retracts the witness, which
|
|
43
|
+
# costs precision, where a mutator missing from a list of emptiers kept the witness of a string it had just
|
|
44
|
+
# emptied — `tr!` and `tr_s!` did, since `"a".tr!("a", "")` is `""`.
|
|
40
45
|
EMPTY_PRESERVING = %i[
|
|
41
46
|
<< concat insert prepend append_as_bytes
|
|
42
47
|
setbyte force_encoding
|
|
43
48
|
upcase! downcase! capitalize! swapcase! reverse! succ! next! squeeze! unicode_normalize!
|
|
49
|
+
bit_set bit_clear bit_flip bitwise_not! bitwise_and! bitwise_or! bitwise_xor!
|
|
44
50
|
].to_set.freeze
|
|
45
51
|
|
|
46
52
|
# `tr!` / `tr_s!` map every character they match to one character of the replacement (padded with its last), so
|
|
@@ -333,7 +333,8 @@ module Rigor
|
|
|
333
333
|
|
|
334
334
|
def collect_included_do_calls(body)
|
|
335
335
|
body.body.flat_map do |stmt|
|
|
336
|
-
|
|
336
|
+
included_do = stmt.is_a?(Prism::CallNode) && stmt.receiver.nil? && stmt.name == :included
|
|
337
|
+
next [] unless included_do && stmt.block.is_a?(Prism::BlockNode)
|
|
337
338
|
|
|
338
339
|
block_body = stmt.block.body
|
|
339
340
|
next [] unless block_body.respond_to?(:body)
|
|
@@ -33,6 +33,10 @@ module Rigor
|
|
|
33
33
|
# Prism *parse* version (it decides which syntax is accepted), it is not threaded to the inference
|
|
34
34
|
# layer, and its default (`"4.0"`) is a default rather than a user statement about the runtime.
|
|
35
35
|
#
|
|
36
|
+
# The one exception is the Ruby-deprecation rule (the ADR-47 WD5 amendment, #1692). An explicit
|
|
37
|
+
# `target_ruby` is a user statement about the runtime, and that rule passes it as `stated_ruby:` to ask
|
|
38
|
+
# whether a deprecated call can run on the stated Ruby. Narrowing and the dead-arm filter never pass it.
|
|
39
|
+
#
|
|
36
40
|
# ## What is folded (and what is deliberately not)
|
|
37
41
|
#
|
|
38
42
|
# * `RUBY_VERSION <cmp> "x.y.z"` — compared with **String** semantics, because that is what runs.
|
|
@@ -91,9 +95,13 @@ module Rigor
|
|
|
91
95
|
private_constant :GEM_VERSION_PATH
|
|
92
96
|
|
|
93
97
|
# @param node — an `if` / `unless` predicate
|
|
94
|
-
# @
|
|
98
|
+
# @param stated_ruby — ADR-47 WD5 amendment (#1692): a `x.y.z` version to read `RUBY_VERSION` as instead of
|
|
99
|
+
# the analyzer's own, for the Ruby-deprecation rules only ({Configuration#stated_runtime_ruby}). Under it
|
|
100
|
+
# nothing else is readable — `RUBY_ENGINE` and the default gems' `VERSION`s belong to the analyzer's
|
|
101
|
+
# interpreter, not to the stated one — so a guard on them answers nil.
|
|
102
|
+
# @return `:truthy` / `:falsey` when the guard is decidable on the analyzer's Ruby (or on `stated_ruby`),
|
|
95
103
|
# otherwise nil (both arms stay live)
|
|
96
|
-
def verdict(node)
|
|
104
|
+
def verdict(node, stated_ruby: nil)
|
|
97
105
|
return nil unless node.is_a?(Prism::CallNode)
|
|
98
106
|
|
|
99
107
|
operator = node.name
|
|
@@ -103,19 +111,19 @@ module Rigor
|
|
|
103
111
|
arguments = node.arguments&.arguments
|
|
104
112
|
return nil unless arguments && arguments.length == 1
|
|
105
113
|
|
|
106
|
-
left = read_operand(node.receiver)
|
|
107
|
-
right = read_operand(arguments.first)
|
|
114
|
+
left = read_operand(node.receiver, stated_ruby)
|
|
115
|
+
right = read_operand(arguments.first, stated_ruby)
|
|
108
116
|
return nil if left.nil? || right.nil?
|
|
109
117
|
|
|
110
118
|
decide(left, right, operator)
|
|
111
119
|
end
|
|
112
120
|
|
|
113
121
|
# Reads one side of the comparison into `[kind, value]`, or nil when it is not readable.
|
|
114
|
-
def read_operand(node)
|
|
122
|
+
def read_operand(node, stated_ruby = nil)
|
|
115
123
|
case node
|
|
116
124
|
when Prism::StringNode then [:literal_string, node.unescaped]
|
|
117
|
-
when Prism::ConstantReadNode, Prism::ConstantPathNode then read_constant(node)
|
|
118
|
-
when Prism::CallNode then read_gem_version(node)
|
|
125
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode then read_constant(node, stated_ruby)
|
|
126
|
+
when Prism::CallNode then read_gem_version(node, stated_ruby)
|
|
119
127
|
end
|
|
120
128
|
end
|
|
121
129
|
private_class_method :read_operand
|
|
@@ -129,9 +137,10 @@ module Rigor
|
|
|
129
137
|
#
|
|
130
138
|
# The foldable set is unchanged and stays closed: an unqualified name folds only when it is one of
|
|
131
139
|
# {PREDEFINED}, a qualified one only when it is in {VERSION_CONSTANTS}.
|
|
132
|
-
def read_constant(node)
|
|
140
|
+
def read_constant(node, stated_ruby = nil)
|
|
133
141
|
path = Source::ConstantPath.qualified_name_or_nil(node)
|
|
134
142
|
return nil unless path
|
|
143
|
+
return (path == "RUBY_VERSION" ? [:string, stated_ruby] : nil) if stated_ruby
|
|
135
144
|
|
|
136
145
|
kind =
|
|
137
146
|
if path.include?("::")
|
|
@@ -149,14 +158,14 @@ module Rigor
|
|
|
149
158
|
# `Gem::Version.new(<readable>)`. The inner operand must be a plain version String — an engine name
|
|
150
159
|
# is not a version, and `Gem::Version.new` raises on anything `Gem::Version.correct?` rejects, so a
|
|
151
160
|
# malformed literal keeps both arms live instead of folding a guard the program cannot even reach.
|
|
152
|
-
def read_gem_version(node)
|
|
161
|
+
def read_gem_version(node, stated_ruby = nil)
|
|
153
162
|
return nil unless node.name == :new && node.block.nil?
|
|
154
163
|
return nil unless Source::ConstantPath.qualified_name_or_nil(node.receiver) == GEM_VERSION_PATH
|
|
155
164
|
|
|
156
165
|
arguments = node.arguments&.arguments
|
|
157
166
|
return nil unless arguments && arguments.length == 1
|
|
158
167
|
|
|
159
|
-
inner = read_operand(arguments.first)
|
|
168
|
+
inner = read_operand(arguments.first, stated_ruby)
|
|
160
169
|
return nil unless inner && STRING_KINDS.include?(inner.first)
|
|
161
170
|
return nil unless ::Gem::Version.correct?(inner.last)
|
|
162
171
|
|
|
@@ -46,6 +46,9 @@ module Rigor
|
|
|
46
46
|
|
|
47
47
|
def initialize(configuration:)
|
|
48
48
|
@configuration = configuration
|
|
49
|
+
@buffer_sources = {}
|
|
50
|
+
@disk_feature_files = nil
|
|
51
|
+
@environment_features = nil
|
|
49
52
|
@generation = 0
|
|
50
53
|
@environment = nil
|
|
51
54
|
@cache_store = nil
|
|
@@ -77,6 +80,9 @@ module Rigor
|
|
|
77
80
|
dependency_source_index: project_scan.dependency_source_index,
|
|
78
81
|
synthetic_method_index: project_scan.synthetic_method_index,
|
|
79
82
|
project_patched_methods: project_scan.project_patched_methods,
|
|
83
|
+
# Issue #1700 — the configured paths' files overlaid with the open buffers; {#note_buffers}
|
|
84
|
+
# rebuilds this when an edit or a save changes the set.
|
|
85
|
+
required_features: (@environment_features = required_features),
|
|
80
86
|
**ProjectEnvironment.dependency_discovery_options(@configuration)
|
|
81
87
|
)
|
|
82
88
|
end
|
|
@@ -119,12 +125,37 @@ module Rigor
|
|
|
119
125
|
diagnostics
|
|
120
126
|
end
|
|
121
127
|
|
|
128
|
+
# Issue #1700 — records the open buffers' text (absolute path => source) and rebuilds the environment
|
|
129
|
+
# when it changes the {Environment::RequiredFeatures} set the current one was built with: a buffer that
|
|
130
|
+
# gains or drops a `require "prime"` loads or unloads the vendored `prime` signatures. The server calls
|
|
131
|
+
# it on every open, change, save and close; neither `didSave` nor a `.rb` edit reaches
|
|
132
|
+
# `workspace/didChangeWatchedFiles`, which most clients send only for the files they were asked to watch.
|
|
133
|
+
# The configured paths are read once per environment and only the buffers are re-scanned, so a keystroke
|
|
134
|
+
# costs a substring test per open buffer.
|
|
135
|
+
def note_buffers(sources)
|
|
136
|
+
@buffer_sources = sources
|
|
137
|
+
return if @environment.nil? || required_features == @environment_features
|
|
138
|
+
|
|
139
|
+
invalidate!
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The required-feature set for the configured paths' files on disk, each open buffer read in place of its
|
|
143
|
+
# file.
|
|
144
|
+
def required_features
|
|
145
|
+
disk = (@disk_feature_files ||= Environment::RequiredFeatures.feature_files(@configuration))
|
|
146
|
+
found = disk.reject { |path, _| @buffer_sources.key?(path) }.values.flatten
|
|
147
|
+
found += @buffer_sources.values.flat_map { |source| Environment::RequiredFeatures.features_of(source) }
|
|
148
|
+
found.uniq.sort.freeze
|
|
149
|
+
end
|
|
150
|
+
|
|
122
151
|
# Drops every cached collaborator and bumps the generation. The next reader rebuilds from scratch.
|
|
123
152
|
# Triggered by `workspace/didChangeWatchedFiles` for project source files and by
|
|
124
153
|
# `workspace/didChangeConfiguration`.
|
|
125
154
|
def invalidate!
|
|
126
155
|
@generation += 1
|
|
127
156
|
@environment = nil
|
|
157
|
+
@environment_features = nil
|
|
158
|
+
@disk_feature_files = nil
|
|
128
159
|
@project_scan = nil
|
|
129
160
|
# The session's per-file cache was computed against the old environment / project scan, so it cannot
|
|
130
161
|
# outlive them. The next save round primes a fresh one.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../version"
|
|
4
4
|
require_relative "buffer_table"
|
|
5
|
+
require_relative "uri"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module LanguageServer
|
|
@@ -221,6 +222,7 @@ module Rigor
|
|
|
221
222
|
bytes: doc.fetch(:text),
|
|
222
223
|
version: doc.fetch(:version)
|
|
223
224
|
)
|
|
225
|
+
note_buffers
|
|
224
226
|
@publisher&.publish_for(uri)
|
|
225
227
|
nil
|
|
226
228
|
end
|
|
@@ -242,6 +244,7 @@ module Rigor
|
|
|
242
244
|
changes: changes,
|
|
243
245
|
version: doc.fetch(:version)
|
|
244
246
|
)
|
|
247
|
+
note_buffers
|
|
245
248
|
@publisher&.publish_for(uri)
|
|
246
249
|
nil
|
|
247
250
|
end
|
|
@@ -257,6 +260,7 @@ module Rigor
|
|
|
257
260
|
def handle_did_save(params)
|
|
258
261
|
uri = params.fetch(:textDocument).fetch(:uri)
|
|
259
262
|
@buffer_table.save(uri: uri)
|
|
263
|
+
note_buffers
|
|
260
264
|
@publisher&.publish_project(uri)
|
|
261
265
|
nil
|
|
262
266
|
end
|
|
@@ -361,10 +365,24 @@ module Rigor
|
|
|
361
365
|
doc = params.fetch(:textDocument)
|
|
362
366
|
uri = doc.fetch(:uri)
|
|
363
367
|
@buffer_table.close(uri: uri)
|
|
368
|
+
note_buffers
|
|
364
369
|
@publisher&.publish_empty(uri)
|
|
365
370
|
nil
|
|
366
371
|
end
|
|
367
372
|
|
|
373
|
+
# Issue #1700 — hands the open Ruby buffers to the project context, which rebuilds its environment when
|
|
374
|
+
# they change the set of required features the vendored signatures are gated on.
|
|
375
|
+
def note_buffers
|
|
376
|
+
return unless @project_context.respond_to?(:note_buffers)
|
|
377
|
+
|
|
378
|
+
sources = @buffer_table.uris.each_with_object({}) do |uri, acc|
|
|
379
|
+
path = Uri.to_path(uri)
|
|
380
|
+
entry = @buffer_table[uri]
|
|
381
|
+
acc[File.expand_path(path)] = entry.bytes if path&.end_with?(".rb") && entry
|
|
382
|
+
end
|
|
383
|
+
@project_context.note_buffers(sources)
|
|
384
|
+
end
|
|
385
|
+
|
|
368
386
|
def method_not_found(method)
|
|
369
387
|
rpc_error(ERROR_METHOD_NOT_FOUND, "method not found: #{method.inspect}")
|
|
370
388
|
end
|