rigortype 0.3.6 → 0.3.8
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 +1 -1
- data/data/builtins/ruby_core/date.yml +6 -3
- data/data/builtins/ruby_core/enumerable.yml +1 -0
- data/data/builtins/ruby_core/exception.yml +2 -1
- data/data/builtins/ruby_core/file.yml +28 -14
- data/data/builtins/ruby_core/hash.yml +5 -2
- data/data/builtins/ruby_core/io.yml +33 -16
- data/data/builtins/ruby_core/random.yml +2 -1
- data/data/builtins/ruby_core/re.yml +6 -3
- data/data/builtins/ruby_core/struct.yml +2 -0
- data/data/builtins/ruby_core/time.yml +2 -1
- data/data/gem_overlay/activesupport/core_ext.rbs +409 -11
- data/docs/handbook/11-sig-gen.md +24 -14
- data/docs/manual/02-cli-reference.md +23 -7
- data/docs/manual/04-diagnostics.md +1 -1
- data/docs/manual/05-inspecting-types.md +20 -8
- data/docs/manual/07-plugins.md +4 -0
- data/docs/manual/plugins/rigor-actionpack.md +61 -0
- data/docs/manual/plugins/rigor-activerecord.md +74 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
- data/docs/manual/plugins/rigor-sidekiq.md +22 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
- data/lib/rigor/analysis/check_rules.rb +383 -48
- data/lib/rigor/analysis/crash_signature.rb +188 -0
- data/lib/rigor/analysis/dependency_recorder.rb +18 -3
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +28 -0
- data/lib/rigor/analysis/incremental_session.rb +63 -16
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
- data/lib/rigor/analysis/reachability/graph.rb +14 -5
- data/lib/rigor/analysis/reachability/scan.rb +7 -3
- data/lib/rigor/analysis/result.rb +32 -0
- data/lib/rigor/analysis/run_cache_key.rb +41 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +233 -16
- data/lib/rigor/analysis/runner/pool_coordinator.rb +266 -47
- data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -2
- data/lib/rigor/analysis/runner.rb +212 -16
- data/lib/rigor/analysis/worker_session.rb +37 -3
- data/lib/rigor/bleeding_edge.rb +7 -6
- data/lib/rigor/builtins/hkt_builtins.rb +45 -6
- data/lib/rigor/builtins/imported_refinements.rb +11 -9
- data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
- data/lib/rigor/builtins/static_return_refinements.rb +20 -1
- data/lib/rigor/cache/annotation_location.rb +72 -0
- data/lib/rigor/cache/descriptor.rb +42 -2
- data/lib/rigor/cache/incremental_snapshot.rb +40 -3
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +70 -8
- data/lib/rigor/cache/store.rb +17 -1
- data/lib/rigor/cli/coverage_command.rb +23 -18
- data/lib/rigor/cli/coverage_mutation.rb +13 -17
- data/lib/rigor/cli/coverage_scan.rb +47 -6
- data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
- data/lib/rigor/cli/fused_protection_report.rb +23 -2
- data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
- data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
- data/lib/rigor/cli/mutation_protection_report.rb +31 -2
- data/lib/rigor/cli/sig_gen_command.rb +47 -0
- data/lib/rigor/cli/type_of_command.rb +188 -41
- data/lib/rigor/cli/type_of_renderer.rb +72 -3
- data/lib/rigor/cli/type_scan_command.rb +10 -1
- data/lib/rigor/cli.rb +1 -1
- data/lib/rigor/configuration.rb +7 -4
- data/lib/rigor/effects/plugin_facts.rb +1 -1
- data/lib/rigor/effects/scanner.rb +2 -3
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
- data/lib/rigor/environment/failure_slot.rb +28 -0
- data/lib/rigor/environment/lockfile_resolver.rb +15 -5
- data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
- data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
- data/lib/rigor/environment/rbs_loader.rb +743 -93
- data/lib/rigor/environment.rb +125 -25
- data/lib/rigor/inference/acceptance.rb +172 -20
- data/lib/rigor/inference/captured_locals.rb +62 -0
- data/lib/rigor/inference/content_join.rb +347 -0
- data/lib/rigor/inference/def_handle.rb +15 -3
- data/lib/rigor/inference/def_node_resolver.rb +62 -3
- data/lib/rigor/inference/expression_typer.rb +1020 -141
- data/lib/rigor/inference/fork_map.rb +6 -1
- data/lib/rigor/inference/hkt_reducer.rb +12 -1
- data/lib/rigor/inference/hkt_registry.rb +61 -3
- data/lib/rigor/inference/hkt_sugar_translator.rb +87 -0
- data/lib/rigor/inference/index_write_widening.rb +48 -0
- data/lib/rigor/inference/indexed_narrowing.rb +14 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
- data/lib/rigor/inference/method_dispatcher.rb +155 -14
- data/lib/rigor/inference/method_parameter_binder.rb +3 -3
- data/lib/rigor/inference/mutation_widening.rb +244 -174
- data/lib/rigor/inference/narrowing.rb +331 -27
- data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
- data/lib/rigor/inference/pre_eval_constants.rb +15 -0
- data/lib/rigor/inference/precision_scanner.rb +5 -2
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +89 -39
- data/lib/rigor/inference/scope_indexer.rb +1493 -140
- data/lib/rigor/inference/statement_evaluator.rb +276 -91
- data/lib/rigor/inference/struct_fold_safety.rb +186 -27
- data/lib/rigor/inference/version_guard.rb +229 -0
- data/lib/rigor/language_server/selection_range_provider.rb +1 -1
- data/lib/rigor/plugin/base.rb +3 -2
- data/lib/rigor/plugin/inflector.rb +14 -5
- data/lib/rigor/plugin/io_boundary.rb +107 -4
- data/lib/rigor/plugin/loader.rb +14 -0
- data/lib/rigor/plugin/registry.rb +25 -2
- data/lib/rigor/protection/analysis_guard.rb +60 -0
- data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
- data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
- data/lib/rigor/protection/discovery_seed.rb +1 -1
- data/lib/rigor/protection/measurement_integrity.rb +36 -0
- data/lib/rigor/protection/mutation_scanner.rb +61 -6
- data/lib/rigor/protection/mutator.rb +121 -18
- data/lib/rigor/rbs_extended/envelope_scanner.rb +8 -7
- data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
- data/lib/rigor/rbs_extended/reporter.rb +93 -13
- data/lib/rigor/rbs_extended.rb +6 -1
- data/lib/rigor/reflection.rb +182 -30
- data/lib/rigor/scope/discovery_index.rb +39 -1
- data/lib/rigor/scope.rb +385 -4
- data/lib/rigor/sig_gen/classification.rb +12 -1
- data/lib/rigor/sig_gen/generator.rb +320 -85
- data/lib/rigor/sig_gen/renderer.rb +14 -12
- data/lib/rigor/sig_gen/writer.rb +3 -0
- data/lib/rigor/source/constant_path.rb +79 -2
- data/lib/rigor/triage/catalogue.rb +1 -1
- data/lib/rigor/type/combinator.rb +10 -0
- data/lib/rigor/type/maybe.rb +47 -0
- data/lib/rigor/type/refined.rb +1 -2
- data/lib/rigor/type/result.rb +53 -0
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
- data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
- data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
- data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
- data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
- data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
- data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
- data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
- data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
- data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
- data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
- data/sig/rigor/analysis/baseline.rbs +1 -1
- data/sig/rigor/environment.rbs +9 -0
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/io_boundary.rbs +2 -0
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +25 -1
- data/sig/rigor/type.rbs +95 -1
- data/sig/rigor.rbs +16 -0
- metadata +71 -3
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Plugin
|
|
5
|
+
class FFI < Base
|
|
6
|
+
module TargetDetector
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Detects whether the project targets ffx or the classic ffi gem per WD6.
|
|
10
|
+
def detect(root:, config: {})
|
|
11
|
+
explicit = config["target"] || config[:target]
|
|
12
|
+
return explicit.to_sym if explicit && explicit != "auto"
|
|
13
|
+
|
|
14
|
+
return :ffx if extconf_uses_ffx?(root)
|
|
15
|
+
return :ffx if lockfile_has_ffx?(root)
|
|
16
|
+
|
|
17
|
+
:ffi
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def extconf_uses_ffx?(root)
|
|
21
|
+
return false if root.nil?
|
|
22
|
+
|
|
23
|
+
Dir.glob(File.join(root.to_s, "ext/**/extconf.rb")).any? do |extconf|
|
|
24
|
+
File.read(extconf).include?("FFX.create_makefile")
|
|
25
|
+
rescue SystemCallError
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def lockfile_has_ffx?(root)
|
|
31
|
+
return false if root.nil?
|
|
32
|
+
|
|
33
|
+
lockfile = File.join(root.to_s, "Gemfile.lock")
|
|
34
|
+
return false unless File.file?(lockfile)
|
|
35
|
+
|
|
36
|
+
File.foreach(lockfile) do |line|
|
|
37
|
+
return true if line =~ /^\s{4}ffx\s/
|
|
38
|
+
end
|
|
39
|
+
false
|
|
40
|
+
rescue SystemCallError
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Plugin
|
|
5
|
+
class FFI < Base
|
|
6
|
+
module Types
|
|
7
|
+
# The 25 primitive type symbols supported by ffx and classic FFI
|
|
8
|
+
FFX_PRIMITIVE_TYPES = %i[
|
|
9
|
+
void
|
|
10
|
+
int uint short ushort long ulong
|
|
11
|
+
int8 int16 int32 int64
|
|
12
|
+
uint8 uint16 uint32 uint64
|
|
13
|
+
long_long ulong_long
|
|
14
|
+
size_t
|
|
15
|
+
float double
|
|
16
|
+
bool
|
|
17
|
+
char uchar
|
|
18
|
+
string
|
|
19
|
+
pointer
|
|
20
|
+
].to_set.freeze
|
|
21
|
+
|
|
22
|
+
# Regex anchor must match end of string \z
|
|
23
|
+
NOMINAL_OPAQUE_REGEX = Regexp.new("(_ptr|_handle|Ptr|Handle)\\z")
|
|
24
|
+
|
|
25
|
+
BOOL_TYPE = Rigor::Type::Combinator.union(
|
|
26
|
+
Rigor::Type::Combinator.constant_of(true),
|
|
27
|
+
Rigor::Type::Combinator.constant_of(false)
|
|
28
|
+
).freeze
|
|
29
|
+
|
|
30
|
+
NIL_TYPE = Rigor::Type::Combinator.constant_of(nil)
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
def nominal_opaque_pointer?(alias_name, exceptions: [])
|
|
35
|
+
sym_str = alias_name.to_s
|
|
36
|
+
return false if Array(exceptions).map(&:to_s).include?(sym_str)
|
|
37
|
+
|
|
38
|
+
NOMINAL_OPAQUE_REGEX.match?(sym_str)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def camelize(name)
|
|
42
|
+
name.to_s.split("_").map(&:capitalize).join
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def return_type_for(type_sym, target: :ffi, module_name: nil, exceptions: [], typedefs: {}, callbacks: {})
|
|
46
|
+
sym = type_sym.is_a?(Symbol) ? type_sym : type_sym.to_s.to_sym
|
|
47
|
+
|
|
48
|
+
# Check if symbol is a defined callback
|
|
49
|
+
if callbacks.key?(sym)
|
|
50
|
+
return target == :ffx ? Rigor::Type::Combinator.nominal_of("Integer") : Rigor::Type::Combinator.nominal_of("FFI::Function")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if typedefs.key?(sym)
|
|
54
|
+
underlying = typedefs[sym]
|
|
55
|
+
if underlying == :pointer && nominal_opaque_pointer?(sym, exceptions: exceptions)
|
|
56
|
+
nominal = camelize(sym)
|
|
57
|
+
nominal = "#{module_name}::#{nominal}" if module_name && !module_name.empty?
|
|
58
|
+
return Rigor::Type::Combinator.nominal_of(nominal)
|
|
59
|
+
end
|
|
60
|
+
sym = underlying
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
case sym
|
|
64
|
+
when :void
|
|
65
|
+
NIL_TYPE
|
|
66
|
+
when :int, :uint, :short, :ushort, :long, :ulong,
|
|
67
|
+
:int8, :int16, :int32, :int64,
|
|
68
|
+
:uint8, :uint16, :uint32, :uint64,
|
|
69
|
+
:long_long, :ulong_long, :size_t, :char, :uchar
|
|
70
|
+
Rigor::Type::Combinator.nominal_of("Integer")
|
|
71
|
+
when :float, :double
|
|
72
|
+
Rigor::Type::Combinator.nominal_of("Float")
|
|
73
|
+
when :bool
|
|
74
|
+
BOOL_TYPE
|
|
75
|
+
when :string
|
|
76
|
+
Rigor::Type::Combinator.nominal_of("String")
|
|
77
|
+
when :pointer
|
|
78
|
+
if target == :ffx
|
|
79
|
+
Rigor::Type::Combinator.nominal_of("Integer")
|
|
80
|
+
else
|
|
81
|
+
Rigor::Type::Combinator.nominal_of("FFI::Pointer")
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
if nominal_opaque_pointer?(sym, exceptions: exceptions)
|
|
85
|
+
nominal = camelize(sym)
|
|
86
|
+
nominal = "#{module_name}::#{nominal}" if module_name && !module_name.empty?
|
|
87
|
+
Rigor::Type::Combinator.nominal_of(nominal)
|
|
88
|
+
else
|
|
89
|
+
Rigor::Type::Combinator.top
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Universal parameter widening per WD7
|
|
95
|
+
def param_type_for(type_sym, target: :ffi, module_name: nil, exceptions: [], typedefs: {}, callbacks: {})
|
|
96
|
+
sym = type_sym.is_a?(Symbol) ? type_sym : type_sym.to_s.to_sym
|
|
97
|
+
|
|
98
|
+
if callbacks.key?(sym)
|
|
99
|
+
return Rigor::Type::Combinator.union(
|
|
100
|
+
Rigor::Type::Combinator.nominal_of("Proc"),
|
|
101
|
+
Rigor::Type::Combinator.nominal_of("Method"),
|
|
102
|
+
Rigor::Type::Combinator.nominal_of("FFI::Pointer"),
|
|
103
|
+
Rigor::Type::Combinator.nominal_of("FFI::Function"),
|
|
104
|
+
NIL_TYPE
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if typedefs.key?(sym)
|
|
109
|
+
underlying = typedefs[sym]
|
|
110
|
+
if underlying == :pointer && nominal_opaque_pointer?(sym, exceptions: exceptions)
|
|
111
|
+
nominal = camelize(sym)
|
|
112
|
+
nominal = "#{module_name}::#{nominal}" if module_name && !module_name.empty?
|
|
113
|
+
return Rigor::Type::Combinator.union(
|
|
114
|
+
Rigor::Type::Combinator.nominal_of(nominal),
|
|
115
|
+
Rigor::Type::Combinator.nominal_of("FFI::Pointer"),
|
|
116
|
+
NIL_TYPE
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
sym = underlying
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
case sym
|
|
123
|
+
when :pointer
|
|
124
|
+
if target == :ffx
|
|
125
|
+
Rigor::Type::Combinator.union(
|
|
126
|
+
Rigor::Type::Combinator.nominal_of("Integer"),
|
|
127
|
+
NIL_TYPE
|
|
128
|
+
)
|
|
129
|
+
else
|
|
130
|
+
Rigor::Type::Combinator.union(
|
|
131
|
+
Rigor::Type::Combinator.nominal_of("FFI::Pointer"),
|
|
132
|
+
Rigor::Type::Combinator.nominal_of("FFI::MemoryPointer"),
|
|
133
|
+
Rigor::Type::Combinator.nominal_of("FFI::AutoPointer"),
|
|
134
|
+
Rigor::Type::Combinator.nominal_of("FFI::Buffer"),
|
|
135
|
+
Rigor::Type::Combinator.nominal_of("Integer"),
|
|
136
|
+
Rigor::Type::Combinator.nominal_of("String"),
|
|
137
|
+
NIL_TYPE
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
when :int, :uint, :short, :ushort, :long, :ulong,
|
|
141
|
+
:int8, :int16, :int32, :int64,
|
|
142
|
+
:uint8, :uint16, :uint32, :uint64,
|
|
143
|
+
:long_long, :ulong_long, :size_t, :char, :uchar
|
|
144
|
+
Rigor::Type::Combinator.nominal_of("Integer")
|
|
145
|
+
when :float, :double
|
|
146
|
+
Rigor::Type::Combinator.union(
|
|
147
|
+
Rigor::Type::Combinator.nominal_of("Float"),
|
|
148
|
+
Rigor::Type::Combinator.nominal_of("Integer")
|
|
149
|
+
)
|
|
150
|
+
when :bool
|
|
151
|
+
BOOL_TYPE
|
|
152
|
+
when :string
|
|
153
|
+
Rigor::Type::Combinator.nominal_of("String")
|
|
154
|
+
else
|
|
155
|
+
Rigor::Type::Combinator.top
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/plugin"
|
|
4
|
+
require_relative "ffi/binding_recognizer"
|
|
5
|
+
require_relative "ffi/types"
|
|
6
|
+
require_relative "ffi/target_detector"
|
|
7
|
+
require_relative "ffi/analyzer"
|
|
8
|
+
require_relative "ffi/catalog"
|
|
9
|
+
require_relative "ffi/discoverer"
|
|
10
|
+
|
|
11
|
+
module Rigor
|
|
12
|
+
module Plugin
|
|
13
|
+
class FFI < Base
|
|
14
|
+
manifest(
|
|
15
|
+
id: "ffi",
|
|
16
|
+
version: "0.1.0",
|
|
17
|
+
description: "Models FFI library bindings, struct layouts, callbacks, carrier types, and ffx target compatibility.",
|
|
18
|
+
signature_paths: ["sig"]
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
producer :ffi_catalog do
|
|
22
|
+
root = config["root"] || Dir.pwd
|
|
23
|
+
Discoverer.discover(root: root)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Dynamic return rule for attached FFI functions
|
|
27
|
+
# Gated on known FFI library receivers and attached function names
|
|
28
|
+
dynamic_return receivers: -> { producer_value(:ffi_catalog)&.libraries || [] },
|
|
29
|
+
methods: -> { producer_value(:ffi_catalog)&.function_method_names || [] } do |call_node, scope|
|
|
30
|
+
catalog = producer_value(:ffi_catalog)
|
|
31
|
+
next nil if catalog.nil?
|
|
32
|
+
|
|
33
|
+
receiver_type = call_node.receiver ? scope&.type_of(call_node.receiver) : scope&.self_type
|
|
34
|
+
receiver_class = case receiver_type
|
|
35
|
+
when Rigor::Type::Nominal, Rigor::Type::Singleton then receiver_type.class_name
|
|
36
|
+
end
|
|
37
|
+
next nil if receiver_class.nil?
|
|
38
|
+
|
|
39
|
+
fact = catalog.function_for(receiver_class, call_node.name)
|
|
40
|
+
next nil if fact.nil?
|
|
41
|
+
|
|
42
|
+
Types.return_type_for(
|
|
43
|
+
fact.return_type,
|
|
44
|
+
target: @target || :ffi,
|
|
45
|
+
module_name: fact.receiver_name,
|
|
46
|
+
exceptions: @exceptions || [],
|
|
47
|
+
typedefs: catalog.typedefs,
|
|
48
|
+
callbacks: catalog.callbacks
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Dynamic return rule for FFI struct field accessors
|
|
53
|
+
# Gated on known FFI struct class names and field names
|
|
54
|
+
dynamic_return receivers: -> { producer_value(:ffi_catalog)&.struct_names || [] },
|
|
55
|
+
methods: -> { producer_value(:ffi_catalog)&.struct_field_names || [] } do |call_node, scope|
|
|
56
|
+
catalog = producer_value(:ffi_catalog)
|
|
57
|
+
next nil if catalog.nil?
|
|
58
|
+
|
|
59
|
+
receiver_type = call_node.receiver ? scope&.type_of(call_node.receiver) : scope&.self_type
|
|
60
|
+
receiver_class = case receiver_type
|
|
61
|
+
when Rigor::Type::Nominal, Rigor::Type::Singleton then receiver_type.class_name
|
|
62
|
+
end
|
|
63
|
+
next nil if receiver_class.nil?
|
|
64
|
+
|
|
65
|
+
fields = catalog.struct_fields(receiver_class)
|
|
66
|
+
next nil if fields.nil?
|
|
67
|
+
|
|
68
|
+
if call_node.name == :[]
|
|
69
|
+
arg = call_node.arguments&.arguments&.first
|
|
70
|
+
field_sym = Analyzer.extract_symbol(arg)
|
|
71
|
+
field_type = fields[field_sym]
|
|
72
|
+
next nil if field_type.nil?
|
|
73
|
+
|
|
74
|
+
Types.return_type_for(
|
|
75
|
+
field_type,
|
|
76
|
+
target: @target || :ffi,
|
|
77
|
+
module_name: receiver_class,
|
|
78
|
+
exceptions: @exceptions || [],
|
|
79
|
+
typedefs: catalog.typedefs,
|
|
80
|
+
callbacks: catalog.callbacks
|
|
81
|
+
)
|
|
82
|
+
elsif call_node.name.to_s.end_with?("=")
|
|
83
|
+
# For both .id = val and [:id] = val, the assigned value is the LAST argument
|
|
84
|
+
val_node = call_node.arguments&.arguments&.last
|
|
85
|
+
val_node ? scope&.type_of(val_node) : Rigor::Type::Combinator.top
|
|
86
|
+
elsif (field_type = fields[call_node.name])
|
|
87
|
+
Types.return_type_for(
|
|
88
|
+
field_type,
|
|
89
|
+
target: @target || :ffi,
|
|
90
|
+
module_name: receiver_class,
|
|
91
|
+
exceptions: @exceptions || [],
|
|
92
|
+
typedefs: catalog.typedefs,
|
|
93
|
+
callbacks: catalog.callbacks
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
node_rule Prism::CallNode do |node, _scope, path|
|
|
99
|
+
Analyzer.ffx_diagnostics_for_call(node, path: path, target: @target || :ffi)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
node_rule Prism::ClassNode do |node, _scope, path|
|
|
103
|
+
Analyzer.ffx_diagnostics_for_class(node, path: path, target: @target || :ffi)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def init(_services)
|
|
107
|
+
root = config["root"] || Dir.pwd
|
|
108
|
+
@target = TargetDetector.detect(root: root, config: config)
|
|
109
|
+
@exceptions = config["exceptions"] || []
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def prepare(_services)
|
|
113
|
+
root = config["root"] || Dir.pwd
|
|
114
|
+
@target = TargetDetector.detect(root: root, config: config)
|
|
115
|
+
@exceptions = config["exceptions"] || []
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
Rigor::Plugin.register(Rigor::Plugin::FFI)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module FFI
|
|
2
|
+
class Pointer
|
|
3
|
+
NULL: Pointer
|
|
4
|
+
def self.new: (Integer address) -> instance
|
|
5
|
+
def null?: () -> bool
|
|
6
|
+
def address: () -> Integer
|
|
7
|
+
def +: (Integer offset) -> Pointer
|
|
8
|
+
def read_string: (?Integer? length) -> String
|
|
9
|
+
def read_bytes: (Integer length) -> String
|
|
10
|
+
def write_string: (String str, ?Integer? length) -> Pointer
|
|
11
|
+
def write_bytes: (String str, ?Integer offset, ?Integer? length) -> Pointer
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class MemoryPointer < Pointer
|
|
15
|
+
def self.new: (Symbol | Integer type, ?Integer count, ?bool clear) -> instance
|
|
16
|
+
def self.from_string: (String s) -> instance
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class AutoPointer < Pointer
|
|
20
|
+
def self.new: (Pointer ptr, ?(Method | Proc | Symbol)? releaser) -> instance
|
|
21
|
+
def free: () -> void
|
|
22
|
+
def autorelease=: (bool val) -> bool
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Buffer < Pointer
|
|
26
|
+
def self.new: (Symbol | Integer type, ?Integer size, ?Integer count) -> instance
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class Function < Pointer
|
|
30
|
+
def self.new: (Symbol return_type, Array[Symbol] param_types, ?Hash[Symbol, untyped] options) ?{ (*untyped) -> untyped } -> instance
|
|
31
|
+
def call: (*untyped) -> untyped
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class Struct
|
|
35
|
+
def self.layout: (*untyped) -> void
|
|
36
|
+
def self.by_ref: () -> untyped
|
|
37
|
+
def self.ptr: () -> untyped
|
|
38
|
+
def self.size: () -> Integer
|
|
39
|
+
def pointer: () -> Pointer
|
|
40
|
+
def to_ptr: () -> Pointer
|
|
41
|
+
def null?: () -> bool
|
|
42
|
+
def []: (Symbol | String name) -> untyped
|
|
43
|
+
def []=: (Symbol | String name, untyped value) -> untyped
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Union < Struct
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class ManagedStruct < Struct
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module Library
|
|
53
|
+
def ffi_lib: (*(String | Array[String]) libs) -> untyped
|
|
54
|
+
def attach_function: (*untyped) -> untyped
|
|
55
|
+
def callback: (Symbol name, Array[Symbol] param_types, Symbol return_type) -> untyped
|
|
56
|
+
def typedef: (Symbol | String old_type, Symbol | String new_type) -> untyped
|
|
57
|
+
def enum: (*untyped) -> untyped
|
|
58
|
+
def bitmask: (*untyped) -> untyped
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module FFX
|
|
63
|
+
module Library
|
|
64
|
+
def ffi_lib: (*(String | Array[String]) libs) -> untyped
|
|
65
|
+
def attach_function: (*untyped) -> untyped
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/plugin"
|
|
4
|
+
require "rigor-ffi"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Plugin
|
|
8
|
+
class FFIRZMQ < Base
|
|
9
|
+
manifest(
|
|
10
|
+
id: "ffi-rzmq",
|
|
11
|
+
version: "0.1.0",
|
|
12
|
+
description: "Models ffi-rzmq / LibZMQ wrappers, socket operations, and cross-gem bindings.",
|
|
13
|
+
signature_paths: ["sig"]
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Rigor::Plugin.register(Rigor::Plugin::FFIRZMQ)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module ZMQ
|
|
2
|
+
REQ: Integer
|
|
3
|
+
REP: Integer
|
|
4
|
+
PUB: Integer
|
|
5
|
+
SUB: Integer
|
|
6
|
+
DONTWAIT: Integer
|
|
7
|
+
SNDMORE: Integer
|
|
8
|
+
|
|
9
|
+
class Context
|
|
10
|
+
def self.new: (?Integer io_threads) -> instance
|
|
11
|
+
def socket: (Integer type) -> Socket?
|
|
12
|
+
def terminate: () -> Integer
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Socket
|
|
16
|
+
def connect: (String endpoint) -> Integer
|
|
17
|
+
def bind: (String endpoint) -> Integer
|
|
18
|
+
def send_string: (String message, ?Integer flags) -> Integer
|
|
19
|
+
def recv_string: (String message, ?Integer flags) -> Integer
|
|
20
|
+
def close: () -> Integer
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module LibZMQ
|
|
25
|
+
def self.zmq_ctx_new: () -> FFI::Pointer
|
|
26
|
+
def self.zmq_socket: (FFI::Pointer context, Integer type) -> FFI::Pointer
|
|
27
|
+
def self.zmq_close: (FFI::Pointer socket) -> Integer
|
|
28
|
+
def self.zmq_msg_init: (FFI::Pointer msg) -> Integer
|
|
29
|
+
end
|
|
@@ -254,7 +254,7 @@ module Rigor
|
|
|
254
254
|
# Mirror of `extract_nullability` but reads the `required:` kwarg, defaulting to `false`
|
|
255
255
|
# (graphql-ruby's argument default — the OPPOSITE polarity of `field`'s `null:` / nullability
|
|
256
256
|
# default).
|
|
257
|
-
# rubocop:disable Naming/PredicateMethod -- extractor returns the literal required value
|
|
257
|
+
# rubocop:disable-next Naming/PredicateMethod -- extractor returns the literal required value
|
|
258
258
|
def extract_required_flag(args)
|
|
259
259
|
kwargs = args.last
|
|
260
260
|
return false unless kwargs.is_a?(Prism::KeywordHashNode)
|
|
@@ -270,7 +270,6 @@ module Rigor
|
|
|
270
270
|
else false
|
|
271
271
|
end
|
|
272
272
|
end
|
|
273
|
-
# rubocop:enable Naming/PredicateMethod
|
|
274
273
|
private_class_method :extract_required_flag
|
|
275
274
|
|
|
276
275
|
# `field :name, Type, null: false` shape. The first positional is a Symbol (field name); the
|
|
@@ -331,7 +330,7 @@ module Rigor
|
|
|
331
330
|
|
|
332
331
|
# Defaults to `true` (matches graphql-ruby's `field` default nullability). Looks for an explicit
|
|
333
332
|
# `null:` keyword and reads its boolean literal.
|
|
334
|
-
# rubocop:disable Naming/PredicateMethod -- extractor returns the literal nullability value
|
|
333
|
+
# rubocop:disable-next Naming/PredicateMethod -- extractor returns the literal nullability value
|
|
335
334
|
def extract_nullability(args)
|
|
336
335
|
kwargs = args.last
|
|
337
336
|
return true unless kwargs.is_a?(Prism::KeywordHashNode)
|
|
@@ -347,7 +346,6 @@ module Rigor
|
|
|
347
346
|
else true
|
|
348
347
|
end
|
|
349
348
|
end
|
|
350
|
-
# rubocop:enable Naming/PredicateMethod
|
|
351
349
|
private_class_method :extract_nullability
|
|
352
350
|
|
|
353
351
|
# Returns the constant chain as an Array of String segments (`["GraphQL", "Schema", "Object"]`).
|
|
@@ -69,7 +69,8 @@ module Rigor
|
|
|
69
69
|
def ruby_files_under(roots)
|
|
70
70
|
roots.flat_map do |root|
|
|
71
71
|
absolute = File.expand_path(root)
|
|
72
|
-
|
|
72
|
+
# ADR-45 WD1b (#613) — boundary-probed: a root that appears later invalidates the warm run.
|
|
73
|
+
next [] unless @io_boundary.directory?(absolute)
|
|
73
74
|
|
|
74
75
|
Dir.glob(File.join(absolute, "**", "*.rb"))
|
|
75
76
|
end
|
|
@@ -58,7 +58,8 @@ module Rigor
|
|
|
58
58
|
def ruby_files_under(roots)
|
|
59
59
|
roots.flat_map do |root|
|
|
60
60
|
absolute = File.expand_path(root)
|
|
61
|
-
|
|
61
|
+
# ADR-45 WD1b (#613) — boundary-probed: a root that appears later invalidates the warm run.
|
|
62
|
+
next [] unless @io_boundary.directory?(absolute)
|
|
62
63
|
|
|
63
64
|
Dir.glob(File.join(absolute, "**", "*.rb"))
|
|
64
65
|
end
|
|
@@ -88,7 +88,8 @@ module Rigor
|
|
|
88
88
|
def locale_files
|
|
89
89
|
@search_paths.flat_map do |root|
|
|
90
90
|
absolute = File.expand_path(root)
|
|
91
|
-
|
|
91
|
+
# ADR-45 WD1b (#613) — boundary-probed: a root that appears later invalidates the warm run.
|
|
92
|
+
next [] unless @io_boundary.directory?(absolute)
|
|
92
93
|
|
|
93
94
|
Dir.glob(File.join(absolute, "**", "*.{yml,yaml}"))
|
|
94
95
|
end.sort
|
|
@@ -177,7 +177,8 @@ module Rigor
|
|
|
177
177
|
def view_files
|
|
178
178
|
@view_search_paths.flat_map do |root|
|
|
179
179
|
absolute = File.expand_path(root)
|
|
180
|
-
|
|
180
|
+
# ADR-45 WD1b (#613) — boundary-probed: a root that appears later invalidates the warm run.
|
|
181
|
+
next [] unless io_boundary.directory?(absolute)
|
|
181
182
|
|
|
182
183
|
Dir.glob(File.join(absolute, "**", "*.{erb,haml,slim}"))
|
|
183
184
|
end.sort
|
|
@@ -164,7 +164,8 @@ module Rigor
|
|
|
164
164
|
def each_ruby_file(dirs, &)
|
|
165
165
|
dirs.each do |dir|
|
|
166
166
|
absolute = File.expand_path(dir)
|
|
167
|
-
|
|
167
|
+
# ADR-45 WD1b (#613) — boundary-probed: a root that appears later invalidates the warm run.
|
|
168
|
+
next unless io_boundary.directory?(absolute)
|
|
168
169
|
|
|
169
170
|
Dir.glob(File.join(absolute, "**", "*.rb")).each(&)
|
|
170
171
|
end
|