cattri 0.1.3 → 0.2.1

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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +34 -0
  3. data/.gitignore +72 -0
  4. data/.rubocop.yml +6 -3
  5. data/CHANGELOG.md +63 -0
  6. data/Gemfile +12 -0
  7. data/README.md +166 -151
  8. data/Steepfile +6 -0
  9. data/bin/console +8 -0
  10. data/bin/setup +8 -0
  11. data/cattri.gemspec +5 -5
  12. data/lib/cattri/attribute.rb +119 -155
  13. data/lib/cattri/attribute_compiler.rb +104 -0
  14. data/lib/cattri/attribute_options.rb +183 -0
  15. data/lib/cattri/attribute_registry.rb +155 -0
  16. data/lib/cattri/context.rb +124 -106
  17. data/lib/cattri/context_registry.rb +36 -0
  18. data/lib/cattri/deferred_attributes.rb +73 -0
  19. data/lib/cattri/dsl.rb +54 -0
  20. data/lib/cattri/error.rb +17 -90
  21. data/lib/cattri/inheritance.rb +35 -0
  22. data/lib/cattri/initializer_patch.rb +38 -0
  23. data/lib/cattri/internal_store.rb +104 -0
  24. data/lib/cattri/introspection.rb +56 -49
  25. data/lib/cattri/version.rb +3 -1
  26. data/lib/cattri.rb +38 -99
  27. data/sig/lib/cattri/attribute.rbs +115 -0
  28. data/sig/lib/cattri/attribute_compiler.rbs +61 -0
  29. data/sig/lib/cattri/attribute_options.rbs +150 -0
  30. data/sig/lib/cattri/attribute_registry.rbs +101 -0
  31. data/sig/lib/cattri/context.rbs +130 -0
  32. data/sig/lib/cattri/context_registry.rbs +31 -0
  33. data/sig/lib/cattri/deferred_attributes.rbs +53 -0
  34. data/sig/lib/cattri/dsl.rbs +66 -0
  35. data/sig/lib/cattri/error.rbs +28 -0
  36. data/sig/lib/cattri/inheritance.rbs +21 -0
  37. data/sig/lib/cattri/initializer_patch.rbs +26 -0
  38. data/sig/lib/cattri/internal_store.rbs +75 -0
  39. data/sig/lib/cattri/introspection.rbs +61 -0
  40. data/sig/lib/cattri/types.rbs +9 -0
  41. data/sig/lib/cattri/visibility.rbs +55 -0
  42. data/sig/lib/cattri.rbs +37 -0
  43. data/spec/cattri/attribute_compiler_spec.rb +179 -0
  44. data/spec/cattri/attribute_options_spec.rb +267 -0
  45. data/spec/cattri/attribute_registry_spec.rb +257 -0
  46. data/spec/cattri/attribute_spec.rb +297 -0
  47. data/spec/cattri/context_registry_spec.rb +45 -0
  48. data/spec/cattri/context_spec.rb +346 -0
  49. data/spec/cattri/deferred_attrributes_spec.rb +117 -0
  50. data/spec/cattri/dsl_spec.rb +69 -0
  51. data/spec/cattri/error_spec.rb +37 -0
  52. data/spec/cattri/inheritance_spec.rb +60 -0
  53. data/spec/cattri/initializer_patch_spec.rb +35 -0
  54. data/spec/cattri/internal_store_spec.rb +139 -0
  55. data/spec/cattri/introspection_spec.rb +90 -0
  56. data/spec/cattri/visibility_spec.rb +68 -0
  57. data/spec/cattri_spec.rb +54 -0
  58. data/spec/simplecov_helper.rb +21 -0
  59. data/spec/spec_helper.rb +16 -0
  60. metadata +79 -6
  61. data/lib/cattri/attribute_definer.rb +0 -143
  62. data/lib/cattri/class_attributes.rb +0 -277
  63. data/lib/cattri/instance_attributes.rb +0 -276
  64. data/sig/cattri.rbs +0 -4
