From fdf954a4e82c770ee22c4fa544707f0d0a6bcdf3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:44:38 +0000 Subject: [PATCH 1/4] Bump the minor-and-patch group with 4 updates Bumps the minor-and-patch group with 4 updates: [state_machines](https://github.com/state-machines/state_machines), [google-protobuf](https://github.com/protocolbuffers/protobuf), [sorbet-static](https://github.com/sorbet/sorbet) and [sorbet-static-and-runtime](https://github.com/sorbet/sorbet). Updates `state_machines` from 0.101.0 to 0.201.0 - [Release notes](https://github.com/state-machines/state_machines/releases) - [Changelog](https://github.com/state-machines/state_machines/blob/master/CHANGELOG.md) - [Commits](https://github.com/state-machines/state_machines/compare/state_machines/v0.101.0...state_machines/v0.201.0) Updates `google-protobuf` from 4.35.0 to 4.35.1 - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Commits](https://github.com/protocolbuffers/protobuf/commits) Updates `sorbet-static` from 0.6.13286 to 0.6.13297 - [Release notes](https://github.com/sorbet/sorbet/releases) - [Commits](https://github.com/sorbet/sorbet/commits) Updates `sorbet-static-and-runtime` from 0.6.13286 to 0.6.13297 - [Release notes](https://github.com/sorbet/sorbet/releases) - [Commits](https://github.com/sorbet/sorbet/commits) --- updated-dependencies: - dependency-name: state_machines dependency-version: 0.201.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-and-patch - dependency-name: google-protobuf dependency-version: 4.35.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: sorbet-static dependency-version: 0.6.13297 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: sorbet-static-and-runtime dependency-version: 0.6.13297 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 60 ++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5d97729b2..1af94c0e8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -161,22 +161,22 @@ GEM activemodel globalid (1.3.0) activesupport (>= 6.1) - google-protobuf (4.35.0-aarch64-linux-gnu) + google-protobuf (4.35.1-aarch64-linux-gnu) bigdecimal rake (~> 13.3) - google-protobuf (4.35.0-aarch64-linux-musl) + google-protobuf (4.35.1-aarch64-linux-musl) bigdecimal rake (~> 13.3) - google-protobuf (4.35.0-arm64-darwin) + google-protobuf (4.35.1-arm64-darwin) bigdecimal rake (~> 13.3) - google-protobuf (4.35.0-x86_64-darwin) + google-protobuf (4.35.1-x86_64-darwin) bigdecimal rake (~> 13.3) - google-protobuf (4.35.0-x86_64-linux-gnu) + google-protobuf (4.35.1-x86_64-linux-gnu) bigdecimal rake (~> 13.3) - google-protobuf (4.35.0-x86_64-linux-musl) + google-protobuf (4.35.1-x86_64-linux-musl) bigdecimal rake (~> 13.3) graphql (2.6.3) @@ -371,15 +371,15 @@ GEM rack (>= 3.2.0) redis-client (>= 0.29.0) smart_properties (1.17.0) - sorbet (0.6.13286) - sorbet-static (= 0.6.13286) - sorbet-runtime (0.6.13286) - sorbet-static (0.6.13286-aarch64-linux) - sorbet-static (0.6.13286-universal-darwin) - sorbet-static (0.6.13286-x86_64-linux) - sorbet-static-and-runtime (0.6.13286) - sorbet (= 0.6.13286) - sorbet-runtime (= 0.6.13286) + sorbet (0.6.13297) + sorbet-static (= 0.6.13297) + sorbet-runtime (0.6.13297) + sorbet-static (0.6.13297-aarch64-linux) + sorbet-static (0.6.13297-universal-darwin) + sorbet-static (0.6.13297-x86_64-linux) + sorbet-static-and-runtime (0.6.13297) + sorbet (= 0.6.13297) + sorbet-runtime (= 0.6.13297) spoom (1.7.16) erubi (>= 1.10.0) prism (>= 0.28.0) @@ -398,7 +398,7 @@ GEM sqlite3 (2.9.0-x86_64-darwin) sqlite3 (2.9.0-x86_64-linux-gnu) sqlite3 (2.9.0-x86_64-linux-musl) - state_machines (0.101.0) + state_machines (0.201.0) stringio (3.2.0) thor (1.5.0) timeout (0.6.1) @@ -503,7 +503,7 @@ CHECKSUMS benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f - bundler (4.0.13) sha256=19f08be7f27022cf0b89f27da0b044ae075e8270a9ef44ad248a932614e1ca3b + bundler (4.0.14) sha256=d09a0a965cf772266a7e49e83610be7c2f4e49e61134c42a56804bb383cc24b8 cityhash (0.9.0) sha256=1c20843d286524de21d0ecf5d43c7e7f18f5fb0c5866294a717f0be13dc1962d concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab config (5.6.1) sha256=a9f0f0f9ffa6d12d43147a3fa1ab8486fe484c3098a350c6a2e0f32430e0d1cc @@ -523,12 +523,12 @@ CHECKSUMS fiber-storage (1.0.1) sha256=f48e5b6d8b0be96dac486332b55cee82240057065dc761c1ea692b2e719240e1 frozen_record (0.27.4) sha256=c6f6a3b64d11046bc6143d174ebf53777b3194b0caff4e3ab653c947de1b8d57 globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11 - google-protobuf (4.35.0-aarch64-linux-gnu) sha256=2cabd61b420918aec1564f9f7414e455bf922d20c5f8139d62783df5558a7aea - google-protobuf (4.35.0-aarch64-linux-musl) sha256=f6b11f3420a4564f68e8233c95eac6924f6a727dfc2f81a56af2e09add551501 - google-protobuf (4.35.0-arm64-darwin) sha256=66ab26d3fc82b8950702e53ab16c198e3c0ea3f2a38aaaf1f32152da45593ac5 - google-protobuf (4.35.0-x86_64-darwin) sha256=05eb5c8bc9899135befff496fc0a3642e7ff3d0943f043841dcc456f5654fea0 - google-protobuf (4.35.0-x86_64-linux-gnu) sha256=999226f3b00cd9fddb1b26851d16060212fa1d90c406aaad47e574682b716059 - google-protobuf (4.35.0-x86_64-linux-musl) sha256=be0218520d77b2aee898b363514b03819f6f63f9c041ae0d0d79b4ce5247bffd + google-protobuf (4.35.1-aarch64-linux-gnu) sha256=50ca44d0eeff3f8475e630a1accdd974256f3510694d574e2c9d6119ea8bc9e1 + google-protobuf (4.35.1-aarch64-linux-musl) sha256=d5c65cef6bd6498a9e5ed5f88cf6cf7e341c10b0a005e32137d5d1a2b6e8c18a + google-protobuf (4.35.1-arm64-darwin) sha256=d9c957df04fa89c749fa9a72a7b383eb4296efc9b2303dc6fd6fbe39c698ad6b + google-protobuf (4.35.1-x86_64-darwin) sha256=66b62b4df00931018a692806df66393efa960d6d2b7da69735187249f950d3ee + google-protobuf (4.35.1-x86_64-linux-gnu) sha256=c786439087512a3fbd199e9897d265b855f951d4027e218ea55e858d45969edd + google-protobuf (4.35.1-x86_64-linux-musl) sha256=91890eb0002934a339fdb7d77a147c46b7474b6799db27872b747b905837f744 graphql (2.6.3) sha256=31fe845b509fda27db38d09380f27ffc0de1c7ea2f9f3b12fa42bdde8317239b hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1 i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5 @@ -608,12 +608,12 @@ CHECKSUMS shopify-money (4.1.1) sha256=523078e44bfde1920f8b3487ddf9144e0fb6af8cdf67e212bed02025c5c5f423 sidekiq (8.1.6) sha256=be20cd051124b1a16cf97ea9157137abbd30a515c16a5ae9312d2eadd045e40f smart_properties (1.17.0) sha256=f9323f8122e932341756ddec8e0ac9ec6e238408a7661508be99439ca6d6384b - sorbet (0.6.13286) sha256=0847e8a192231c1167ad169672dba5dd3a3fbb0d1c2ea1dbc936d4aad2d4af92 - sorbet-runtime (0.6.13286) sha256=0b1bd9ecd70ec511db0696e1162e7c8953ff27a8cc09d7181907c7dd7b859afb - sorbet-static (0.6.13286-aarch64-linux) sha256=16d159aa5391f08c7e73abd81a63a00b04b4f106717aef26731b8d741f95748f - sorbet-static (0.6.13286-universal-darwin) sha256=0068238028ad5dfb793a2801599dc2ac819273c8083d03da56ee342d73090c2f - sorbet-static (0.6.13286-x86_64-linux) sha256=5de724ee2cba6d06211ae005d00c04a9d83109927156178ff2aeeccdac5e204c - sorbet-static-and-runtime (0.6.13286) sha256=c571dcc190d4e4b7eecb29ffe819b491e9e6607e16a7c387a64a677f7ee34f25 + sorbet (0.6.13297) sha256=e2044b8e6080c5a41be5d548dbcd4924fed266ce0c5f4c8bfa7e0d0c1621f0f6 + sorbet-runtime (0.6.13297) sha256=f653ac56cf2ba1ff543aaea876a2434acde1d75782fee7e46b0a0a8ee3991a47 + sorbet-static (0.6.13297-aarch64-linux) sha256=36a539606182af3dab499edccb99f78598ce55bba545269c097d0b4147eaeacb + sorbet-static (0.6.13297-universal-darwin) sha256=200b5fb70b440f97144cd0f42976b710022dd4eda79df57a6a124213e90c07e9 + sorbet-static (0.6.13297-x86_64-linux) sha256=722dd1d8caf01ec87a20067cf3e82f26b19eec86891d12cb3b1c368fcae02289 + sorbet-static-and-runtime (0.6.13297) sha256=00654320fd7dd556cb1b91ed278805775b20d475301c5045f5fd46b70670b1fd spoom (1.7.16) sha256=b6033d1aaede800ef37505474d260d57a02fa63364b4725ea40b6f606e932d59 sprockets (4.2.2) sha256=761e5a49f1c288704763f73139763564c845a8f856d52fba013458f8af1b59b1 sqlite3 (2.9.0-aarch64-linux-gnu) sha256=cfe1e0216f46d7483839719bf827129151e6c680317b99d7b8fc1597a3e13473 @@ -622,7 +622,7 @@ CHECKSUMS sqlite3 (2.9.0-x86_64-darwin) sha256=59fe51baa3cb33c36d27ce78b4ed9360cd33ccca09498c2ae63850c97c0a6026 sqlite3 (2.9.0-x86_64-linux-gnu) sha256=72fff9bd750070ba3af695511ba5f0e0a2d8a9206f84869640b3e99dfaf3d5a5 sqlite3 (2.9.0-x86_64-linux-musl) sha256=ef716ba7a66d7deb1ccc402ac3a6d7343da17fac862793b7f0be3d2917253c90 - state_machines (0.101.0) sha256=cbcec89699a0388493226a4b18d790bd5d49c38ba782d2099342a3dd78bcbca3 + state_machines (0.201.0) sha256=807bafdd5d7dbfb3e23d77897edfe38e505b6e0738a2b7259dab566f46b9d319 stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 tapioca (0.19.1) thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 From ab5ffa7fddb8900a9dc930b8459b47df6e6d1541 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Wed, 17 Jun 2026 18:25:49 -0400 Subject: [PATCH 2/4] `bin/tapioca gem` --- ...@4.35.0.rbi => google-protobuf@4.35.1.rbi} | 0 ...0.101.0.rbi => state_machines@0.201.0.rbi} | 1356 +++++++++++++---- 2 files changed, 1018 insertions(+), 338 deletions(-) rename sorbet/rbi/gems/{google-protobuf@4.35.0.rbi => google-protobuf@4.35.1.rbi} (100%) rename sorbet/rbi/gems/{state_machines@0.101.0.rbi => state_machines@0.201.0.rbi} (85%) diff --git a/sorbet/rbi/gems/google-protobuf@4.35.0.rbi b/sorbet/rbi/gems/google-protobuf@4.35.1.rbi similarity index 100% rename from sorbet/rbi/gems/google-protobuf@4.35.0.rbi rename to sorbet/rbi/gems/google-protobuf@4.35.1.rbi diff --git a/sorbet/rbi/gems/state_machines@0.101.0.rbi b/sorbet/rbi/gems/state_machines@0.201.0.rbi similarity index 85% rename from sorbet/rbi/gems/state_machines@0.101.0.rbi rename to sorbet/rbi/gems/state_machines@0.201.0.rbi index 78f7af4bd..17c15d64c 100644 --- a/sorbet/rbi/gems/state_machines@0.101.0.rbi +++ b/sorbet/rbi/gems/state_machines@0.201.0.rbi @@ -77,19 +77,28 @@ class StateMachines::AttributeTransitionCollection < ::StateMachines::Transition private + # Completes transitions that were stored for deferred completion by + # machines outside this collection while the action was running. Their + # before callbacks and state persistence already happened mid-action; + # only their after callbacks remain. Clears the stored references so + # they cannot leak into a later action cycle with stale data (issue #91). + # + # pkg:gem/state_machines#lib/state_machines/transition_collection.rb:276 + def complete_nested_transitions; end + # Tracks that before callbacks have now completed # - # pkg:gem/state_machines#lib/state_machines/transition_collection.rb:266 + # pkg:gem/state_machines#lib/state_machines/transition_collection.rb:293 def persist; end # Resets callback tracking # - # pkg:gem/state_machines#lib/state_machines/transition_collection.rb:272 + # pkg:gem/state_machines#lib/state_machines/transition_collection.rb:299 def reset; end # Resets the event attribute so it can be re-evaluated if attempted again # - # pkg:gem/state_machines#lib/state_machines/transition_collection.rb:278 + # pkg:gem/state_machines#lib/state_machines/transition_collection.rb:305 def rollback; end # Hooks into running transition callbacks so that event / event transition @@ -659,7 +668,7 @@ module StateMachines::EvalHelpers # Validates string input before eval to prevent code injection # This is a basic safety check - not foolproof security # - # pkg:gem/state_machines#lib/state_machines/eval_helpers.rb:215 + # pkg:gem/state_machines#lib/state_machines/eval_helpers.rb:199 def validate_eval_string(method_string); end end @@ -1801,9 +1810,244 @@ class StateMachines::Machine # Determines whether an action hook was defined for firing attribute-based # event transitions when the configured action gets called. # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1615 + # pkg:gem/state_machines#lib/state_machines/machine.rb:589 def action_hook?(self_only = T.unsafe(nil)); end + # The callbacks to invoke before/after a transition is performed + # + # Maps :before => callbacks and :after => callbacks + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:466 + def callbacks; end + + # Defines a new helper method in an instance or class scope with the given + # name. If the method is already defined in the scope, then this will not + # override it. + # + # If passing in a block, there are two side effects to be aware of + # 1. The method cannot be chained, meaning that the block cannot call +super+ + # 2. If the method is already defined in an ancestor, then it will not get + # overridden and a warning will be output. + # + # Example: + # + # # Instance helper + # machine.define_helper(:instance, :state_name) do |machine, object| + # machine.states.match(object).name + # end + # + # # Class helper + # machine.define_helper(:class, :state_machine_name) do |machine, klass| + # "State" + # end + # + # You can also define helpers using string evaluation like so: + # + # # Instance helper + # machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 + # def state_name + # self.class.state_machine(:state).states.match(self).name + # end + # end_eval + # + # # Class helper + # machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1 + # def state_machine_name + # "State" + # end + # end_eval + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:515 + def define_helper(scope, method, *_arg2, **_arg3, &block); end + + # pkg:gem/state_machines#lib/state_machines/machine.rb:583 + def draw(**_arg0); end + + # Gets a description of the errors for the given object. This is used to + # provide more detailed information when an InvalidTransition exception is + # raised. + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:543 + def errors_for(_object); end + + # The events that trigger transitions. These are sorted, by default, in + # the order in which they were defined. + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:450 + def events; end + + # Generates the message to use when invalidating the given object after + # failing to transition on a specific event + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:554 + def generate_message(name, values = T.unsafe(nil)); end + + # Marks the given object as invalid with the given message. + # + # By default, this is a no-op. + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:538 + def invalidate(_object, _attribute, _message, _values = T.unsafe(nil)); end + + # The name of the machine, used for scoping methods generated for the + # machine as a whole (not states or events) + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:446 + def name; end + + # An identifier that forces all methods (including state predicates and + # event methods) to be generated with the value prefixed or suffixed, + # depending on the context. + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:474 + def namespace; end + + # The class that the machine is defined in + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:442 + def owner_class; end + + # pkg:gem/state_machines#lib/state_machines/machine.rb:579 + def renderer; end + + # Resets any errors previously added when invalidating the given object. + # + # By default, this is a no-op. + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:550 + def reset(_object); end + + # A list of all of the states known to this state machine. This will pull + # states from the following sources: + # * Initial state + # * State behaviors + # * Event transitions (:to, :from, and :except_from options) + # * Transition callbacks (:to, :from, :except_to, and :except_from options) + # * Unreferenced states (using +other_states+ helper) + # + # These are sorted, by default, in the order in which they were referenced. + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:461 + def states; end + + # Whether the machine will use transactions when firing events + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:477 + def use_transactions; end + + # Runs a transaction, rolling back any changes if the yielded block fails. + # + # This is only applicable to integrations that involve databases. By + # default, this will not run any transactions since the changes aren't + # taking place within the context of a database. + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:571 + def within_transaction(object, &_arg1); end + + protected + + # Runs additional initialization hooks. By default, this is a no-op. + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:596 + def after_initialize; end + + # Gets the initial attribute value defined by the owner class (outside of + # the machine's definition). By default, this is always nil. + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:605 + def owner_class_attribute_default; end + + # Checks whether the given state matches the attribute default specified + # by the owner class + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:611 + def owner_class_attribute_default_matches?(state); end + + # Always yields + # + # pkg:gem/state_machines#lib/state_machines/machine.rb:599 + def transaction(_object); end +end + +# pkg:gem/state_machines#lib/state_machines/machine/action_hooks.rb:5 +module StateMachines::Machine::ActionHooks + protected + + # The method to hook into for triggering transitions when invoked. By + # default, this is the action configured for the machine. + # + # Since the default hook technique relies on module inheritance, the + # action must be defined in an ancestor of the owner classs in order for + # it to be the action hook. + # + # pkg:gem/state_machines#lib/state_machines/machine/action_hooks.rb:48 + def action_hook; end + + # Adds helper methods for automatically firing events when an action + # is invoked + # + # pkg:gem/state_machines#lib/state_machines/machine/action_hooks.rb:17 + def define_action_helpers; end + + # Determines whether action helpers should be defined for this machine. + # This is only true if there is an action configured and no other machines + # have process this same configuration already. + # + # pkg:gem/state_machines#lib/state_machines/machine/action_hooks.rb:11 + def define_action_helpers?; end + + # Hooks directly into actions by defining the same method in an included + # module. As a result, when the action gets invoked, any state events + # defined for the object will get run. Method visibility is preserved. + # + # pkg:gem/state_machines#lib/state_machines/machine/action_hooks.rb:27 + def define_action_hook; end +end + +# AsyncMode extensions for the Machine class +# Provides async-aware methods while maintaining backward compatibility +# +# pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:10 +module StateMachines::Machine::AsyncExtensions + # Check if this specific machine instance has async mode enabled + # + # pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:63 + def async_mode_enabled?; end + + # Configure this specific machine instance for async mode + # + # Example: + # class Vehicle + # state_machine initial: :parked do + # configure_async_mode! # Enable async for this machine + # + # event :ignite do + # transition parked: :idling + # end + # end + # end + # + # pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:25 + def configure_async_mode!(enabled = T.unsafe(nil)); end + + # Thread-safe version of state reading + # + # pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:68 + def read_safely(object, attribute, ivar = T.unsafe(nil)); end + + # Thread-safe callback execution for async operations + # + # pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:78 + def run_callbacks_safely(type, object, context, transition); end + + # Thread-safe version of state writing + # + # pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:73 + def write_safely(object, attribute, value, ivar = T.unsafe(nil)); end +end + +# pkg:gem/state_machines#lib/state_machines/machine/callbacks.rb:5 +module StateMachines::Machine::Callbacks # Creates a callback that will be invoked *after* a transition failures to # be performed so long as the given requirements match the transition. # @@ -1833,7 +2077,7 @@ class StateMachines::Machine # end # end # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1483 + # pkg:gem/state_machines#lib/state_machines/machine/callbacks.rb:311 def after_failure(*args, **options, &_arg2); end # Creates a callback that will be invoked *after* a transition is @@ -1842,7 +2086,7 @@ class StateMachines::Machine # See +before_transition+ for a description of the possible configurations # for defining callbacks. # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1378 + # pkg:gem/state_machines#lib/state_machines/machine/callbacks.rb:220 def after_transition(*args, **options, &_arg2); end # Creates a callback that will be invoked *around* a transition so long as @@ -1901,7 +2145,7 @@ class StateMachines::Machine # See +before_transition+ for a description of the possible configurations # for defining callbacks. # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1444 + # pkg:gem/state_machines#lib/state_machines/machine/callbacks.rb:279 def around_transition(*args, **options, &_arg2); end # Creates a callback that will be invoked *before* a transition is @@ -2110,382 +2354,505 @@ class StateMachines::Machine # As can be seen, any number of transitions can be created using various # combinations of configuration options. # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1362 + # pkg:gem/state_machines#lib/state_machines/machine/callbacks.rb:211 def before_transition(*args, **options, &_arg2); end - # The callbacks to invoke before/after a transition is performed + private + + # pkg:gem/state_machines#lib/state_machines/machine/callbacks.rb:321 + def add_transition_callback(type, args, options, &_arg3); end +end + +# pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:5 +module StateMachines::Machine::ClassMethods + # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:55 + def default_messages; end + + # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:63 + def default_messages=(messages); end + + # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:47 + def draw(*_arg0); end + + # Attempts to find or create a state machine for the given class. For + # example, # - # Maps :before => callbacks and :after => callbacks + # StateMachines::Machine.find_or_create(Vehicle) + # StateMachines::Machine.find_or_create(Vehicle, :initial => :parked) + # StateMachines::Machine.find_or_create(Vehicle, :status) + # StateMachines::Machine.find_or_create(Vehicle, :status, :initial => :parked) # - # pkg:gem/state_machines#lib/state_machines/machine.rb:466 - def callbacks; end + # If a machine of the given name already exists in one of the class's + # superclasses, then a copy of that machine will be created and stored + # in the new owner class (the original will remain unchanged). + # + # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:17 + def find_or_create(owner_class, *args, &_arg2); end - # Defines a new helper method in an instance or class scope with the given - # name. If the method is already defined in the scope, then this will not - # override it. - # - # If passing in a block, there are two side effects to be aware of - # 1. The method cannot be chained, meaning that the block cannot call +super+ - # 2. If the method is already defined in an ancestor, then it will not get - # overridden and a warning will be output. - # - # Example: - # - # # Instance helper - # machine.define_helper(:instance, :state_name) do |machine, object| - # machine.states.match(object).name - # end - # - # # Class helper - # machine.define_helper(:class, :state_machine_name) do |machine, klass| - # "State" - # end - # - # You can also define helpers using string evaluation like so: - # - # # Instance helper - # machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1 - # def state_name - # self.class.state_machine(:state).states.match(self).name - # end - # end_eval + # Default messages to use for validation errors in ORM integrations + # Thread-safe access via atomic operations on simple values # - # # Class helper - # machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1 - # def state_machine_name - # "State" - # end - # end_eval + # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:53 + def ignore_method_conflicts; end + + # Default messages to use for validation errors in ORM integrations + # Thread-safe access via atomic operations on simple values # - # pkg:gem/state_machines#lib/state_machines/machine.rb:552 - def define_helper(scope, method, *_arg2, **_arg3, &block); end + # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:53 + def ignore_method_conflicts=(_arg0); end - # pkg:gem/state_machines#lib/state_machines/machine.rb:1609 - def draw(**_arg0); end + # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:77 + def renderer; end - # Gets a description of the errors for the given object. This is used to - # provide more detailed information when an InvalidTransition exception is - # raised. - # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1569 - def errors_for(_object); end + # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:75 + def renderer=(_arg0); end - # The events that trigger transitions. These are sorted, by default, in - # the order in which they were defined. - # - # pkg:gem/state_machines#lib/state_machines/machine.rb:450 - def events; end + # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:68 + def replace_messages(message_hash); end - # Generates the message to use when invalidating the given object after - # failing to transition on a specific event - # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1580 - def generate_message(name, values = T.unsafe(nil)); end + private - # Marks the given object as invalid with the given message. - # - # By default, this is a no-op. + # Deep freezes a hash and all its string values for thread safety # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1564 - def invalidate(_object, _attribute, _message, _values = T.unsafe(nil)); end + # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:86 + def deep_freeze_hash(hash); end +end - # The name of the machine, used for scoping methods generated for the - # machine as a whole (not states or events) +# pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:5 +module StateMachines::Machine::Configuration + # Initializes a new state machine with the given configuration. # - # pkg:gem/state_machines#lib/state_machines/machine.rb:446 - def name; end + # pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:7 + def initialize(owner_class, *args, &_arg2); end - # An identifier that forces all methods (including state predicates and - # event methods) to be generated with the value prefixed or suffixed, - # depending on the context. + # Gets the attribute name for the given machine scope. # - # pkg:gem/state_machines#lib/state_machines/machine.rb:474 - def namespace; end + # pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:123 + def attribute(name = T.unsafe(nil)); end - # The class that the machine is defined in + # Sets the initial state of the machine. This can be either the static name + # of a state or a lambda block which determines the initial state at + # creation time. # - # pkg:gem/state_machines#lib/state_machines/machine.rb:442 - def owner_class; end - - # pkg:gem/state_machines#lib/state_machines/machine.rb:1605 - def renderer; end + # pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:109 + def initial_state=(new_initial_state); end - # Resets any errors previously added when invalidating the given object. - # - # By default, this is a no-op. + # Sets the class which is the owner of this state machine. Any methods + # generated by states, events, or other parts of the machine will be defined + # on the given owner class. # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1576 - def reset(_object); end + # pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:81 + def owner_class=(klass); end - # A list of all of the states known to this state machine. This will pull - # states from the following sources: - # * Initial state - # * State behaviors - # * Event transitions (:to, :from, and :except_from options) - # * Transition callbacks (:to, :from, :except_to, and :except_from options) - # * Unreferenced states (using +other_states+ helper) - # - # These are sorted, by default, in the order in which they were referenced. - # - # pkg:gem/state_machines#lib/state_machines/machine.rb:461 - def states; end + private - # Whether the machine will use transactions when firing events + # Creates a copy of this machine in addition to copies of each associated + # event/states/callback, so that the modifications to those collections do + # not affect the original machine. # - # pkg:gem/state_machines#lib/state_machines/machine.rb:477 - def use_transactions; end + # pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:66 + def initialize_copy(orig); end +end - # Runs a transaction, rolling back any changes if the yielded block fails. +# pkg:gem/state_machines#lib/state_machines/machine/event_methods.rb:5 +module StateMachines::Machine::EventMethods + # Defines one or more events for the machine and the transitions that can + # be performed when those events are run. # - # This is only applicable to integrations that involve databases. By - # default, this will not run any transactions since the changes aren't - # taking place within the context of a database. + # This method is also aliased as +on+ for improved compatibility with + # using a domain-specific language. # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1597 - def within_transaction(object, &_arg1); end - - protected - - # Runs additional initialization hooks. By default, this is a no-op. + # Configuration options: + # * :human_name - The human-readable version of this event's name. + # By default, this is either defined by the integration or stringifies the + # name and converts underscores to spaces. + # + # == Instance methods + # + # The following instance methods are generated when a new event is defined + # (the "park" event is used as an example): + # * park(..., run_action = true) - Fires the "park" event, + # transitioning from the current state to the next valid state. If the + # last argument is a boolean, it will control whether the machine's action + # gets run. + # * park!(..., run_action = true) - Fires the "park" event, + # transitioning from the current state to the next valid state. If the + # transition fails, then a StateMachines::InvalidTransition error will be + # raised. If the last argument is a boolean, it will control whether the + # machine's action gets run. + # * can_park?(requirements = {}) - Checks whether the "park" event + # can be fired given the current state of the object. This will *not* run + # validations or callbacks in ORM integrations. It will only determine if + # the state machine defines a valid transition for the event. To check + # whether an event can fire *and* passes validations, use event attributes + # (e.g. state_event) as described in the "Events" documentation of each + # ORM integration. + # * park_transition(requirements = {}) - Gets the next transition + # that would be performed if the "park" event were to be fired now on the + # object or nil if no transitions can be performed. Like can_park? + # this will also *not* run validations or callbacks. It will only + # determine if the state machine defines a valid transition for the event. + # + # With a namespace of "car", the above names map to the following methods: + # * can_park_car? + # * park_car_transition + # * park_car + # * park_car! + # + # The can_park? and park_transition helpers both take an + # optional set of requirements for determining what transitions are available + # for the current object. These requirements include: + # * :from - One or more states to transition from. If none are + # specified, then this will be the object's current state. + # * :to - One or more states to transition to. If none are + # specified, then this will match any to state. + # * :guard - Whether to guard transitions with the if/unless + # conditionals defined for each one. Default is true. # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1622 - def after_initialize; end - - # Gets the initial attribute value defined by the owner class (outside of - # the machine's definition). By default, this is always nil. + # == Defining transitions # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1636 - def owner_class_attribute_default; end - - # Checks whether the given state matches the attribute default specified - # by the owner class + # +event+ requires a block which allows you to define the possible + # transitions that can happen as a result of that event. For example, # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1642 - def owner_class_attribute_default_matches?(state); end - - # Always yields + # event :park, :stop do + # transition :idling => :parked + # end # - # pkg:gem/state_machines#lib/state_machines/machine.rb:1630 - def transaction(_object); end -end - -# pkg:gem/state_machines#lib/state_machines/machine/action_hooks.rb:5 -module StateMachines::Machine::ActionHooks - protected - - # The method to hook into for triggering transitions when invoked. By - # default, this is the action configured for the machine. + # event :first_gear do + # transition :parked => :first_gear, :if => :seatbelt_on? + # transition :parked => same # Allow to loopback if seatbelt is off + # end # - # Since the default hook technique relies on module inheritance, the - # action must be defined in an ancestor of the owner classs in order for - # it to be the action hook. + # See StateMachines::Event#transition for more information on + # the possible options that can be passed in. # - # pkg:gem/state_machines#lib/state_machines/machine/action_hooks.rb:48 - def action_hook; end - - # Adds helper methods for automatically firing events when an action - # is invoked + # *Note* that this block is executed within the context of the actual event + # object. As a result, you will not be able to reference any class methods + # on the model without referencing the class itself. For example, # - # pkg:gem/state_machines#lib/state_machines/machine/action_hooks.rb:17 - def define_action_helpers; end - - # Determines whether action helpers should be defined for this machine. - # This is only true if there is an action configured and no other machines - # have process this same configuration already. + # class Vehicle + # def self.safe_states + # [:parked, :idling, :stalled] + # end # - # pkg:gem/state_machines#lib/state_machines/machine/action_hooks.rb:11 - def define_action_helpers?; end - - # Hooks directly into actions by defining the same method in an included - # module. As a result, when the action gets invoked, any state events - # defined for the object will get run. Method visibility is preserved. + # state_machine do + # event :park do + # transition Vehicle.safe_states => :parked + # end + # end + # end # - # pkg:gem/state_machines#lib/state_machines/machine/action_hooks.rb:27 - def define_action_hook; end -end - -# AsyncMode extensions for the Machine class -# Provides async-aware methods while maintaining backward compatibility -# -# pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:10 -module StateMachines::Machine::AsyncExtensions - # Check if this specific machine instance has async mode enabled + # == Overriding the event method # - # pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:63 - def async_mode_enabled?; end - - # Configure this specific machine instance for async mode + # By default, this will define an instance method (with the same name as the + # event) that will fire the next possible transition for that. Although the + # +before_transition+, +after_transition+, and +around_transition+ hooks + # allow you to define behavior that gets executed as a result of the event's + # transition, you can also override the event method in order to have a + # little more fine-grained control. + # + # For example: # - # Example: # class Vehicle - # state_machine initial: :parked do - # configure_async_mode! # Enable async for this machine + # state_machine do + # event :park do + # ... + # end + # end # - # event :ignite do - # transition parked: :idling + # def park(*) + # take_deep_breath # Executes before the transition (and before_transition hooks) even if no transition is possible + # if result = super # Runs the transition and all before/after/around hooks + # applaud # Executes after the transition (and after_transition hooks) # end + # result # end # end # - # pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:25 - def configure_async_mode!(enabled = T.unsafe(nil)); end - - # Thread-safe version of state reading + # There are a few important things to note here. First, the method + # signature is defined with an unlimited argument list in order to allow + # callers to continue passing arguments that are expected by state_machine. + # For example, it will still allow calls to +park+ with a single parameter + # for skipping the configured action. # - # pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:68 - def read_safely(object, attribute, ivar = T.unsafe(nil)); end - - # Thread-safe callback execution for async operations + # Second, the overridden event method must call +super+ in order to run the + # logic for running the next possible transition. In order to remain + # consistent with other events, the result of +super+ is returned. # - # pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:78 - def run_callbacks_safely(type, object, context, transition); end - - # Thread-safe version of state writing + # Third, any behavior defined in this method will *not* get executed if + # you're taking advantage of attribute-based event transitions. For example: # - # pkg:gem/state_machines#lib/state_machines/machine/async_extensions.rb:73 - def write_safely(object, attribute, value, ivar = T.unsafe(nil)); end -end - -# pkg:gem/state_machines#lib/state_machines/machine/callbacks.rb:5 -module StateMachines::Machine::Callbacks - # Creates a callback that will be invoked after a transition has failed - # to be performed. + # vehicle = Vehicle.new + # vehicle.state_event = 'park' + # vehicle.save # - # pkg:gem/state_machines#lib/state_machines/machine/callbacks.rb:47 - def after_failure(*args, **options, &_arg2); end - - # Creates a callback that will be invoked *after* a transition is - # performed so long as the given requirements match the transition. + # In this case, the +park+ event will run the before/after/around transition + # hooks and transition the state, but the behavior defined in the overriden + # +park+ method will *not* be executed. # - # pkg:gem/state_machines#lib/state_machines/machine/callbacks.rb:21 - def after_transition(*args, **options, &_arg2); end - - # Creates a callback that will be invoked *around* a transition so long - # as the given requirements match the transition. + # == Defining additional arguments # - # pkg:gem/state_machines#lib/state_machines/machine/callbacks.rb:34 - def around_transition(*args, **options, &_arg2); end - - # Creates a callback that will be invoked *before* a transition is - # performed so long as the given requirements match the transition. + # Additional arguments can be passed into events and accessed by transition + # hooks like so: # - # pkg:gem/state_machines#lib/state_machines/machine/callbacks.rb:8 - def before_transition(*args, **options, &_arg2); end -end - -# pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:5 -module StateMachines::Machine::ClassMethods - # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:55 - def default_messages; end - - # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:63 - def default_messages=(messages); end - - # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:47 - def draw(*_arg0); end - - # Attempts to find or create a state machine for the given class. For - # example, + # class Vehicle + # state_machine do + # after_transition :on => :park do |vehicle, transition| + # kind = *transition.args # :parallel + # ... + # end + # after_transition :on => :park, :do => :take_deep_breath # - # StateMachines::Machine.find_or_create(Vehicle) - # StateMachines::Machine.find_or_create(Vehicle, :initial => :parked) - # StateMachines::Machine.find_or_create(Vehicle, :status) - # StateMachines::Machine.find_or_create(Vehicle, :status, :initial => :parked) + # event :park do + # ... + # end # - # If a machine of the given name already exists in one of the class's - # superclasses, then a copy of that machine will be created and stored - # in the new owner class (the original will remain unchanged). + # def take_deep_breath(transition) + # kind = *transition.args # :parallel + # ... + # end + # end + # end # - # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:17 - def find_or_create(owner_class, *args, &_arg2); end - - # Default messages to use for validation errors in ORM integrations - # Thread-safe access via atomic operations on simple values + # vehicle = Vehicle.new + # vehicle.park(:parallel) # - # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:53 - def ignore_method_conflicts; end - - # Default messages to use for validation errors in ORM integrations - # Thread-safe access via atomic operations on simple values + # *Remember* that if the last argument is a boolean, it will be used as the + # +run_action+ parameter to the event action. Using the +park+ action + # example from above, you can might call it like so: # - # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:53 - def ignore_method_conflicts=(_arg0); end - - # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:77 - def renderer; end - - # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:75 - def renderer=(_arg0); end - - # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:68 - def replace_messages(message_hash); end - - private - - # Deep freezes a hash and all its string values for thread safety + # vehicle.park # => Uses default args and runs machine action + # vehicle.park(:parallel) # => Specifies the +kind+ argument and runs the machine action + # vehicle.park(:parallel, false) # => Specifies the +kind+ argument and *skips* the machine action # - # pkg:gem/state_machines#lib/state_machines/machine/class_methods.rb:86 - def deep_freeze_hash(hash); end -end - -# pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:5 -module StateMachines::Machine::Configuration - # Initializes a new state machine with the given configuration. + # If you decide to override the +park+ event method *and* define additional + # arguments, you can do so as shown below: + # + # class Vehicle + # state_machine do + # event :park do + # ... + # end + # end + # + # def park(kind = :parallel, *args) + # take_deep_breath if kind == :parallel + # super + # end + # end + # + # Note that +super+ is called instead of super(*args). This allow + # the entire arguments list to be accessed by transition callbacks through + # StateMachines::Transition#args. + # + # === Using matchers + # + # The +all+ / +any+ matchers can be used to easily execute blocks for a + # group of events. Note, however, that you cannot use these matchers to + # set configurations for events. Blocks using these matchers can be + # defined at any point in the state machine and will always get applied to + # the proper events. + # + # For example: # - # pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:7 - def initialize(owner_class, *args, &_arg2); end - - # Gets the attribute name for the given machine scope. + # state_machine :initial => :parked do + # ... # - # pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:123 - def attribute(name = T.unsafe(nil)); end - - # Sets the initial state of the machine. This can be either the static name - # of a state or a lambda block which determines the initial state at - # creation time. + # event all - [:crash] do + # transition :stalled => :parked + # end + # end # - # pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:109 - def initial_state=(new_initial_state); end - - # Sets the class which is the owner of this state machine. Any methods - # generated by states, events, or other parts of the machine will be defined - # on the given owner class. + # == Example # - # pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:81 - def owner_class=(klass); end - - private - - # Creates a copy of this machine in addition to copies of each associated - # event/states/callback, so that the modifications to those collections do - # not affect the original machine. + # class Vehicle + # state_machine do + # # The park, stop, and halt events will all share the given transitions + # event :park, :stop, :halt do + # transition [:idling, :backing_up] => :parked + # end # - # pkg:gem/state_machines#lib/state_machines/machine/configuration.rb:66 - def initialize_copy(orig); end -end - -# pkg:gem/state_machines#lib/state_machines/machine/event_methods.rb:5 -module StateMachines::Machine::EventMethods - # Defines one or more events for the machine and the transitions that can - # be performed when those events are run. + # event :stop do + # transition :first_gear => :idling + # end + # + # event :ignite do + # transition :parked => :idling + # transition :idling => same # Allow ignite while still idling + # end + # end + # end # - # pkg:gem/state_machines#lib/state_machines/machine/event_methods.rb:8 + # pkg:gem/state_machines#lib/state_machines/machine/event_methods.rb:232 def event(*names, &_arg1); end - # pkg:gem/state_machines#lib/state_machines/machine/event_methods.rb:37 + # pkg:gem/state_machines#lib/state_machines/machine/event_methods.rb:261 def on(*names, &_arg1); end - # Gets the list of all possible transition paths from the current state to - # the given target state. If multiple target states are provided, then - # this will return all possible paths to those states. + # Generates a list of the possible transition sequences that can be run on + # the given object. These paths can reveal all of the possible states and + # events that can be encountered in the object's state machine based on the + # object's current state. + # + # Configuration options: + # * +from+ - The initial state to start all paths from. By default, this + # is the object's current state. + # * +to+ - The target state to end all paths on. By default, paths will + # end when they loop back to the first transition on the path. + # * +deep+ - Whether to allow the target state to be crossed more than once + # in a path. By default, paths will immediately stop when the target + # state (if specified) is reached. If this is enabled, then paths can + # continue even after reaching the target state; they will stop when + # reaching the target state a second time. + # + # *Note* that the object is never modified when the list of paths is + # generated. + # + # == Examples + # + # class Vehicle + # state_machine :initial => :parked do + # event :ignite do + # transition :parked => :idling + # end + # + # event :shift_up do + # transition :idling => :first_gear, :first_gear => :second_gear + # end + # + # event :shift_down do + # transition :second_gear => :first_gear, :first_gear => :idling + # end + # end + # end + # + # vehicle = Vehicle.new # => # + # vehicle.state # => "parked" # - # pkg:gem/state_machines#lib/state_machines/machine/event_methods.rb:54 + # vehicle.state_paths + # # => [ + # # [#, + # # #, + # # #, + # # #, + # # #], + # # + # # [#, + # # #, + # # #] + # # ] + # + # vehicle.state_paths(:from => :parked, :to => :second_gear) + # # => [ + # # [#, + # # #, + # # #] + # # ] + # + # In addition to getting the possible paths that can be accessed, you can + # also get summary information about the states / events that can be + # accessed at some point along one of the paths. For example: + # + # # Get the list of states that can be accessed from the current state + # vehicle.state_paths.to_states # => [:idling, :first_gear, :second_gear] + # + # # Get the list of events that can be accessed from the current state + # vehicle.state_paths.events # => [:ignite, :shift_up, :shift_down] + # + # pkg:gem/state_machines#lib/state_machines/machine/event_methods.rb:431 def paths_for(object, requirements = T.unsafe(nil)); end # Creates a new transition that determines what to change the current state # to when an event fires. # - # pkg:gem/state_machines#lib/state_machines/machine/event_methods.rb:41 + # == Defining transitions + # + # The options for a new transition uses the Hash syntax to map beginning + # states to ending states. For example, + # + # transition :parked => :idling, :idling => :first_gear, :on => :ignite + # + # In this case, when the +ignite+ event is fired, this transition will cause + # the state to be +idling+ if it's current state is +parked+ or +first_gear+ + # if it's current state is +idling+. + # + # To help define these implicit transitions, a set of helpers are available + # for slightly more complex matching: + # * all - Matches every state in the machine + # * all - [:parked, :idling, ...] - Matches every state except those specified + # * any - An alias for +all+ (matches every state in the machine) + # * same - Matches the same state being transitioned from + # + # See StateMachines::MatcherHelpers for more information. + # + # Examples: + # + # transition all => nil, :on => :ignite # Transitions to nil regardless of the current state + # transition all => :idling, :on => :ignite # Transitions to :idling regardless of the current state + # transition all - [:idling, :first_gear] => :idling, :on => :ignite # Transitions every state but :idling and :first_gear to :idling + # transition nil => :idling, :on => :ignite # Transitions to :idling from the nil state + # transition :parked => :idling, :on => :ignite # Transitions to :idling if :parked + # transition [:parked, :stalled] => :idling, :on => :ignite # Transitions to :idling if :parked or :stalled + # + # transition :parked => same, :on => :park # Loops :parked back to :parked + # transition [:parked, :stalled] => same, :on => [:park, :stall] # Loops either :parked or :stalled back to the same state on the park and stall events + # transition all - :parked => same, :on => :noop # Loops every state but :parked back to the same state + # + # # Transitions to :idling if :parked, :first_gear if :idling, or :second_gear if :first_gear + # transition :parked => :idling, :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up + # + # == Verbose transitions + # + # Transitions can also be defined use an explicit set of configuration + # options: + # * :from - A state or array of states that can be transitioned from. + # If not specified, then the transition can occur for *any* state. + # * :to - The state that's being transitioned to. If not specified, + # then the transition will simply loop back (i.e. the state will not change). + # * :except_from - A state or array of states that *cannot* be + # transitioned from. + # + # These options must be used when defining transitions within the context + # of a state. + # + # Examples: + # + # transition :to => nil, :on => :park + # transition :to => :idling, :on => :ignite + # transition :except_from => [:idling, :first_gear], :to => :idling, :on => :ignite + # transition :from => nil, :to => :idling, :on => :ignite + # transition :from => [:parked, :stalled], :to => :idling, :on => :ignite + # + # == Conditions + # + # In addition to the state requirements for each transition, a condition + # can also be defined to help determine whether that transition is + # available. These options will work on both the normal and verbose syntax. + # + # Configuration options: + # * :if - A method, proc or string to call to determine if the + # transition should occur (e.g. :if => :moving?, or :if => lambda {|vehicle| vehicle.speed > 60}). + # The condition should return or evaluate to true or false. + # * :unless - A method, proc or string to call to determine if the + # transition should not occur (e.g. :unless => :stopped?, or :unless => lambda {|vehicle| vehicle.speed <= 60}). + # The condition should return or evaluate to true or false. + # + # Examples: + # + # transition :parked => :idling, :on => :ignite, :if => :moving? + # transition :parked => :idling, :on => :ignite, :unless => :stopped? + # transition :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up, :if => :seatbelt_on? + # + # transition :from => :parked, :to => :idling, :on => ignite, :if => :moving? + # transition :from => :parked, :to => :idling, :on => ignite, :unless => :stopped? + # + # == Order of operations + # + # Transitions are evaluated in the order in which they're defined. As a + # result, if more than one transition applies to a given object, then the + # first transition that matches will be performed. + # + # pkg:gem/state_machines#lib/state_machines/machine/event_methods.rb:352 def transition(options); end end @@ -2693,14 +3060,45 @@ end module StateMachines::Machine::StateMethods # Whether a dynamic initial state is being used in the machine # - # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:14 + # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:45 def dynamic_initial_state?; end # Gets the initial state of the machine for the given object. If a dynamic # initial state was configured for this machine, then the object will be # passed into the lambda block to help determine the actual state. # - # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:9 + # == Examples + # + # With a static initial state: + # + # class Vehicle + # state_machine :initial => :parked do + # ... + # end + # end + # + # vehicle = Vehicle.new + # Vehicle.state_machine.initial_state(vehicle) # => # + # + # With a dynamic initial state: + # + # class Vehicle + # attr_accessor :force_idle + # + # state_machine :initial => lambda {|vehicle| vehicle.force_idle ? :idling : :parked} do + # ... + # end + # end + # + # vehicle = Vehicle.new + # + # vehicle.force_idle = true + # Vehicle.state_machine.initial_state(vehicle) # => # + # + # vehicle.force_idle = false + # Vehicle.state_machine.initial_state(vehicle) # => # + # + # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:40 def initial_state(object); end # Initializes the state on the given object. Initial values are only set if @@ -2712,25 +3110,291 @@ module StateMachines::Machine::StateMethods # * :to - A hash to set the initial value in instead of writing # directly to the object # - # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:26 + # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:57 def initialize_state(object, options = T.unsafe(nil)); end - # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:73 + # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:370 def other_states(*names, &_arg1); end # Gets the current value stored in the given object's attribute. # - # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:76 + # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:373 def read(object, attribute, ivar = T.unsafe(nil)); end # Customizes the definition of one or more states in the machine. # - # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:40 + # Configuration options: + # * :value - The actual value to store when an object transitions + # to the state. Default is the name (stringified). + # * :cache - If a dynamic value (via a lambda block) is being used, + # then setting this to true will cache the evaluated result + # * :if - Determines whether an object's value matches the state + # (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}). + # By default, the configured value is matched. + # * :human_name - The human-readable version of this state's name. + # By default, this is either defined by the integration or stringifies the + # name and converts underscores to spaces. + # + # == Customizing the stored value + # + # Whenever a state is automatically discovered in the state machine, its + # default value is assumed to be the stringified version of the name. For + # example, + # + # class Vehicle + # state_machine :initial => :parked do + # event :ignite do + # transition :parked => :idling + # end + # end + # end + # + # In the above state machine, there are two states automatically discovered: + # :parked and :idling. These states, by default, will store their stringified + # equivalents when an object moves into that state (e.g. "parked" / "idling"). + # + # For legacy systems or when tying state machines into existing frameworks, + # it's oftentimes necessary to need to store a different value for a state + # than the default. In order to continue taking advantage of an expressive + # state machine and helper methods, every defined state can be re-configured + # with a custom stored value. For example, + # + # class Vehicle + # state_machine :initial => :parked do + # event :ignite do + # transition :parked => :idling + # end + # + # state :idling, :value => 'IDLING' + # state :parked, :value => 'PARKED + # end + # end + # + # This is also useful if being used in association with a database and, + # instead of storing the state name in a column, you want to store the + # state's foreign key: + # + # class VehicleState < ActiveRecord::Base + # end + # + # class Vehicle < ActiveRecord::Base + # state_machine :attribute => :state_id, :initial => :parked do + # event :ignite do + # transition :parked => :idling + # end + # + # states.each do |state| + # self.state(state.name, :value => lambda { VehicleState.find_by_name(state.name.to_s).id }, :cache => true) + # end + # end + # end + # + # In the above example, each known state is configured to store it's + # associated database id in the +state_id+ attribute. Also, notice that a + # lambda block is used to define the state's value. This is required in + # situations (like testing) where the model is loaded without any existing + # data (i.e. no VehicleState records available). + # + # One caveat to the above example is to keep performance in mind. To avoid + # constant db hits for looking up the VehicleState ids, the value is cached + # by specifying the :cache option. Alternatively, a custom + # caching strategy can be used like so: + # + # class VehicleState < ActiveRecord::Base + # cattr_accessor :cache_store + # self.cache_store = ActiveSupport::Cache::MemoryStore.new + # + # def self.find_by_name(name) + # cache_store.fetch(name) { find(:first, :conditions => {:name => name}) } + # end + # end + # + # === Dynamic values + # + # In addition to customizing states with other value types, lambda blocks + # can also be specified to allow for a state's value to be determined + # dynamically at runtime. For example, + # + # class Vehicle + # state_machine :purchased_at, :initial => :available do + # event :purchase do + # transition all => :purchased + # end + # + # event :restock do + # transition all => :available + # end + # + # state :available, :value => nil + # state :purchased, :if => lambda {|value| !value.nil?}, :value => lambda {Time.now} + # end + # end + # + # In the above definition, the :purchased state is customized with + # both a dynamic value *and* a value matcher. + # + # When an object transitions to the purchased state, the value's lambda + # block will be called. This will get the current time and store it in the + # object's +purchased_at+ attribute. + # + # *Note* that the custom matcher is very important here. Since there's no + # way for the state machine to figure out an object's state when it's set to + # a runtime value, it must be explicitly defined. If the :if option + # were not configured for the state, then an ArgumentError exception would + # be raised at runtime, indicating that the state machine could not figure + # out what the current state of the object was. + # + # == Behaviors + # + # Behaviors define a series of methods to mixin with objects when the current + # state matches the given one(s). This allows instance methods to behave + # a specific way depending on what the value of the object's state is. + # + # For example, + # + # class Vehicle + # attr_accessor :driver + # attr_accessor :passenger + # + # state_machine :initial => :parked do + # event :ignite do + # transition :parked => :idling + # end + # + # state :parked do + # def speed + # 0 + # end + # + # def rotate_driver + # driver = self.driver + # self.driver = passenger + # self.passenger = driver + # true + # end + # end + # + # state :idling, :first_gear do + # def speed + # 20 + # end + # + # def rotate_driver + # self.state = 'parked' + # rotate_driver + # end + # end + # + # other_states :backing_up + # end + # end + # + # In the above example, there are two dynamic behaviors defined for the + # class: + # * +speed+ + # * +rotate_driver+ + # + # Each of these behaviors are instance methods on the Vehicle class. However, + # which method actually gets invoked is based on the current state of the + # object. Using the above class as the example: + # + # vehicle = Vehicle.new + # vehicle.driver = 'John' + # vehicle.passenger = 'Jane' + # + # # Behaviors in the "parked" state + # vehicle.state # => "parked" + # vehicle.speed # => 0 + # vehicle.rotate_driver # => true + # vehicle.driver # => "Jane" + # vehicle.passenger # => "John" + # + # vehicle.ignite # => true + # + # # Behaviors in the "idling" state + # vehicle.state # => "idling" + # vehicle.speed # => 20 + # vehicle.rotate_driver # => true + # vehicle.driver # => "John" + # vehicle.passenger # => "Jane" + # + # As can be seen, both the +speed+ and +rotate_driver+ instance method + # implementations changed how they behave based on what the current state + # of the vehicle was. + # + # === Invalid behaviors + # + # If a specific behavior has not been defined for a state, then a + # NoMethodError exception will be raised, indicating that that method would + # not normally exist for an object with that state. + # + # Using the example from before: + # + # vehicle = Vehicle.new + # vehicle.state = 'backing_up' + # vehicle.speed # => NoMethodError: undefined method 'speed' for # in state "backing_up" + # + # === Using matchers + # + # The +all+ / +any+ matchers can be used to easily define behaviors for a + # group of states. Note, however, that you cannot use these matchers to + # set configurations for states. Behaviors using these matchers can be + # defined at any point in the state machine and will always get applied to + # the proper states. + # + # For example: + # + # state_machine :initial => :parked do + # ... + # + # state all - [:parked, :idling, :stalled] do + # validates_presence_of :speed + # + # def speed + # gear * 10 + # end + # end + # end + # + # == State-aware class methods + # + # In addition to defining scopes for instance methods that are state-aware, + # the same can be done for certain types of class methods. + # + # Some libraries have support for class-level methods that only run certain + # behaviors based on a conditions hash passed in. For example: + # + # class Vehicle < ActiveRecord::Base + # state_machine do + # ... + # state :first_gear, :second_gear, :third_gear do + # validates_presence_of :speed + # validates_inclusion_of :speed, :in => 0..25, :if => :in_school_zone? + # end + # end + # end + # + # In the above ActiveRecord model, two validations have been defined which + # will *only* run when the Vehicle object is in one of the three states: + # +first_gear+, +second_gear+, or +third_gear. Notice, also, that if/unless + # conditions can continue to be used. + # + # This functionality is not library-specific and can work for any class-level + # method that is defined like so: + # + # def validates_presence_of(attribute, options = {}) + # ... + # end + # + # The minimum requirement is that the last argument in the method be an + # options hash which contains at least :if condition support. + # + # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:337 def state(*names, &_arg1); end # Sets a new value in the given object's attribute. # - # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:86 + # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:383 def write(object, attribute, value, ivar = T.unsafe(nil)); end protected @@ -2738,7 +3402,7 @@ module StateMachines::Machine::StateMethods # Determines if the machine's attribute needs to be initialized. This # will only be true if the machine's attribute is blank. # - # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:95 + # pkg:gem/state_machines#lib/state_machines/machine/state_methods.rb:392 def initialize_state?(object); end end @@ -2749,32 +3413,33 @@ module StateMachines::Machine::Utilities # Adds sibling machine configurations to the current machine. This # will add states from other machines that have the same attribute. # - # pkg:gem/state_machines#lib/state_machines/machine/utilities.rb:77 + # pkg:gem/state_machines#lib/state_machines/machine/utilities.rb:78 def add_sibling_machine_configs; end - # Looks up the ancestor class that has the given method defined. This - # is used to find the method owner which is used to determine where to - # define new methods. + # Determines whether there's already a helper method defined within the + # given scope. This is true only if one of the owner's ancestors defines + # the method and is further along in the ancestor chain than this + # machine's helper module. # - # pkg:gem/state_machines#lib/state_machines/machine/utilities.rb:23 + # pkg:gem/state_machines#lib/state_machines/machine/utilities.rb:24 def owner_class_ancestor_has_method?(scope, method); end # Determines whether the given method is defined in the owner class or # in a superclass. # - # pkg:gem/state_machines#lib/state_machines/machine/utilities.rb:52 + # pkg:gem/state_machines#lib/state_machines/machine/utilities.rb:53 def owner_class_has_method?(scope, method); end # Pluralizes the given word using #pluralize (if available) or simply # adding an "s" to the end of the word # - # pkg:gem/state_machines#lib/state_machines/machine/utilities.rb:59 + # pkg:gem/state_machines#lib/state_machines/machine/utilities.rb:60 def pluralize(word); end # Generates the results for the given scope based on one or more states to # filter by # - # pkg:gem/state_machines#lib/state_machines/machine/utilities.rb:70 + # pkg:gem/state_machines#lib/state_machines/machine/utilities.rb:71 def run_scope(scope, machine, klass, states); end # Looks up other machines that have been defined in the owner class and @@ -4021,16 +4686,16 @@ class StateMachines::State # pkg:gem/state_machines#lib/state_machines/state.rb:122 def final?; end + # The human-readable name for the state # Transforms the state name into a more human-readable format, such as # "first gear" instead of "first_gear" - # The human-readable name for the state # # pkg:gem/state_machines#lib/state_machines/state.rb:134 def human_name(klass = T.unsafe(nil)); end + # The human-readable name for the state # Transforms the state name into a more human-readable format, such as # "first gear" instead of "first_gear" - # The human-readable name for the state # # pkg:gem/state_machines#lib/state_machines/state.rb:26 def human_name=(_arg0); end @@ -4108,6 +4773,8 @@ class StateMachines::State # pkg:gem/state_machines#lib/state_machines/state.rb:23 def qualified_name; end + # The value that is written to a machine's attribute when an object + # transitions into this state # The value that represents this state. This will optionally evaluate the # original block if it's a lambda block. Otherwise, the static value is # returned. @@ -4117,12 +4784,12 @@ class StateMachines::State # State.new(machine, :parked, :value => 1).value # => 1 # State.new(machine, :parked, :value => lambda {Time.now}).value # => Tue Jan 01 00:00:00 UTC 2008 # State.new(machine, :parked, :value => lambda {Time.now}).value(false) # => - # The value that is written to a machine's attribute when an object - # transitions into this state # # pkg:gem/state_machines#lib/state_machines/state.rb:167 def value(eval = T.unsafe(nil)); end + # The value that is written to a machine's attribute when an object + # transitions into this state # The value that represents this state. This will optionally evaluate the # original block if it's a lambda block. Otherwise, the static value is # returned. @@ -4132,8 +4799,6 @@ class StateMachines::State # State.new(machine, :parked, :value => 1).value # => 1 # State.new(machine, :parked, :value => lambda {Time.now}).value # => Tue Jan 01 00:00:00 UTC 2008 # State.new(machine, :parked, :value => lambda {Time.now}).value(false) # => - # The value that is written to a machine's attribute when an object - # transitions into this state # # pkg:gem/state_machines#lib/state_machines/state.rb:30 def value=(_arg0); end @@ -4485,6 +5150,15 @@ class StateMachines::Transition # pkg:gem/state_machines#lib/state_machines/transition.rb:140 def attributes; end + # Completes a transition whose before phase has already run by executing + # its after callbacks, resuming any paused around callbacks first. Used + # to finish transitions that were stored for deferred completion (e.g. + # generated mid-action from an event attribute) once their action has + # succeeded. + # + # pkg:gem/state_machines#lib/state_machines/transition.rb:354 + def complete_deferred_after_callbacks; end + # The event that triggered the transition # # pkg:gem/state_machines#lib/state_machines/transition.rb:69 @@ -4731,7 +5405,7 @@ class StateMachines::Transition # exception will not bubble up to the caller since +after+ callbacks # should never halt the execution of a +perform+. # - # pkg:gem/state_machines#lib/state_machines/transition.rb:569 + # pkg:gem/state_machines#lib/state_machines/transition.rb:566 def after; end # Runs the machine's +before+ callbacks for this transition. Only @@ -4741,7 +5415,7 @@ class StateMachines::Transition # Once the callbacks are run, they cannot be run again until this transition # is reset. # - # pkg:gem/state_machines#lib/state_machines/transition.rb:508 + # pkg:gem/state_machines#lib/state_machines/transition.rb:505 def before(complete = T.unsafe(nil), index = T.unsafe(nil), &block); end # Gets a hash of the context defining this unique transition (including @@ -4753,7 +5427,7 @@ class StateMachines::Transition # transition = StateMachines::Transition.new(Vehicle.new, machine, :ignite, :parked, :idling) # transition.context # => {:on => :ignite, :from => :parked, :to => :idling} # - # pkg:gem/state_machines#lib/state_machines/transition.rb:592 + # pkg:gem/state_machines#lib/state_machines/transition.rb:589 def context; end # Runs a block that may get paused. If the block doesn't pause, then @@ -4766,15 +5440,21 @@ class StateMachines::Transition # Options: # * :fiber - Whether to use fiber-based execution (default: true) # - # pkg:gem/state_machines#lib/state_machines/transition.rb:360 + # pkg:gem/state_machines#lib/state_machines/transition.rb:373 def pausable(options = T.unsafe(nil)); end # Pauses the current callback execution. This should only occur within # around callbacks when the remainder of the callback will be executed at # a later point in time. # - # pkg:gem/state_machines#lib/state_machines/transition.rb:482 + # pkg:gem/state_machines#lib/state_machines/transition.rb:479 def pause; end + + # Unwraps a result returned from a pausable fiber, re-raising any exception + # captured inside the fiber and importing its exported thread storage + # + # pkg:gem/state_machines#lib/state_machines/transition.rb:464 + def unwrap_fiber_result(result); end end # Represents a collection of transitions in a state machine From abcd789357bbcdc5b3ac12080bd788858c740295 Mon Sep 17 00:00:00 2001 From: Kaan Ozkan Date: Mon, 22 Jun 2026 13:10:12 -0400 Subject: [PATCH 3/4] Cache generic types by constant identity Assisted-By: devx/eb25d669-b4ee-4e28-9b69-129b45e1e178 --- lib/tapioca/runtime/generic_type_registry.rb | 23 ++++++++-------- .../runtime/generic_type_registry_spec.rb | 27 +++++++++++++++++++ 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/lib/tapioca/runtime/generic_type_registry.rb b/lib/tapioca/runtime/generic_type_registry.rb index 923ba2186..9cdb5d5ee 100644 --- a/lib/tapioca/runtime/generic_type_registry.rb +++ b/lib/tapioca/runtime/generic_type_registry.rb @@ -6,7 +6,7 @@ module Runtime # This class is responsible for storing and looking up information related to generic types. # # The class stores 2 different kinds of data, in two separate lookup tables: - # 1. a lookup of generic type instances by name: `@generic_instances` + # 1. a lookup of generic type instances by constant and name: `@generic_instances` # 2. a lookup of type variable serializer by constant and type variable # instance: `@type_variables` # @@ -21,7 +21,7 @@ module Runtime # variable to type variable serializers. This allows us to associate type variables # to the constant names that represent them, easily. module GenericTypeRegistry - @generic_instances = {} #: Hash[String, Module[top]] + @generic_instances = {}.compare_by_identity #: Hash[Module[top], Hash[String, Module[top]]] @type_variables = {}.compare_by_identity #: Hash[Module[top], Array[TypeVariableModule]] @@ -45,8 +45,9 @@ class << self # and cloning the given constant so that we can return a type that is the same # as the current type but is a different instance and has a different name method. # - # We cache those cloned instances by their name in `@generic_instances`, so that - # we don't keep instantiating a new type every single time it is referenced. + # We cache those cloned instances by their original constant and their name in + # `@generic_instances`, so that we don't keep instantiating a new type every single + # time it is referenced. # For example, `[Foo[Integer], Foo[Integer], Foo[Integer], Foo[String]]` will only # result in 2 clones (1 for `Foo[Integer]` and another for `Foo[String]`) and # 2 hash lookups (for the other two `Foo[Integer]`s). @@ -64,12 +65,15 @@ def register_type(constant, types) # # Also, we try to memoize the generic type based on the name, so that # we don't have to keep recreating them all the time. - @generic_instances[name] ||= create_generic_type(constant, name) + generic_instances = @generic_instances[constant] ||= {} + generic_instances[name] ||= create_generic_type(constant, name) end #: (Object instance) -> bool def generic_type_instance?(instance) - @generic_instances.values.any? { |generic_type| generic_type === instance } + @generic_instances.values.any? do |generic_instances| + generic_instances.values.any? { |generic_type| generic_type === instance } + end end #: (Module[top] constant) -> Array[TypeVariableModule]? @@ -88,7 +92,7 @@ def lookup_type_variables(constant) # can return it from the original methods as well. #: (untyped constant, TypeVariableModule type_variable) -> void def register_type_variable(constant, type_variable) - type_variables = lookup_or_initialize_type_variables(constant) + type_variables = @type_variables[constant] ||= [] type_variables << type_variable end @@ -163,11 +167,6 @@ def create_safe_subclass(constant) owner.send(:define_method, :inherited, inherited_method) end end - - #: (Module[top] constant) -> Array[TypeVariableModule] - def lookup_or_initialize_type_variables(constant) - @type_variables[constant] ||= [] - end end end end diff --git a/spec/tapioca/runtime/generic_type_registry_spec.rb b/spec/tapioca/runtime/generic_type_registry_spec.rb index 47632356f..f0291804b 100644 --- a/spec/tapioca/runtime/generic_type_registry_spec.rb +++ b/spec/tapioca/runtime/generic_type_registry_spec.rb @@ -57,6 +57,24 @@ class GenericTypeRegistrySpec < Minitest::Spec end end + describe ".register_type" do + it "does not reuse generic instances for redefined constants with the same name" do + first_constant, first_generic_type = register_reloadable_generic + + self.class.send(:remove_const, :ReloadableGeneric) # rubocop:disable RSpec/RemoveConst + + second_constant, second_generic_type = register_reloadable_generic + + refute_same(first_generic_type, second_generic_type) + assert_operator(first_generic_type, :<, first_constant) + assert_operator(second_generic_type, :<, second_constant) + ensure + if self.class.const_defined?(:ReloadableGeneric, false) + self.class.send(:remove_const, :ReloadableGeneric) # rubocop:disable RSpec/RemoveConst + end + end + end + describe "the patch for .inherited on generic classes" do # This is more of an internal detail and cross-cutting concern of all the public APIs, # but it's easier to test here on its own. @@ -74,6 +92,15 @@ class GenericTypeRegistrySpec < Minitest::Spec end end + def register_reloadable_generic + constant = T.let(Class.new, T.untyped) + constant.extend(T::Generic) + constant.const_set(:Element, constant.type_member) + + self.class.const_set(:ReloadableGeneric, constant) + [constant, constant[Object]] + end + class SampleGenericClass extend T::Generic From a6fa2a698e6d5ade97d34b003d143b3fbfcfbb73 Mon Sep 17 00:00:00 2001 From: Kaan Ozkan Date: Mon, 22 Jun 2026 13:11:17 -0400 Subject: [PATCH 4/4] Preserve generic type overrides in Sorbet casts Assisted-By: devx/eb25d669-b4ee-4e28-9b69-129b45e1e178 --- lib/tapioca/internal.rb | 1 + lib/tapioca/sorbet_ext/generic_name_patch.rb | 20 -------- lib/tapioca/sorbet_ext/generic_type_patch.rb | 48 +++++++++++++++++++ .../runtime/generic_type_registry_spec.rb | 20 ++++++++ 4 files changed, 69 insertions(+), 20 deletions(-) create mode 100644 lib/tapioca/sorbet_ext/generic_type_patch.rb diff --git a/lib/tapioca/internal.rb b/lib/tapioca/internal.rb index ac8f8e592..a77eb7c1f 100644 --- a/lib/tapioca/internal.rb +++ b/lib/tapioca/internal.rb @@ -12,6 +12,7 @@ require "tapioca/sorbet_ext/backcompat_patches" require "tapioca/sorbet_ext/name_patch" require "tapioca/sorbet_ext/generic_name_patch" +require "tapioca/sorbet_ext/generic_type_patch" require "tapioca/sorbet_ext/proc_bind_patch" require "tapioca/sorbet_ext/void_patch" require "tapioca/runtime/generic_type_registry" diff --git a/lib/tapioca/sorbet_ext/generic_name_patch.rb b/lib/tapioca/sorbet_ext/generic_name_patch.rb index aa3553d52..aa229c5f8 100644 --- a/lib/tapioca/sorbet_ext/generic_name_patch.rb +++ b/lib/tapioca/sorbet_ext/generic_name_patch.rb @@ -82,26 +82,6 @@ def name prepend GenericPatch end end - - module Utils - module Private - module PrivateCoercePatch - def coerce_and_check_module_types(val, check_val, check_module_type) - if val.is_a?(Tapioca::TypeVariableModule) - val.coerce_to_type_variable - elsif val.respond_to?(:__tapioca_override_type) - val.__tapioca_override_type - else - super - end - end - end - - class << self - prepend(PrivateCoercePatch) - end - end - end end module Tapioca diff --git a/lib/tapioca/sorbet_ext/generic_type_patch.rb b/lib/tapioca/sorbet_ext/generic_type_patch.rb new file mode 100644 index 000000000..040da07d4 --- /dev/null +++ b/lib/tapioca/sorbet_ext/generic_type_patch.rb @@ -0,0 +1,48 @@ +# typed: true +# frozen_string_literal: true + +module T + module Utils + module Private + # Preserve Tapioca's generic type variables and instantiated generic + # names when Sorbet coerces them into runtime types. + module TapiocaGenericTypeCoercePatch + def coerce_and_check_module_types(val, check_val, check_module_type) + if val.is_a?(Tapioca::TypeVariableModule) + val.coerce_to_type_variable + elsif val.respond_to?(:__tapioca_override_type) + val.__tapioca_override_type + else + super + end + end + end + + class << self + prepend(TapiocaGenericTypeCoercePatch) + end + end + end + + module Private + module Casts + module TapiocaGenericTypeCastPatch + # https://github.com/sorbet/sorbet/commit/b8d64c7fd9a08e2b9159b5d592bc2de6d586b44a + # inlines the Module fast path in `T.let`, `T.cast`, `T.bind`, and + # `T.assert_type!`, so generic module clones can reach this cast path + # without going through `T::Utils::Private::TapiocaGenericTypeCoercePatch`. + def cast(value, type, cast_method) + if type.respond_to?(:__tapioca_override_type) + type = type.__tapioca_override_type + end + + super(value, type, cast_method) + end + end + + class << self + prepend(TapiocaGenericTypeCastPatch) + end + end + end +end diff --git a/spec/tapioca/runtime/generic_type_registry_spec.rb b/spec/tapioca/runtime/generic_type_registry_spec.rb index f0291804b..3f2b137f0 100644 --- a/spec/tapioca/runtime/generic_type_registry_spec.rb +++ b/spec/tapioca/runtime/generic_type_registry_spec.rb @@ -58,6 +58,10 @@ class GenericTypeRegistrySpec < Minitest::Spec end describe ".register_type" do + it "allows generic interface implementations to be cast to generic interface types" do + T.let(SampleGenericInterfaceImplementation.new, SampleGenericInterface[Object]) + end + it "does not reuse generic instances for redefined constants with the same name" do first_constant, first_generic_type = register_reloadable_generic @@ -107,6 +111,22 @@ class SampleGenericClass Element = type_member end + module SampleGenericInterface + extend T::Generic + + interface! + + Element = type_member + end + + class SampleGenericInterfaceImplementation + extend T::Generic + + include SampleGenericInterface + + Element = type_member + end + class RaisesInInheritedCallback extend T::Generic