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,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/plugin"
|
|
4
|
+
require "rigor/inference/hkt_registry"
|
|
5
|
+
require "rigor/inference/hkt_body_parser"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Plugin
|
|
9
|
+
# rigor-dry-monads — Tier A plugin for dry-monads Result and Maybe carriers per
|
|
10
|
+
# [ADR-20](../../../../../docs/adr/20-lightweight-hkt.md) Slice 4.
|
|
11
|
+
class DryMonads < Rigor::Plugin::Base
|
|
12
|
+
RESULT_REGISTRATION = Inference::HktRegistry::Registration.new(
|
|
13
|
+
uri: :"dry_monads::result",
|
|
14
|
+
arity: 2,
|
|
15
|
+
variance: %i[out out],
|
|
16
|
+
bound: Type::Combinator.result_of(Type::Combinator.untyped, Type::Combinator.untyped)
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
MAYBE_REGISTRATION = Inference::HktRegistry::Registration.new(
|
|
20
|
+
uri: :"dry_monads::maybe",
|
|
21
|
+
arity: 1,
|
|
22
|
+
variance: %i[out],
|
|
23
|
+
bound: Type::Combinator.maybe_of(Type::Combinator.untyped)
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
RESULT_DEFINITION = Inference::HktRegistry.definition_with_body_tree(
|
|
27
|
+
uri: :"dry_monads::result",
|
|
28
|
+
params: %i[T E],
|
|
29
|
+
body_tree: Inference::HktBodyParser.parse("Result[T, E]", params: %i[T E]),
|
|
30
|
+
source_path: __FILE__,
|
|
31
|
+
source_line: __LINE__
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
MAYBE_DEFINITION = Inference::HktRegistry.definition_with_body_tree(
|
|
35
|
+
uri: :"dry_monads::maybe",
|
|
36
|
+
params: %i[T],
|
|
37
|
+
body_tree: Inference::HktBodyParser.parse("Maybe[T]", params: %i[T]),
|
|
38
|
+
source_path: __FILE__,
|
|
39
|
+
source_line: __LINE__
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
CONSTRUCTOR_METHODS = %i[Success Failure Some None].freeze
|
|
43
|
+
UNWRAP_METHODS = %i[value! failure value_or].freeze
|
|
44
|
+
|
|
45
|
+
manifest(
|
|
46
|
+
id: "dry-monads",
|
|
47
|
+
version: "0.1.0",
|
|
48
|
+
description: "Lightweight HKT support for dry-monads Result and Maybe carriers",
|
|
49
|
+
hkt_registrations: [RESULT_REGISTRATION, MAYBE_REGISTRATION],
|
|
50
|
+
hkt_definitions: [RESULT_DEFINITION, MAYBE_DEFINITION]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
dynamic_return methods: -> { CONSTRUCTOR_METHODS } do |call_node, scope|
|
|
54
|
+
args = call_node.arguments&.arguments || []
|
|
55
|
+
case call_node.name
|
|
56
|
+
when :Success
|
|
57
|
+
val = args.first ? scope.type_of(args.first) : Type::Combinator.untyped
|
|
58
|
+
Type::Combinator.result_of(val, Type::Combinator.bot)
|
|
59
|
+
when :Failure
|
|
60
|
+
err = args.first ? scope.type_of(args.first) : Type::Combinator.untyped
|
|
61
|
+
Type::Combinator.result_of(Type::Combinator.bot, err)
|
|
62
|
+
when :Some
|
|
63
|
+
val = args.first ? scope.type_of(args.first) : Type::Combinator.untyped
|
|
64
|
+
Type::Combinator.maybe_of(val)
|
|
65
|
+
when :None
|
|
66
|
+
Type::Combinator.maybe_of(Type::Combinator.bot)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
dynamic_return receivers: %w[Dry::Monads::Result Dry::Monads::Maybe Result Maybe],
|
|
71
|
+
methods: -> { UNWRAP_METHODS } do |call_node, scope|
|
|
72
|
+
receiver = call_node.receiver
|
|
73
|
+
next nil if receiver.nil?
|
|
74
|
+
|
|
75
|
+
rec_type = scope.type_of(receiver)
|
|
76
|
+
args = call_node.arguments&.arguments || []
|
|
77
|
+
case call_node.name
|
|
78
|
+
when :value!
|
|
79
|
+
if rec_type.is_a?(Type::Result)
|
|
80
|
+
rec_type.ok_type
|
|
81
|
+
elsif rec_type.is_a?(Type::Maybe)
|
|
82
|
+
rec_type.value_type
|
|
83
|
+
end
|
|
84
|
+
when :failure
|
|
85
|
+
rec_type.err_type if rec_type.is_a?(Type::Result)
|
|
86
|
+
when :value_or
|
|
87
|
+
fallback = args.first ? scope.type_of(args.first) : Type::Combinator.untyped
|
|
88
|
+
if rec_type.is_a?(Type::Result)
|
|
89
|
+
Type::Combinator.union(rec_type.ok_type, fallback)
|
|
90
|
+
elsif rec_type.is_a?(Type::Maybe)
|
|
91
|
+
Type::Combinator.union(rec_type.value_type, fallback)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
Rigor::Plugin.register(DryMonads)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rigor/plugin"
|
|
4
|
+
require "rigor-ffi"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Plugin
|
|
8
|
+
class Ethon < Base
|
|
9
|
+
manifest(
|
|
10
|
+
id: "ethon",
|
|
11
|
+
version: "0.1.0",
|
|
12
|
+
description: "Models Ethon / Libcurl options, dynamic return types, and multi-loop operations.",
|
|
13
|
+
signature_paths: ["sig"]
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Option catalogue and return types for Easy methods
|
|
17
|
+
EASY_RETURN_TYPES = {
|
|
18
|
+
perform: -> { Rigor::Type::Combinator.nominal_of("Integer") },
|
|
19
|
+
response_code: -> { Rigor::Type::Combinator.nominal_of("Integer") },
|
|
20
|
+
return_code: -> { Rigor::Type::Combinator.nominal_of("Symbol") },
|
|
21
|
+
total_time: -> { Rigor::Type::Combinator.nominal_of("Float") },
|
|
22
|
+
response_body: -> { Rigor::Type::Combinator.nominal_of("String") },
|
|
23
|
+
response_headers: -> { Rigor::Type::Combinator.nominal_of("String") }
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
dynamic_return receivers: ["Ethon::Easy"], methods: EASY_RETURN_TYPES.keys do |call_node, _scope|
|
|
27
|
+
builder = EASY_RETURN_TYPES[call_node.name]
|
|
28
|
+
builder&.call
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Rigor::Plugin.register(Rigor::Plugin::Ethon)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Ethon
|
|
2
|
+
class Easy
|
|
3
|
+
def initialize: (?Hash[Symbol, untyped] options) -> void
|
|
4
|
+
def url=: (String url) -> String
|
|
5
|
+
def url: () -> String?
|
|
6
|
+
def perform: () -> Integer
|
|
7
|
+
def response_code: () -> Integer
|
|
8
|
+
def return_code: () -> Symbol
|
|
9
|
+
def total_time: () -> Float
|
|
10
|
+
def response_body: () -> String
|
|
11
|
+
def response_headers: () -> String
|
|
12
|
+
def handle: () -> FFI::Pointer
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Multi
|
|
16
|
+
def initialize: (?Hash[Symbol, untyped] options) -> void
|
|
17
|
+
def add: (Easy easy) -> void
|
|
18
|
+
def perform: () -> void
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module Curl
|
|
22
|
+
def self.easy_init: () -> FFI::Pointer
|
|
23
|
+
def self.easy_cleanup: (FFI::Pointer handle) -> void
|
|
24
|
+
def self.easy_perform: (FFI::Pointer handle) -> Integer
|
|
25
|
+
def self.set_option: (Symbol option, untyped value, FFI::Pointer handle) -> Integer
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -51,9 +51,10 @@ module Rigor
|
|
|
51
51
|
def ruby_files_under(roots)
|
|
52
52
|
roots.flat_map do |root|
|
|
53
53
|
absolute = File.expand_path(root)
|
|
54
|
-
|
|
54
|
+
# ADR-45 WD1b (#613) — boundary-probed: a root that appears later invalidates the warm run.
|
|
55
|
+
if @io_boundary.file?(absolute)
|
|
55
56
|
[absolute]
|
|
56
|
-
elsif
|
|
57
|
+
elsif @io_boundary.directory?(absolute)
|
|
57
58
|
Dir.glob(File.join(absolute, "**", "*.rb"))
|
|
58
59
|
else
|
|
59
60
|
[]
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
class FFI < Base
|
|
8
|
+
module Analyzer
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def extract_symbol(node)
|
|
12
|
+
case node
|
|
13
|
+
when Prism::SymbolNode
|
|
14
|
+
node.unescaped.to_sym
|
|
15
|
+
when Prism::StringNode
|
|
16
|
+
node.unescaped.to_sym
|
|
17
|
+
when Prism::CallNode
|
|
18
|
+
extract_symbol(node.receiver) if node.name == :to_sym && node.receiver
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def extract_type_symbol(node)
|
|
23
|
+
case node
|
|
24
|
+
when Prism::SymbolNode
|
|
25
|
+
node.unescaped.to_sym
|
|
26
|
+
when Prism::StringNode
|
|
27
|
+
node.unescaped.to_sym
|
|
28
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
|
29
|
+
node.slice.to_sym
|
|
30
|
+
when Prism::CallNode
|
|
31
|
+
# e.g. MyStruct.ptr or MyStruct.by_ref
|
|
32
|
+
node.slice.to_sym
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Universal binding extractor for attach_function, sodium_function, etc.
|
|
37
|
+
def extract_function_binding(call_node, method_name:, module_name: nil, default_return: :void)
|
|
38
|
+
return nil unless call_node.is_a?(Prism::CallNode) && call_node.name == method_name
|
|
39
|
+
|
|
40
|
+
all_args = call_node.arguments&.arguments || []
|
|
41
|
+
return nil if all_args.empty?
|
|
42
|
+
|
|
43
|
+
# Strip trailing keyword options hash if present
|
|
44
|
+
pos_args = all_args.grep_v(Prism::KeywordHashNode)
|
|
45
|
+
return nil if pos_args.empty?
|
|
46
|
+
|
|
47
|
+
ruby_name = extract_symbol(pos_args[0])
|
|
48
|
+
return nil if ruby_name.nil?
|
|
49
|
+
|
|
50
|
+
if pos_args.size >= 4
|
|
51
|
+
# Form B: ruby_name, c_name, arg_types, return_type
|
|
52
|
+
c_name = extract_symbol(pos_args[1]) || ruby_name
|
|
53
|
+
args_node = pos_args[2]
|
|
54
|
+
ret_node = pos_args[3]
|
|
55
|
+
elsif pos_args.size == 3
|
|
56
|
+
# Form A: ruby_name, arg_types, return_type
|
|
57
|
+
c_name = ruby_name
|
|
58
|
+
args_node = pos_args[1]
|
|
59
|
+
ret_node = pos_args[2]
|
|
60
|
+
elsif pos_args.size == 2
|
|
61
|
+
c_name = ruby_name
|
|
62
|
+
args_node = nil
|
|
63
|
+
ret_node = pos_args[1]
|
|
64
|
+
else
|
|
65
|
+
return nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
arg_types = []
|
|
69
|
+
if args_node.is_a?(Prism::ArrayNode)
|
|
70
|
+
arg_types = (args_node.elements || []).map { |elem| extract_type_symbol(elem) }.compact
|
|
71
|
+
elsif args_node.is_a?(Prism::ConstantReadNode) || args_node.is_a?(Prism::ConstantPathNode)
|
|
72
|
+
arg_types = [args_node.slice.to_sym]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
return_type = ret_node ? (extract_type_symbol(ret_node) || default_return) : default_return
|
|
76
|
+
|
|
77
|
+
AttachFunctionFact.new(
|
|
78
|
+
ruby_name: ruby_name,
|
|
79
|
+
c_name: c_name,
|
|
80
|
+
arg_types: arg_types,
|
|
81
|
+
return_type: return_type,
|
|
82
|
+
node: call_node,
|
|
83
|
+
receiver_name: module_name
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def extract_attach_function(call_node, module_name: nil)
|
|
88
|
+
extract_function_binding(call_node, method_name: :attach_function, module_name: module_name, default_return: :void)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def ffx_diagnostics_for_call(call_node, path:, target:)
|
|
92
|
+
return [] unless target == :ffx && call_node.is_a?(Prism::CallNode)
|
|
93
|
+
|
|
94
|
+
diagnostics = []
|
|
95
|
+
|
|
96
|
+
case call_node.name
|
|
97
|
+
when :callback
|
|
98
|
+
diagnostics << Rigor::Analysis::Diagnostic.new(
|
|
99
|
+
path: path,
|
|
100
|
+
line: call_node.location.start_line,
|
|
101
|
+
column: call_node.location.start_column + 1,
|
|
102
|
+
rule: "ffx.unsupported-callback",
|
|
103
|
+
message: "callback declarations are not supported by ffx (C extensions use native function pointers)",
|
|
104
|
+
severity: :error
|
|
105
|
+
)
|
|
106
|
+
when :typedef
|
|
107
|
+
diagnostics << Rigor::Analysis::Diagnostic.new(
|
|
108
|
+
path: path,
|
|
109
|
+
line: call_node.location.start_line,
|
|
110
|
+
column: call_node.location.start_column + 1,
|
|
111
|
+
rule: "ffx.unsupported-typedef",
|
|
112
|
+
message: "typedef declarations are not supported by ffx",
|
|
113
|
+
severity: :error
|
|
114
|
+
)
|
|
115
|
+
when :enum, :bitmask
|
|
116
|
+
diagnostics << Rigor::Analysis::Diagnostic.new(
|
|
117
|
+
path: path,
|
|
118
|
+
line: call_node.location.start_line,
|
|
119
|
+
column: call_node.location.start_column + 1,
|
|
120
|
+
rule: "ffx.unsupported-enum",
|
|
121
|
+
message: "#{call_node.name} declarations are not supported by ffx",
|
|
122
|
+
severity: :error
|
|
123
|
+
)
|
|
124
|
+
when :attach_function
|
|
125
|
+
args = call_node.arguments&.arguments || []
|
|
126
|
+
pos_args = args.grep_v(Prism::KeywordHashNode)
|
|
127
|
+
|
|
128
|
+
args_node = pos_args.size >= 4 ? pos_args[2] : pos_args[1]
|
|
129
|
+
ret_node = pos_args.size >= 4 ? pos_args[3] : pos_args[2]
|
|
130
|
+
|
|
131
|
+
if args_node.is_a?(Prism::ArrayNode)
|
|
132
|
+
args_node.elements.each do |elem|
|
|
133
|
+
type_sym = extract_type_symbol(elem)
|
|
134
|
+
if type_sym == :varargs
|
|
135
|
+
diagnostics << Rigor::Analysis::Diagnostic.new(
|
|
136
|
+
path: path,
|
|
137
|
+
line: elem.location.start_line,
|
|
138
|
+
column: elem.location.start_column + 1,
|
|
139
|
+
rule: "ffx.unsupported-varargs",
|
|
140
|
+
message: "varargs are not supported by ffx",
|
|
141
|
+
severity: :error
|
|
142
|
+
)
|
|
143
|
+
elsif type_sym && !Types::FFX_PRIMITIVE_TYPES.include?(type_sym)
|
|
144
|
+
diagnostics << Rigor::Analysis::Diagnostic.new(
|
|
145
|
+
path: path,
|
|
146
|
+
line: elem.location.start_line,
|
|
147
|
+
column: elem.location.start_column + 1,
|
|
148
|
+
rule: "ffx.unsupported-type",
|
|
149
|
+
message: "type :#{type_sym} is not supported by ffx (expected one of 25 primitive types)",
|
|
150
|
+
severity: :error
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if ret_node
|
|
157
|
+
ret_sym = extract_type_symbol(ret_node)
|
|
158
|
+
if ret_sym && !Types::FFX_PRIMITIVE_TYPES.include?(ret_sym)
|
|
159
|
+
diagnostics << Rigor::Analysis::Diagnostic.new(
|
|
160
|
+
path: path,
|
|
161
|
+
line: ret_node.location.start_line,
|
|
162
|
+
column: ret_node.location.start_column + 1,
|
|
163
|
+
rule: "ffx.unsupported-type",
|
|
164
|
+
message: "return type :#{ret_sym} is not supported by ffx (expected one of 25 primitive types)",
|
|
165
|
+
severity: :error
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
diagnostics
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def ffx_diagnostics_for_class(class_node, path:, target:)
|
|
175
|
+
return [] unless target == :ffx && class_node.is_a?(Prism::ClassNode)
|
|
176
|
+
|
|
177
|
+
superclass_name = class_node.superclass&.slice
|
|
178
|
+
return [] unless superclass_name
|
|
179
|
+
|
|
180
|
+
if ["FFI::Struct", "::FFI::Struct", "FFI::Union", "::FFI::Union", "FFI::ManagedStruct", "::FFI::ManagedStruct"].include?(superclass_name)
|
|
181
|
+
[
|
|
182
|
+
Rigor::Analysis::Diagnostic.new(
|
|
183
|
+
path: path,
|
|
184
|
+
line: class_node.location.start_line,
|
|
185
|
+
column: class_node.location.start_column + 1,
|
|
186
|
+
rule: "ffx.unsupported-struct",
|
|
187
|
+
message: "FFI::Struct and FFI::Union are not supported by ffx",
|
|
188
|
+
severity: :error
|
|
189
|
+
)
|
|
190
|
+
]
|
|
191
|
+
else
|
|
192
|
+
[]
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Plugin
|
|
5
|
+
class FFI < Base
|
|
6
|
+
# Fact yielded by a registered BindingRecognizer or emitted from literal attach_function.
|
|
7
|
+
AttachFunctionFact = Data.define(
|
|
8
|
+
:ruby_name,
|
|
9
|
+
:c_name,
|
|
10
|
+
:arg_types,
|
|
11
|
+
:return_type,
|
|
12
|
+
:blocking,
|
|
13
|
+
:node,
|
|
14
|
+
:receiver_name
|
|
15
|
+
) do
|
|
16
|
+
def initialize(
|
|
17
|
+
ruby_name:,
|
|
18
|
+
c_name: ruby_name,
|
|
19
|
+
arg_types: [],
|
|
20
|
+
return_type: :void,
|
|
21
|
+
blocking: false,
|
|
22
|
+
node: nil,
|
|
23
|
+
receiver_name: nil
|
|
24
|
+
)
|
|
25
|
+
super(
|
|
26
|
+
ruby_name: ruby_name.to_sym,
|
|
27
|
+
c_name: (c_name || ruby_name).to_sym,
|
|
28
|
+
arg_types: Array(arg_types),
|
|
29
|
+
return_type: return_type,
|
|
30
|
+
blocking: blocking,
|
|
31
|
+
node: node,
|
|
32
|
+
receiver_name: receiver_name&.to_s
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Extension point for sub-plugins to register custom DSL recognizers (WD2).
|
|
38
|
+
class BindingRecognizer
|
|
39
|
+
attr_reader :name, :block
|
|
40
|
+
|
|
41
|
+
def initialize(name, &block)
|
|
42
|
+
@name = name.to_sym
|
|
43
|
+
@block = block
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def recognize(node, scope)
|
|
47
|
+
Array(@block.call(node, scope))
|
|
48
|
+
rescue StandardError
|
|
49
|
+
[]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class << self
|
|
54
|
+
def binding_recognizers
|
|
55
|
+
@binding_recognizers ||= []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def register_binding_recognizer(name, &)
|
|
59
|
+
binding_recognizers << BindingRecognizer.new(name, &)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class Base
|
|
65
|
+
def self.ffi_binding_recognizers
|
|
66
|
+
@ffi_binding_recognizers ||= []
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.ffi_binding_recognizer(name, &)
|
|
70
|
+
ffi_binding_recognizers << Rigor::Plugin::FFI::BindingRecognizer.new(name, &)
|
|
71
|
+
Rigor::Plugin::FFI.register_binding_recognizer(name, &)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Plugin
|
|
5
|
+
class FFI < Base
|
|
6
|
+
class FFICatalog
|
|
7
|
+
attr_reader :functions,
|
|
8
|
+
:functions_by_receiver,
|
|
9
|
+
:libraries,
|
|
10
|
+
:structs,
|
|
11
|
+
:typedefs,
|
|
12
|
+
:enums,
|
|
13
|
+
:callbacks,
|
|
14
|
+
:function_method_names,
|
|
15
|
+
:struct_field_names,
|
|
16
|
+
:struct_names,
|
|
17
|
+
:all_receiver_names,
|
|
18
|
+
:all_method_names
|
|
19
|
+
|
|
20
|
+
def initialize(functions: {}, functions_by_receiver: {}, libraries: Set.new, structs: {}, typedefs: {}, enums: {}, callbacks: {})
|
|
21
|
+
@functions = functions
|
|
22
|
+
@functions_by_receiver = functions_by_receiver
|
|
23
|
+
@libraries = libraries
|
|
24
|
+
@structs = structs
|
|
25
|
+
@typedefs = typedefs
|
|
26
|
+
@enums = enums
|
|
27
|
+
@callbacks = callbacks
|
|
28
|
+
|
|
29
|
+
@function_method_names = Set.new(functions.keys.map(&:to_sym)).freeze
|
|
30
|
+
|
|
31
|
+
field_names = Set.new
|
|
32
|
+
structs.each_value do |fields|
|
|
33
|
+
fields.each_key do |f|
|
|
34
|
+
field_names << f.to_sym
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
field_names << :[]
|
|
38
|
+
field_names << :[]=
|
|
39
|
+
@struct_field_names = field_names.freeze
|
|
40
|
+
@struct_names = structs.keys.freeze
|
|
41
|
+
|
|
42
|
+
@all_receiver_names = (libraries.to_a + structs.keys).freeze
|
|
43
|
+
@all_method_names = (@function_method_names + @struct_field_names).freeze
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def function_for(receiver_name, method_name)
|
|
47
|
+
if receiver_name
|
|
48
|
+
scoped = @functions_by_receiver[[receiver_name.to_s, method_name.to_sym]]
|
|
49
|
+
return scoped if scoped
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Fallback if receiver matches any registered library
|
|
53
|
+
return unless receiver_name.nil? || @libraries.include?(receiver_name.to_s)
|
|
54
|
+
|
|
55
|
+
@functions[method_name.to_sym]&.first
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def struct_fields(struct_name)
|
|
59
|
+
@structs[struct_name.to_s]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Plugin
|
|
7
|
+
class FFI < Base
|
|
8
|
+
class Discoverer
|
|
9
|
+
def self.discover(root:)
|
|
10
|
+
new(root: root).discover
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(root:)
|
|
14
|
+
@root = root
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def discover
|
|
18
|
+
functions = {}
|
|
19
|
+
functions_by_receiver = {}
|
|
20
|
+
libraries = Set.new
|
|
21
|
+
structs = {}
|
|
22
|
+
typedefs = {}
|
|
23
|
+
enums = {}
|
|
24
|
+
callbacks = {}
|
|
25
|
+
|
|
26
|
+
ruby_files.each do |file_path|
|
|
27
|
+
parse_result = Prism.parse_file(file_path)
|
|
28
|
+
next unless parse_result.value
|
|
29
|
+
|
|
30
|
+
walk_tree(parse_result.value, nil) do |node, mod_name|
|
|
31
|
+
case node
|
|
32
|
+
when Prism::CallNode
|
|
33
|
+
handle_call(
|
|
34
|
+
node,
|
|
35
|
+
mod_name,
|
|
36
|
+
functions,
|
|
37
|
+
functions_by_receiver,
|
|
38
|
+
libraries,
|
|
39
|
+
structs,
|
|
40
|
+
typedefs,
|
|
41
|
+
enums,
|
|
42
|
+
callbacks
|
|
43
|
+
)
|
|
44
|
+
when Prism::ClassNode
|
|
45
|
+
handle_class(node, mod_name, structs)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
FFICatalog.new(
|
|
51
|
+
functions: functions,
|
|
52
|
+
functions_by_receiver: functions_by_receiver,
|
|
53
|
+
libraries: libraries,
|
|
54
|
+
structs: structs,
|
|
55
|
+
typedefs: typedefs,
|
|
56
|
+
enums: enums,
|
|
57
|
+
callbacks: callbacks
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def ruby_files
|
|
64
|
+
return [] unless @root && File.directory?(@root)
|
|
65
|
+
|
|
66
|
+
Dir.glob(File.join(@root, "**", "*.rb")).reject { |p| p.include?("/vendor/") || p.include?("/.git/") }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def walk_tree(node, current_module, &)
|
|
70
|
+
return unless node
|
|
71
|
+
|
|
72
|
+
case node
|
|
73
|
+
when Prism::ClassNode, Prism::ModuleNode
|
|
74
|
+
name = node.constant_path.slice
|
|
75
|
+
full_name = current_module ? "#{current_module}::#{name}" : name
|
|
76
|
+
yield node, full_name
|
|
77
|
+
node.body&.child_nodes&.each { |child| walk_tree(child, full_name, &) }
|
|
78
|
+
else
|
|
79
|
+
yield node, current_module
|
|
80
|
+
node.child_nodes.each { |child| walk_tree(child, current_module, &) }
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def handle_call(node, mod_name, functions, functions_by_receiver, libraries, structs, typedefs, enums, callbacks)
|
|
85
|
+
case node.name
|
|
86
|
+
when :extend
|
|
87
|
+
arg = node.arguments&.arguments&.first
|
|
88
|
+
if arg && ["FFI::Library", "::FFI::Library", "FFX::Library", "::FFX::Library"].include?(arg.slice) && mod_name
|
|
89
|
+
libraries << mod_name
|
|
90
|
+
end
|
|
91
|
+
when :attach_function
|
|
92
|
+
fact = Analyzer.extract_attach_function(node, module_name: mod_name)
|
|
93
|
+
if fact
|
|
94
|
+
(functions[fact.ruby_name] ||= []) << fact
|
|
95
|
+
if mod_name
|
|
96
|
+
libraries << mod_name
|
|
97
|
+
functions_by_receiver[[mod_name, fact.ruby_name]] = fact
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
when :callback
|
|
101
|
+
# Issue 4 fix: extract callback definitions
|
|
102
|
+
args = node.arguments&.arguments || []
|
|
103
|
+
if args.size >= 3
|
|
104
|
+
cb_name = Analyzer.extract_symbol(args[0])
|
|
105
|
+
cb_params = []
|
|
106
|
+
if args[1].is_a?(Prism::ArrayNode)
|
|
107
|
+
cb_params = (args[1].elements || []).map { |elem| Analyzer.extract_type_symbol(elem) }.compact
|
|
108
|
+
end
|
|
109
|
+
cb_ret = Analyzer.extract_type_symbol(args[2]) || :void
|
|
110
|
+
if cb_name
|
|
111
|
+
callbacks[cb_name] = { params: cb_params, return_type: cb_ret }
|
|
112
|
+
typedefs[cb_name] = :pointer
|
|
113
|
+
libraries << mod_name if mod_name
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
when :typedef
|
|
117
|
+
args = node.arguments&.arguments || []
|
|
118
|
+
if args.size >= 2
|
|
119
|
+
old_t = Analyzer.extract_type_symbol(args[0])
|
|
120
|
+
new_t = Analyzer.extract_symbol(args[1])
|
|
121
|
+
typedefs[new_t] = old_t if old_t && new_t
|
|
122
|
+
libraries << mod_name if mod_name
|
|
123
|
+
end
|
|
124
|
+
when :enum, :bitmask
|
|
125
|
+
args = node.arguments&.arguments || []
|
|
126
|
+
if args.size >= 2
|
|
127
|
+
enum_name = Analyzer.extract_symbol(args[0])
|
|
128
|
+
enums[enum_name] = true if enum_name
|
|
129
|
+
libraries << mod_name if mod_name
|
|
130
|
+
end
|
|
131
|
+
when :layout
|
|
132
|
+
# Struct layout definition
|
|
133
|
+
if mod_name
|
|
134
|
+
fields = structs[mod_name] ||= {}
|
|
135
|
+
args = node.arguments&.arguments || []
|
|
136
|
+
i = 0
|
|
137
|
+
while i < args.size
|
|
138
|
+
f_name = Analyzer.extract_symbol(args[i])
|
|
139
|
+
f_type = Analyzer.extract_type_symbol(args[i + 1])
|
|
140
|
+
fields[f_name] = f_type if f_name && f_type
|
|
141
|
+
i += 2
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
else
|
|
145
|
+
# Issue 5 fix: pass mod_name to sub-plugin recognizers
|
|
146
|
+
FFI.binding_recognizers.each do |recognizer|
|
|
147
|
+
facts = recognizer.recognize(node, mod_name)
|
|
148
|
+
Array(facts).each do |fact|
|
|
149
|
+
next unless fact.is_a?(AttachFunctionFact)
|
|
150
|
+
|
|
151
|
+
(functions[fact.ruby_name] ||= []) << fact
|
|
152
|
+
rec = fact.receiver_name || mod_name
|
|
153
|
+
if rec
|
|
154
|
+
libraries << rec
|
|
155
|
+
functions_by_receiver[[rec, fact.ruby_name]] = fact
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def handle_class(node, mod_name, structs)
|
|
163
|
+
superclass_name = node.superclass&.slice
|
|
164
|
+
return unless superclass_name
|
|
165
|
+
|
|
166
|
+
return unless ["FFI::Struct", "::FFI::Struct", "FFI::Union", "::FFI::Union"].include?(superclass_name)
|
|
167
|
+
|
|
168
|
+
structs[mod_name] ||= {} if mod_name
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|