@@ -0,0 +1,115 @@
1
+ module Cattri
2
+ # @internal
3
+ #
4
+ # Attribute acts as a thin wrapper around AttributeOptions,
5
+ # exposing core attribute metadata and behavior in a safe, immutable way.
6
+ #
7
+ # Each Attribute instance represents a single logical property,
8
+ # and delegates its behavior (default, visibility, coercion, etc.) to its associated AttributeOptions.
9
+ #
10
+ # @example
11
+ # attribute = Attribute.new(:enabled, default: true, expose: :read_write)
12
+ # attribute.name # => :enabled
13
+ # attribute.default.call # => true
14
+ # attribute.expose # => :read_write
15
+ class Attribute
16
+ @options: AttributeOptions
17
+
18
+ @defined_in: ::Module
19
+
20
+ # @return [Module] the class or module this attribute was defined in
21
+ attr_reader defined_in: ::Module
22
+
23
+ # Initializes a new attribute definition.
24
+ #
25
+ # @param name [Symbol, String] the attribute name
26
+ # @param defined_in [Module] the class or module where this attribute is defined
27
+ # @param options [Hash] configuration options
28
+ # @option options [Boolean] :class whether the attribute is class-level (internally mapped to :class_attribute)
29
+ # @param transformer [Proc] optional block used to coerce/validate assigned values
30
+ def initialize: (
31
+ identifier name,
32
+ defined_in: ::Module,
33
+ ?ivar: identifier,
34
+ ?final: bool,
35
+ ?scope: scope_types,
36
+ ?predicate: bool,
37
+ ?default: ::Proc | untyped | nil,
38
+ ?expose: expose_types,
39
+ ?visibility: visibility_types
40
+ ) { (?) -> untyped } -> void
41
+
42
+ # Serializes this attribute and its configuration to a frozen hash.
43
+ #
44
+ # @return [Hash<Symbol, Object>]
45
+ def to_h: () -> ::Hash[::Symbol, untyped]
46
+
47
+ # @return [Symbol] the canonical name of the attribute
48
+ def name: () -> ::Symbol
49
+
50
+ # @return [Symbol] the backing instance variable (e.g., :@enabled)
51
+ def ivar: () -> ::Symbol
52
+
53
+ # @return [Proc] a callable lambda for the attribute’s default value
54
+ def default: () -> ::Proc
55
+
56
+ # @return [Proc] a callable transformer used to process assigned values
57
+ def transformer: () -> ::Proc
58
+
59
+ # @return [Symbol] method exposure type (:read, :write, :read_write, or :none)
60
+ def expose: () -> expose_types
61
+
62
+ # @return [Symbol] method visibility (:public, :protected, :private)
63
+ def visibility: () -> visibility_types
64
+
65
+ # @return [Boolean] whether the reader should remain internal
66
+ def internal_reader?: () -> bool
67
+
68
+ # @return [Boolean] whether the writer should remain internal
69
+ def internal_writer?: () -> bool
70
+
71
+ # @return [Boolean] whether the attribute allows reading
72
+ def readable?: () -> bool
73
+
74
+ # @return [Boolean] whether the attribute allows writing
75
+ def writable?: () -> bool
76
+
77
+ # @return [Boolean] whether the attribute is marked readonly
78
+ def readonly?: () -> bool
79
+
80
+ # @return [Boolean] whether the attribute is marked final (write-once)
81
+ def final?: () -> bool
82
+
83
+ # @return [Boolean] whether the attribute is class-level
84
+ def class_attribute?: () -> bool
85
+
86
+ # @return [Boolean] whether the attribute defines a predicate method (`:name?`)
87
+ def with_predicate?: () -> bool
88
+
89
+ # Returns the methods that will be defined for this attribute.
90
+ #
91
+ # Includes the base accessor, optional writer, and optional predicate.
92
+ #
93
+ # @return [Array<Symbol>] a list of method names
94
+ def allowed_methods: () -> ::Array[::Symbol]
95
+
96
+ # Validates whether this attribute is assignable in the current context.
97
+ #
98
+ # @raise [Cattri::AttributeError] if assignment is disallowed
99
+ def validate_assignment!: () -> void
100
+
101
+ # Resolves the default value for this attribute.
102
+ #
103
+ # @return [Object] the evaluated default
104
+ # @raise [Cattri::AttributeError] if default evaluation fails
105
+ def evaluate_default: () -> untyped
106
+
107
+ # Processes and transforms an incoming assignment for this attribute.
108
+ #
109
+ # @param args [Array] positional arguments to pass to the transformer
110
+ # @param kwargs [Hash] keyword arguments to pass to the transformer
111
+ # @return [Object] the transformed value
112
+ # @raise [Cattri::AttributeError] if transformation fails
113
+ def process_assignment: (*untyped args, **untyped kwargs) -> untyped
114
+ end
115
+ end
@@ -0,0 +1,61 @@
1
+ module Cattri
2
+ # @internal
3
+ #
4
+ # Responsible for defining methods on the target class/module
5
+ # based on the metadata in a {Cattri::Attribute}.
6
+ #
7
+ # This includes:
8
+ # - callable accessors (acting as both reader and writer)
9
+ # - predicate methods
10
+ # - explicit writers (`:name=` methods)
11
+ #
12
+ # Handles both instance and class-level attributes, including
13
+ # memoization and validation of default values for final attributes.
14
+ class AttributeCompiler
15
+ # Defines accessor methods for the given attribute in the provided context.
16
+ #
17
+ # For `final` + `class_attribute` attributes, the default is eagerly assigned.
18
+ # Then, if permitted by `expose`, the reader, writer, and/or predicate methods are defined.
19
+ #
20
+ # @param attribute [Cattri::Attribute] the attribute to define
21
+ # @param context [Cattri::Context] the target context for method definition
22
+ # @return [void]
23
+ def self.define_accessor: (Attribute attribute, Context context) -> void
24
+
25
+ private
26
+
27
+ # Defines a callable method that acts as both getter and setter.
28
+ #
29
+ # If called with no arguments, it returns the default (memoized).
30
+ # If called with arguments, it processes the assignment and writes the value.
31
+ #
32
+ # @param attribute [Cattri::Attribute]
33
+ # @param context [Cattri::Context]
34
+ # @return [void]
35
+ def self.define_accessor!: (Attribute attribute, Context context) -> void
36
+
37
+ # Defines a writer method `:name=`, assigning a transformed value to the backing store.
38
+ #
39
+ # @param attribute [Cattri::Attribute]
40
+ # @param context [Cattri::Context]
41
+ # @return [void]
42
+ def self.define_writer!: (Attribute attribute, Context context) -> void
43
+
44
+ # Defines a predicate method `:name?` that returns the truthiness of the value.
45
+ #
46
+ # @param attribute [Cattri::Attribute]
47
+ # @param context [Cattri::Context]
48
+ # @return [void]
49
+ def self.define_predicate!: (Attribute attribute, Context context) -> void
50
+
51
+ # Returns the default value for the attribute, memoizing it in the backing store.
52
+ #
53
+ # For `final` attributes, raises unless explicitly initialized.
54
+ #
55
+ # @param receiver [Object] the instance or class receiving the value
56
+ # @param attribute [Cattri::Attribute]
57
+ # @return [Object] the stored or evaluated default
58
+ # @raise [Cattri::AttributeError] if final attribute is unset or evaluation fails
59
+ def self.memoize_default_value: (InternalStore receiver, Attribute attribute) -> untyped
60
+ end
61
+ end
@@ -0,0 +1,150 @@
1
+ module Cattri
2
+ # @internal
3
+ #
4
+ # AttributeOptions encapsulates normalized metadata for a single Cattri-defined attribute.
5
+ #
6
+ # It validates, transforms, and freezes all input during initialization,
7
+ # ensuring attribute safety and immutability at runtime.
8
+ #
9
+ # @example
10
+ # options = AttributeOptions.new(:enabled, default: true, expose: :read_write)
11
+ # options.name # => :enabled
12
+ # options.default.call # => true
13
+ # options.expose # => :read_write
14
+ class AttributeOptions
15
+ @name: ::Symbol
16
+
17
+ @ivar: ::Symbol
18
+
19
+ @final: bool
20
+
21
+ @scope: scope_types
22
+
23
+ @predicate: bool
24
+
25
+ @default: ::Proc
26
+
27
+ @transformer: ::Proc
28
+
29
+ @expose: expose_types
30
+
31
+ @visibility: visibility_types
32
+
33
+ # Validates and normalizes the `expose` configuration.
34
+ #
35
+ # @param expose [Symbol, String] one of: :read, :write, :read_write, :none
36
+ # @return [Symbol]
37
+ # @raise [Cattri::AttributeError] if the value is invalid
38
+ def self.validate_expose!: (expose_types | identifier expose) -> untyped
39
+
40
+ # Validates and normalizes method visibility.
41
+ #
42
+ # @param visibility [Symbol, String] one of: :public, :protected, :private
43
+ # @return [Symbol]
44
+ # @raise [Cattri::AttributeError] if the value is invalid
45
+ def self.validate_visibility!: (visibility_types | identifier visibility) -> untyped
46
+
47
+ # Valid method visibility levels.
48
+ VISIBILITIES: ::Array[visibility_types]
49
+
50
+ # Valid expose options for method generation.
51
+ EXPOSE_OPTIONS: ::Array[expose_types]
52
+
53
+ # Valid scope types.
54
+ SCOPES: ::Array[scope_types]
55
+
56
+ # Built-in Ruby value types that are safe to reuse as-is (no dup needed).
57
+ SAFE_VALUE_TYPES: ::Array[untyped]
58
+
59
+ attr_reader name: ::Symbol
60
+
61
+ attr_reader ivar: ::Symbol
62
+
63
+ attr_reader final: bool
64
+
65
+ attr_reader scope: scope_types
66
+
67
+ attr_reader predicate: bool
68
+
69
+ attr_reader default: ::Proc
70
+
71
+ attr_reader transformer: ::Proc
72
+
73
+ attr_reader expose: expose_types
74
+
75
+ attr_reader visibility: visibility_types
76
+
77
+ # Initializes a frozen attribute configuration.
78
+ #
79
+ # @param name [Symbol, String] the attribute name
80
+ # @param ivar [Symbol, String, nil] optional custom instance variable name
81
+ # @param final [Boolean] marks the attribute as write-once
82
+ # @param class_attribute [Boolean] indicates if the attribute is class-level
83
+ # @param predicate [Boolean] whether to define a `?` predicate method
84
+ # @param default [Object, Proc, nil] default value or callable
85
+ # @param transformer [Proc, nil] optional coercion block
86
+ # @param expose [Symbol] access level to define (:read, :write, :read_write, :none)
87
+ # @param visibility [Symbol] method visibility (:public, :protected, :private)
88
+ def initialize: (
89
+ identifier name,
90
+ ?ivar: identifier?,
91
+ ?final: bool,
92
+ ?scope: scope_types,
93
+ ?predicate: bool,
94
+ ?default: untyped,
95
+ ?transformer: ::Proc?,
96
+ ?expose: expose_types,
97
+ ?visibility: visibility_types
98
+ ) -> void
99
+
100
+ # Returns a frozen hash representation of this option set.
101
+ #
102
+ # @return [Hash<Symbol, Object>]
103
+ def to_h: () -> ::Hash[::Symbol, untyped]
104
+
105
+ # Allows hash-style access to the option set.
106
+ #
107
+ # @param key [Symbol, String]
108
+ # @return [Object]
109
+ def []: (untyped key) -> untyped
110
+
111
+ private
112
+
113
+ # Normalizes the instance variable name, defaulting to @name.
114
+ #
115
+ # @param ivar [String, Symbol, nil]
116
+ # @return [Symbol]
117
+ def normalize_ivar: (identifier? ivar) -> ::Symbol
118
+
119
+ # Wraps the default in a Proc with immutability protection.
120
+ #
121
+ # - Returns original Proc if given.
122
+ # - Wraps immutable types as-is.
123
+ # - Duplicates mutable values at runtime.
124
+ #
125
+ # @param default [Object, Proc, nil]
126
+ # @return [Proc]
127
+ def normalize_default: (::Proc | untyped default) -> ::Proc
128
+
129
+ # Returns a normalized assignment transformer.
130
+ #
131
+ # Falls back to a default transformer that returns:
132
+ # - `kwargs` if `args.empty?`
133
+ # - the single argument if one is passed
134
+ # - `[*args, kwargs]` otherwise
135
+ #
136
+ # @param transformer [Proc, nil]
137
+ # @return [Proc]
138
+ def normalize_transformer: (::Proc? transformer) -> ::Proc
139
+
140
+ # Validates and normalizes the provided scope value.
141
+ #
142
+ # If `scope` is `nil`, it defaults to `:instance`. If it's one of the allowed
143
+ # values (`:class`, `:instance`), it is returned as-is. Otherwise, an error is raised.
144
+ #
145
+ # @param scope [Symbol, nil] the requested attribute scope
146
+ # @return [Symbol] the validated scope (`:class` or `:instance`)
147
+ # @raise [Cattri::AttributeError] if the scope is invalid
148
+ def validate_scope!: (scope_types scope) -> ::Symbol
149
+ end
150
+ end
@@ -0,0 +1,101 @@
1
+ module Cattri
2
+ # Cattri::AttributeRegistry is responsible for managing attribute definitions
3
+ # for a given context (class or module). It validates uniqueness, applies
4
+ # definition logic, and supports inheritance and introspection.
5
+ #
6
+ # It handles both eager and deferred attribute compilation and ensures correct
7
+ # behavior for `class: true`, `final: true`, and other attribute options.
8
+ class AttributeRegistry
9
+ @context: Context
10
+
11
+ @__cattri_registered_attributes: ::Hash[::Symbol, Attribute]
12
+
13
+ # @return [Cattri::Context] the context this registry operates within
14
+ attr_reader context: Context
15
+
16
+ # Initializes a new registry for the provided context.
17
+ #
18
+ # @param context [Cattri::Context]
19
+ def initialize: (Context context) -> void
20
+
21
+ # Returns the attributes registered directly on this context.
22
+ #
23
+ # @return [Hash{Symbol => Cattri::Attribute}]
24
+ def registered_attributes: () -> ::Hash[::Symbol, Attribute]
25
+
26
+ # Returns all known attributes, optionally including inherited definitions.
27
+ #
28
+ # @param with_ancestors [Boolean] whether to include ancestors
29
+ # @return [Hash{Symbol => Cattri::Attribute}]
30
+ def defined_attributes: (?with_ancestors: bool) -> ::Hash[::Symbol, Attribute]
31
+
32
+ # Fetches an attribute by name, or returns nil.
33
+ #
34
+ # @param name [String, Symbol]
35
+ # @param with_ancestors [Boolean]
36
+ # @return [Cattri::Attribute, nil]
37
+ def fetch_attribute: (identifier name, ?with_ancestors: bool) -> Attribute
38
+
39
+ # Fetches an attribute by name, or raises if not found.
40
+ #
41
+ # @param name [String, Symbol]
42
+ # @param with_ancestors [Boolean]
43
+ # @return [Cattri::Attribute]
44
+ # @raise [Cattri::AttributeError] if the attribute is not defined
45
+ def fetch_attribute!: (identifier name, ?with_ancestors: bool) -> Attribute
46
+
47
+ # Defines a new attribute and registers it on the current context.
48
+ #
49
+ # @param name [String, Symbol] the attribute name
50
+ # @param value [Object, Proc, nil] default value or initializer
51
+ # @param options [Hash] attribute options (`:class`, `:final`, etc.)
52
+ # @yield [*args] optional transformation block used as setter
53
+ # @return [Array<Symbol>] list of methods defined by this attribute
54
+ # @raise [Cattri::AttributeError] if the name is already defined
55
+ def define_attribute: (
56
+ identifier name,
57
+ Proc | untyped value,
58
+ ?ivar: identifier,
59
+ ?final: bool,
60
+ ?scope: scope_types,
61
+ ?predicate: bool,
62
+ ?default: ::Proc | untyped | nil,
63
+ ?expose: expose_types,
64
+ ?visibility: visibility_types
65
+ ) { (?) -> untyped } -> ::Array[::Symbol]
66
+
67
+ # Copies registered attributes from this context to another,
68
+ # preserving definitions and assigning values for `final: true, class: true`.
69
+ #
70
+ # @param target_context [Cattri::Context]
71
+ # @return [void]
72
+ def copy_attributes_to: (Context target_context) -> void
73
+
74
+ private
75
+
76
+ # Validates that no attribute with the same name is already registered.
77
+ #
78
+ # @param name [Symbol]
79
+ # @raise [Cattri::AttributeError]
80
+ def validate_unique!: (::Symbol name) -> void
81
+
82
+ # Registers an attribute and applies or defers its definition.
83
+ #
84
+ # @param attribute [Cattri::Attribute]
85
+ # @return [void]
86
+ def register_attribute: (Attribute attribute) -> void
87
+
88
+ # Defers the attribute definition if in a module context.
89
+ #
90
+ # @param attribute [Cattri::Attribute]
91
+ # @return [void]
92
+ def defer_definition: (Attribute attribute) -> void
93
+
94
+ # Applies the attribute definition using the compiler.
95
+ #
96
+ # @param attribute [Cattri::Attribute]
97
+ # @return [void]
98
+ # @raise [Cattri::AttributeError]
99
+ def apply_definition!: (Attribute attribute) -> void
100
+ end
101
+ end
@@ -0,0 +1,130 @@
1
+ module Cattri
2
+ # Cattri::Context encapsulates the class or module that attributes are being defined on.
3
+ #
4
+ # It provides a safe interface for dynamically defining methods and tracking metadata,
5
+ # such as declared accessors, access visibility, and deferred attribute declarations.
6
+ #
7
+ # It handles:
8
+ # - Attribute method definitions (reader/writer/predicate)
9
+ # - Visibility enforcement
10
+ # - Target resolution (instance vs. class-level)
11
+ # - Method deduplication and tracking
12
+ #
13
+ # All method definitions occur directly on the resolved target (e.g., the class or its singleton).
14
+ class Context
15
+ @target: ::Module
16
+
17
+ @__cattri_defined_methods: ::Hash[::Symbol, ::Set[::Symbol]]
18
+
19
+ # The class or module that owns the attributes.
20
+ #
21
+ # @return [Module]
22
+ attr_reader target: ::Module
23
+
24
+ # @param target [Module, Class]
25
+ def initialize: (::Module target) -> void
26
+
27
+ # Returns a frozen copy of all attribute methods explicitly defined by this context.
28
+ #
29
+ # This does not include inherited or module-defined methods.
30
+ #
31
+ # @return [Hash{Symbol => Set<Symbol>}] map of attribute name to defined method names
32
+ def defined_methods: () -> ::Hash[::Symbol, ::Set[::Symbol]]
33
+
34
+ # Whether this target should defer method definitions (e.g., if it's a module).
35
+ #
36
+ # @return [Boolean]
37
+ def defer_definitions?: () -> bool
38
+
39
+ # Ensures the target includes Cattri::DeferredAttributes if needed.
40
+ #
41
+ # Used to prepare modules for later application of attributes when included elsewhere.
42
+ #
43
+ # @return [void]
44
+ def ensure_deferred_support!: () -> void
45
+
46
+ # All ancestors and included modules used for attribute lookup and inheritance.
47
+ #
48
+ # @return [Array<Module>]
49
+ def attribute_lookup_sources: () -> ::Array[::Module]
50
+
51
+ # Defines a method for the given attribute unless already defined locally.
52
+ #
53
+ # Respects attribute-level force overwrite and enforces visibility rules.
54
+ #
55
+ # @param attribute [Cattri::Attribute]
56
+ # @param name [Symbol, nil] optional method name override
57
+ # @yield method implementation block
58
+ # @raise [Cattri::AttributeError] if method is already defined and not forced
59
+ # @return [void]
60
+ def define_method: (Attribute attribute, ?name: identifier?) { (?) -> untyped } -> void
61
+
62
+ # Checks if the given method is already defined on the resolved target.
63
+ #
64
+ # Only checks methods directly defined on the class or singleton—not ancestors.
65
+ #
66
+ # @param attribute [Cattri::Attribute]
67
+ # @param name [Symbol, nil]
68
+ # @return [Boolean]
69
+ def method_defined?: (Attribute attribute, ?name: identifier?) -> bool
70
+
71
+ private
72
+
73
+ # Internal tracking of explicitly defined methods per attribute.
74
+ #
75
+ # @return [Hash{Symbol => Set<Symbol>}]
76
+ def __cattri_defined_methods: () -> ::Hash[::Symbol, ::Set[::Symbol]]
77
+
78
+ # Determines whether to define the method on the instance or singleton.
79
+ #
80
+ # @param attribute [Cattri::Attribute]
81
+ # @return [Module]
82
+ def target_for: (Attribute attribute) -> ::Module
83
+
84
+ # Defines the method and applies its access visibility.
85
+ #
86
+ # @param target [Module]
87
+ # @param attribute [Cattri::Attribute]
88
+ # @param name [Symbol]
89
+ # @yield method implementation
90
+ # @return [void]
91
+ def define_method!: (::Module target, Attribute attribute, ::Symbol name) { (?) -> untyped } -> void
92
+
93
+ # Applies visibility (`public`, `protected`, `private`) to a method.
94
+ #
95
+ # Skips application for `:public` (default in Ruby).
96
+ #
97
+ # @param target [Module]
98
+ # @param name [Symbol]
99
+ # @param attribute [Cattri::Attribute]
100
+ # @return [void]
101
+ def apply_visibility!: (::Module target, ::Symbol name, Attribute attribute) -> void
102
+
103
+ # Determines the effective visibility of the attribute.
104
+ #
105
+ # - If the attribute has no public writer or reader (i.e., `expose: :write` or `:none`)
106
+ # - Returns `:protected` for class-level attributes
107
+ # - Returns `:private` for instance-level attributes
108
+ # - Otherwise, returns the explicitly declared visibility (`attribute.visibility`)
109
+ #
110
+ # This ensures that internal-only attributes remain inaccessible outside their scope,
111
+ # while still being usable by subclasses if class-level.
112
+ #
113
+ # @param attribute [Cattri::Attribute]
114
+ # @return [Symbol]
115
+ def effective_visibility: (Attribute attribute, ::Symbol name) -> visibility_types
116
+
117
+ # Determines whether the given method name (accessor or writer)
118
+ # should be treated as internal-only based on the attribute's `expose` configuration.
119
+ #
120
+ # This is used when resolving method visibility (e.g., private vs protected).
121
+ #
122
+ # - Writer methods (`:attr=`) are considered internal if the attribute lacks public read access.
123
+ # - Reader methods (`:attr`) are considered internal if the attribute lacks public write access.
124
+ #
125
+ # @param attribute [Cattri::Attribute] the attribute definition
126
+ # @param name [Symbol, String] the method name being defined
127
+ # @return [Boolean] true if the method should be scoped for internal use only
128
+ def internal_method?: (Attribute attribute, Symbol name) -> bool
129
+ end
130
+ end
@@ -0,0 +1,31 @@
1
+ module Cattri
2
+ # Provides per-class or per-module access to the attribute registry and method definition context.
3
+ #
4
+ # This module is included into both the base and singleton class of any class using Cattri.
5
+ # It initializes and exposes a lazily-evaluated `attribute_registry` and `context` specific
6
+ # to the current scope, enabling safe and isolated attribute handling.
7
+ module ContextRegistry
8
+ @attribute_registry: AttributeRegistry
9
+
10
+ @context: Context
11
+
12
+ private
13
+
14
+ # Returns the attribute definition registry for this class or module.
15
+ #
16
+ # The registry is responsible for tracking all defined attributes, both class-level and
17
+ # instance-level, handling application logic, and copying across subclasses where needed.
18
+ #
19
+ # @return [Cattri::AttributeRegistry] the registry used to define and apply attributes
20
+ def attribute_registry: () -> AttributeRegistry
21
+
22
+ # Returns the method definition context for this class or module.
23
+ #
24
+ # The context wraps the current target (class or module) and provides utilities
25
+ # for defining attribute methods (readers, writers, predicates), managing visibility,
26
+ # and recording declared methods to avoid duplication.
27
+ #
28
+ # @return [Cattri::Context] the context used for method definition and visibility tracking
29
+ def context: () -> Context
30
+ end
31
+ end
@@ -0,0 +1,53 @@
1
+ module Cattri
2
+ # Provides support for defining attributes within a module that should be
3
+ # applied later to any class or module that includes or extends it.
4
+ #
5
+ # This allows DSL modules to define Cattri attributes without prematurely
6
+ # applying them to themselves, deferring application to the including/extending context.
7
+ module DeferredAttributes
8
+ @deferred_attributes: ::Hash[::Symbol, Attribute]
9
+
10
+ # Hook into the module extension lifecycle to ensure deferred attributes are
11
+ # applied when the module is included or extended.
12
+ #
13
+ # @param base [Module] the module that extended this module
14
+ # @return [void]
15
+ def self.extended: (::Module base) -> void
16
+
17
+ # Hook methods for inclusion/extension that trigger deferred application.
18
+ module Hook
19
+ # Called when a module including `DeferredAttributes` is included into another module/class.
20
+ #
21
+ # @param target [Module] the including class or module
22
+ # @return [void]
23
+ def included: (::Module target) -> void
24
+
25
+ # Called when a module including `DeferredAttributes` is extended into another module/class.
26
+ #
27
+ # @param target [Module] the extending class or module
28
+ # @return [void]
29
+ def extended: (::Module target) -> void
30
+ end
31
+
32
+ # Registers an attribute to be applied later when this module is included or extended.
33
+ #
34
+ # @param attribute [Cattri::Attribute] the attribute to defer
35
+ # @return [void]
36
+ def defer_attribute: (Attribute attribute) -> void
37
+
38
+ # Applies all deferred attributes to the target class or module.
39
+ #
40
+ # This is triggered automatically by the {Hook} on `included` or `extended`.
41
+ #
42
+ # @param target [Module] the class or module to apply the attributes to
43
+ # @return [void]
44
+ def apply_deferred_attributes: (::Module target) -> void
45
+
46
+ private
47
+
48
+ # Internal storage of deferred attributes for this module.
49
+ #
50
+ # @return [Hash{Symbol => Cattri::Attribute}]
51
+ def deferred_attributes: () -> ::Hash[::Symbol, Attribute]
52
+ end
53
+ end