orthoses 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +123 -0
- data/lib/orthoses/builder.rb +55 -0
- data/lib/orthoses/call_tracer.rb +53 -0
- data/lib/orthoses/constant.rb +47 -0
- data/lib/orthoses/content.rb +113 -0
- data/lib/orthoses/create_file_by_name.rb +44 -0
- data/lib/orthoses/delegate_class.rb +45 -0
- data/lib/orthoses/filter.rb +24 -0
- data/lib/orthoses/include_extend_prepend.rb +58 -0
- data/lib/orthoses/load_rbs.rb +89 -0
- data/lib/orthoses/object_space_all.rb +25 -0
- data/lib/orthoses/pp.rb +15 -0
- data/lib/orthoses/store.rb +16 -0
- data/lib/orthoses/tap.rb +19 -0
- data/lib/orthoses/utils.rb +166 -0
- data/lib/orthoses/version.rb +5 -0
- data/lib/orthoses/walk.rb +25 -0
- data/lib/orthoses.rb +46 -0
- data/orthoses.gemspec +36 -0
- data/sig/orthoses/_call.rbs +5 -0
- data/sig/orthoses/_middle_ware.rbs +5 -0
- data/sig/orthoses/builder/call_logable.rbs +4 -0
- data/sig/orthoses/builder.rbs +5 -0
- data/sig/orthoses/call_tracer.rbs +5 -0
- data/sig/orthoses/const_load_error.rbs +8 -0
- data/sig/orthoses/constant.rbs +7 -0
- data/sig/orthoses/content.rbs +11 -0
- data/sig/orthoses/create_file_by_name.rbs +7 -0
- data/sig/orthoses/delegate_class.rbs +4 -0
- data/sig/orthoses/filter.rbs +7 -0
- data/sig/orthoses/include_extend_prepend.rbs +4 -0
- data/sig/orthoses/load_rbs.rbs +4 -0
- data/sig/orthoses/name_space_error.rbs +4 -0
- data/sig/orthoses/object_space_all.rbs +4 -0
- data/sig/orthoses/pp.rbs +4 -0
- data/sig/orthoses/store.rbs +4 -0
- data/sig/orthoses/tap.rbs +4 -0
- data/sig/orthoses/utils.rbs +27 -0
- data/sig/orthoses/walk.rbs +8 -0
- data/sig/orthoses.rbs +9 -0
- metadata +102 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orthoses
|
4
|
+
class LoadRBS
|
5
|
+
# use Orthoses::LoadRBS,
|
6
|
+
# paths: Dir.glob("known_sig/**/*.rbs")
|
7
|
+
def initialize(loader, paths:)
|
8
|
+
@loader = loader
|
9
|
+
@paths = paths
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
@loader.call.tap do |store|
|
14
|
+
tmp_env = RBS::Environment.new
|
15
|
+
|
16
|
+
@paths.each do |path|
|
17
|
+
Orthoses.logger.debug("Load #{path}")
|
18
|
+
buffer = RBS::Buffer.new(name: path.to_s, content: File.read(path.to_s, encoding: "UTF-8"))
|
19
|
+
decls = RBS::Parser.parse_signature(buffer)
|
20
|
+
decls.each { |decl| tmp_env << decl }
|
21
|
+
end
|
22
|
+
|
23
|
+
tmp_env.class_decls.each do |type_name, m_entry|
|
24
|
+
name = type_name.relative!.to_s
|
25
|
+
content = store[name]
|
26
|
+
case decl = m_entry.decls.first.decl
|
27
|
+
when RBS::AST::Declarations::Module
|
28
|
+
self_types = decl.self_types.empty? ? nil : " : #{decl.self_types.join(', ')}"
|
29
|
+
content.header = "module #{name_and_params(name, decl.type_params)}#{self_types}"
|
30
|
+
when RBS::AST::Declarations::Class
|
31
|
+
super_class = decl.super_class.nil? ? nil : " < #{name_and_args(decl.super_class.name, decl.super_class.args)}"
|
32
|
+
content.header = "class #{name_and_params(name, decl.type_params)}#{super_class}"
|
33
|
+
end
|
34
|
+
decls_to_lines(m_entry.decls.map(&:decl)).each do |line|
|
35
|
+
content << line
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
tmp_env.interface_decls.each do |type_name, s_entry|
|
40
|
+
name = type_name.relative!.to_s
|
41
|
+
content = store[name]
|
42
|
+
decl = s_entry.decl
|
43
|
+
content.header = "interface #{name_and_params(name, decl.type_params)}"
|
44
|
+
decls_to_lines([decl]).each do |line|
|
45
|
+
content << line
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def name_and_params(name, params)
|
54
|
+
if params.empty?
|
55
|
+
"#{name}"
|
56
|
+
else
|
57
|
+
ps = params.each.map do |param|
|
58
|
+
param.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
"#{name}[#{ps.join(", ")}]"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def name_and_args(name, args)
|
66
|
+
if name && args
|
67
|
+
if args.empty?
|
68
|
+
"#{name}"
|
69
|
+
else
|
70
|
+
"#{name}[#{args.join(", ")}]"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def decls_to_lines(decls)
|
76
|
+
out = StringIO.new
|
77
|
+
writer = RBS::Writer.new(out: out)
|
78
|
+
decls.each do |decl|
|
79
|
+
if decl.respond_to?(:members)
|
80
|
+
decl.members.each do |member|
|
81
|
+
next if member.respond_to?(:members)
|
82
|
+
writer.write_member(member)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
out.string.each_line(chomp: true).to_a
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orthoses
|
4
|
+
class ObjectSpaceAll
|
5
|
+
def initialize(loader, if: nil)
|
6
|
+
@loader = loader
|
7
|
+
@if = binding.local_variable_get(:if)
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
store = @loader.call
|
12
|
+
|
13
|
+
after_modules = ObjectSpace.each_object(Module).to_a
|
14
|
+
after_modules.each do |mod|
|
15
|
+
mod_name = Utils.module_name(mod)
|
16
|
+
next if mod_name.nil?
|
17
|
+
next unless @if.nil? || @if.call(mod)
|
18
|
+
|
19
|
+
store[mod_name]
|
20
|
+
end
|
21
|
+
|
22
|
+
store
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/orthoses/pp.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orthoses
|
4
|
+
# Internal middleware for return store object
|
5
|
+
# Builder set this middleware on last stack by default
|
6
|
+
class Store
|
7
|
+
def initialize(loader)
|
8
|
+
@loader = loader
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
@loader.call
|
13
|
+
Utils.new_store
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/orthoses/tap.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orthoses
|
4
|
+
# use Orthoses::Tap do |store|
|
5
|
+
# store["Foo::Bar"] << "def baz: () -> void"
|
6
|
+
# end
|
7
|
+
class Tap
|
8
|
+
def initialize(loader, &block)
|
9
|
+
@loader = loader
|
10
|
+
@block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
@loader.call.tap do |store|
|
15
|
+
@block.call(store)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orthoses
|
4
|
+
module Utils
|
5
|
+
def self.unautoload!
|
6
|
+
ObjectSpace.each_object(Module) do |mod|
|
7
|
+
each_const_recursive(mod)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.each_const_recursive(root, cache: {}, on_error: nil, &block)
|
12
|
+
root.constants(false).each do |const|
|
13
|
+
val = root.const_get(const)
|
14
|
+
next if cache[const].equal?(val)
|
15
|
+
cache[const] = val
|
16
|
+
next if val.equal?(root)
|
17
|
+
block.call(root, const, val) if block
|
18
|
+
if val.respond_to?(:constants)
|
19
|
+
each_const_recursive(val, cache: cache, on_error: on_error, &block)
|
20
|
+
end
|
21
|
+
rescue LoadError, StandardError => err
|
22
|
+
Orthoses.logger.warn("Orthoses::Utils.each_const_recursive: #{err.class}: #{err.message} on #{err.backtrace.first}")
|
23
|
+
if on_error
|
24
|
+
on_error.call(ConstLoadError.new(root: root, const: const, error: err))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.rbs_defined_const?(name, library: nil, collection: false)
|
30
|
+
return false if name.start_with?("#<")
|
31
|
+
env = rbs_environment(library: library, collection: collection)
|
32
|
+
name = name.sub(/Object::/, '')
|
33
|
+
target = rbs_type_name(name)
|
34
|
+
env.constant_decls.has_key?(target)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.rbs_defined_class?(name, library: nil, collection: false)
|
38
|
+
return false if name.start_with?("#<")
|
39
|
+
env = rbs_environment(library: library, collection: collection)
|
40
|
+
target = rbs_type_name(name)
|
41
|
+
env.class_decls.has_key?(target)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.rbs_type_name(name)
|
45
|
+
name = "::#{name}" if !name.start_with?("::")
|
46
|
+
RBS::Namespace.parse(name).to_type_name
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.rbs_environment(library: nil, collection: false)
|
50
|
+
@env_cache ||= {}
|
51
|
+
if hit = @env_cache[[library, collection]]
|
52
|
+
return hit
|
53
|
+
end
|
54
|
+
|
55
|
+
loader = RBS::EnvironmentLoader.new
|
56
|
+
|
57
|
+
if collection
|
58
|
+
lock = RBS::Collection::Config::PATH&.then { |p| RBS::Collection::Config.lockfile_of(p) }
|
59
|
+
loader.add_collection(lock) if lock
|
60
|
+
end
|
61
|
+
|
62
|
+
case library
|
63
|
+
when "stdlib"
|
64
|
+
RBS::Repository::DEFAULT_STDLIB_ROOT.each_entry do |path|
|
65
|
+
lib = path.to_s
|
66
|
+
loader.add(library: lib.to_s) unless lib == "." || lib == ".."
|
67
|
+
end
|
68
|
+
else
|
69
|
+
Array(library).each do |lib|
|
70
|
+
loader.add(library: lib.to_s)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
environment = RBS::Environment.from_loader(loader).resolve_type_names
|
75
|
+
@env_cache[[library, collection]] = environment
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.object_to_rbs(object, strict:)
|
79
|
+
case object
|
80
|
+
when Class, Module
|
81
|
+
"singleton(#{object})"
|
82
|
+
when Integer, Symbol, String
|
83
|
+
if strict
|
84
|
+
object.inspect
|
85
|
+
else
|
86
|
+
Utils.module_name(object.class) || 'untyped'
|
87
|
+
end
|
88
|
+
when true, false, nil
|
89
|
+
object.inspect
|
90
|
+
when Set
|
91
|
+
if object.empty?
|
92
|
+
"Set[untyped]"
|
93
|
+
else
|
94
|
+
ary = object.map do |o|
|
95
|
+
object_to_rbs(o, strict: strict)
|
96
|
+
end
|
97
|
+
"Set[#{ary.uniq.join(' | ')}]"
|
98
|
+
end
|
99
|
+
when Array
|
100
|
+
if object.empty?
|
101
|
+
"Array[untyped]"
|
102
|
+
else
|
103
|
+
ary = object.map do |o|
|
104
|
+
object_to_rbs(o, strict: strict)
|
105
|
+
end
|
106
|
+
if strict
|
107
|
+
"[#{ary.join(', ')}]"
|
108
|
+
else
|
109
|
+
"Array[#{ary.uniq.join(' | ')}]"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
when Hash
|
113
|
+
if object.empty?
|
114
|
+
"Hash[untyped, untyped]"
|
115
|
+
else
|
116
|
+
if strict && object.keys.all? { |key| key.is_a?(Symbol) && /\A\w+\z/.match?(key) }
|
117
|
+
"{ #{object.map { |k, v| "#{k}: #{object_to_rbs(v, strict: strict)}" }.join(', ')} }"
|
118
|
+
else
|
119
|
+
keys = object.keys.map { |k| object_to_rbs(k, strict: strict) }.uniq
|
120
|
+
values = object.values.map { |k| object_to_rbs(k, strict: strict) }.uniq
|
121
|
+
"Hash[#{keys.join(' | ')}, #{values.join(' | ')}]"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
else
|
125
|
+
Utils.module_name(object.class) || 'untyped'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
UNBOUND_NAME_METHOD = Module.instance_method(:name)
|
130
|
+
|
131
|
+
# Want to get the module name even if the method is overwritten.
|
132
|
+
# e.g.) Rails::Info
|
133
|
+
def self.module_name(mod)
|
134
|
+
return nil unless mod
|
135
|
+
UNBOUND_NAME_METHOD.bind(mod).call
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.module_to_type_name(mod)
|
139
|
+
name = Utils.module_name(mod)
|
140
|
+
if name && !name.empty?
|
141
|
+
TypeName(name)
|
142
|
+
else
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.known_type_params(name)
|
148
|
+
type_name =
|
149
|
+
case name
|
150
|
+
when String
|
151
|
+
TypeName(name).absolute!
|
152
|
+
when Module
|
153
|
+
module_to_type_name(name)
|
154
|
+
else
|
155
|
+
raise TypeError
|
156
|
+
end
|
157
|
+
rbs_environment(collection: true).class_decls[type_name]&.then do |entry|
|
158
|
+
entry.decls.first.decl.type_params
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.new_store
|
163
|
+
Hash.new { |h, k| h[k] = Content.new(name: k) }
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Orthoses
|
4
|
+
class Walk
|
5
|
+
def initialize(loader, root:)
|
6
|
+
@loader = loader
|
7
|
+
@root = root
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
@loader.call.tap do |store|
|
12
|
+
root = Object.const_get(@root) if @root.instance_of?(String)
|
13
|
+
Utils.module_name(root)&.then { |root_name| store[root_name] }
|
14
|
+
Orthoses::Utils.each_const_recursive(root) do |current, const, val|
|
15
|
+
if val.kind_of?(Module)
|
16
|
+
Utils.module_name(val)&.then do |val_name|
|
17
|
+
Orthoses.logger.debug("Add [#{val_name}] on #{__FILE__}:#{__LINE__}")
|
18
|
+
store[val_name]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/orthoses.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbs'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
require_relative 'orthoses/builder'
|
7
|
+
require_relative 'orthoses/call_tracer'
|
8
|
+
require_relative 'orthoses/constant'
|
9
|
+
require_relative 'orthoses/content'
|
10
|
+
require_relative 'orthoses/create_file_by_name'
|
11
|
+
require_relative 'orthoses/delegate_class'
|
12
|
+
require_relative 'orthoses/filter'
|
13
|
+
require_relative 'orthoses/include_extend_prepend'
|
14
|
+
require_relative 'orthoses/load_rbs'
|
15
|
+
require_relative 'orthoses/object_space_all'
|
16
|
+
require_relative 'orthoses/pp'
|
17
|
+
require_relative 'orthoses/store'
|
18
|
+
require_relative 'orthoses/tap'
|
19
|
+
require_relative 'orthoses/utils'
|
20
|
+
require_relative 'orthoses/version'
|
21
|
+
require_relative 'orthoses/walk'
|
22
|
+
|
23
|
+
module Orthoses
|
24
|
+
class ConstLoadError < StandardError
|
25
|
+
attr_reader :root
|
26
|
+
attr_reader :const
|
27
|
+
attr_reader :error
|
28
|
+
def initialize(root:, const:, error:)
|
29
|
+
@root = root
|
30
|
+
@const = const
|
31
|
+
@error = error
|
32
|
+
end
|
33
|
+
|
34
|
+
def message
|
35
|
+
"root=#{root}, const=#{const}, error=#{error.inspect}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class NameSpaceError < StandardError
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
attr_accessor :logger
|
44
|
+
end
|
45
|
+
self.logger = ::Logger.new($stdout, level: :info)
|
46
|
+
end
|
data/orthoses.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/orthoses/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "orthoses"
|
7
|
+
spec.version = Orthoses::VERSION
|
8
|
+
spec.authors = ["ksss"]
|
9
|
+
spec.email = ["co000ri@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Framework for Generate RBS"
|
12
|
+
spec.description = "Build RBS by Rack base architecture"
|
13
|
+
spec.homepage = "https://github.com/ksss/orthoses"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 2.6.0"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
|
20
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
21
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
22
|
+
next true if (f == __FILE__)
|
23
|
+
next true if f.match?(%r{\A(?:bin|known_sig)/}) # dir
|
24
|
+
next true if f.match?(%r{\A\.(?:git)}) # git
|
25
|
+
next true if f.match?(%r{\A(?:rbs_collection|Steepfile|Rakefile)}) # top file
|
26
|
+
next true if f.match?(%r{_test\.rb\z}) # test
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
spec.add_dependency "rbs", "~> 2.0"
|
36
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# THIS IS GENERATED CODE from `$ rake generate_self_sig`
|
2
|
+
|
3
|
+
class Orthoses::ConstLoadError < StandardError
|
4
|
+
attr_reader root: Module
|
5
|
+
attr_reader const: Symbol
|
6
|
+
attr_reader error: untyped
|
7
|
+
def initialize: (root: Module, const: Symbol, error: untyped) -> void
|
8
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# THIS IS GENERATED CODE from `$ rake generate_self_sig`
|
2
|
+
|
3
|
+
class Orthoses::Constant
|
4
|
+
@loader: Orthoses::_Call
|
5
|
+
include Orthoses::_MiddleWare
|
6
|
+
def initialize: (Orthoses::_Call loader, ?if: ^(Module, Symbol, untyped) -> boolish, ?on_error: ^(Orthoses::ConstLoadError) -> void) -> void
|
7
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# THIS IS GENERATED CODE from `$ rake generate_self_sig`
|
2
|
+
|
3
|
+
class Orthoses::Content
|
4
|
+
attr_reader name: String
|
5
|
+
attr_reader body: Array[String]
|
6
|
+
attr_accessor header: String?
|
7
|
+
def initialize: (name: String) -> void
|
8
|
+
def <<: (String) -> void
|
9
|
+
def concat: (Array[String]) -> void
|
10
|
+
def to_rbs: () -> String
|
11
|
+
end
|
data/sig/orthoses/pp.rbs
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# THIS IS GENERATED CODE from `$ rake generate_self_sig`
|
2
|
+
|
3
|
+
module Orthoses::Utils
|
4
|
+
UNBOUND_NAME_METHOD: UnboundMethod
|
5
|
+
|
6
|
+
def self.unautoload!: () -> void
|
7
|
+
|
8
|
+
def self.each_const_recursive: (Module root, ?cache: Hash[untyped, true], ?on_error: ^(Orthoses::ConstLoadError) -> void?) ?{ (Module, Symbol, untyped) -> void } -> void
|
9
|
+
|
10
|
+
def self.rbs_defined_const?: (String name, ?library: (String | Array[String])?, ?collection: boolish) -> bool
|
11
|
+
|
12
|
+
def self.rbs_defined_class?: (String name, ?library: (String | Array[String])?, ?collection: boolish) -> bool
|
13
|
+
|
14
|
+
def self.rbs_type_name: (String) -> RBS::TypeName
|
15
|
+
|
16
|
+
def self.rbs_environment: (?library: String | Array[String] | nil, ?collection: boolish) -> RBS::Environment
|
17
|
+
|
18
|
+
def self.object_to_rbs: (untyped object, strict: bool) -> String
|
19
|
+
|
20
|
+
def self.module_name: (Module mod) -> String?
|
21
|
+
|
22
|
+
def self.module_to_type_name: (Module) -> RBS::TypeName?
|
23
|
+
|
24
|
+
def self.known_type_params: (Module | String) -> Array[RBS::AST::TypeParam]
|
25
|
+
|
26
|
+
def self.new_store: () -> Orthoses::store
|
27
|
+
end
|