sorbet-eraser 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54913e2de72273efae7221b3dc96923c1b45e7d17020ec47bd9a417bea9a62cc
4
- data.tar.gz: 71f3cc0a28acefd20d9a4a1f3a06081a568c5b8276cb1241f34d2bb3e7f64fb8
3
+ metadata.gz: 5ddbc396f3f164a3d0e67e9cf7046733ce52f792eb0abc17e9f5f056f415cd5e
4
+ data.tar.gz: 965ef9955b75bba0a2702ea127d5923d7e4b1a55ed2b2e6dca239970adbb0fe1
5
5
  SHA512:
6
- metadata.gz: 06ca1f48f68eea0d10933f83ffe8ba4b296cda3c87240a5db6704ac27d9d6a12202a46b1af82ee3983ba335d4254f230a8f3d861578556ee76ea3544c551cd70
7
- data.tar.gz: 547b88a3fb93e133c69cdd2d05ce684f62cc1650afe836fe9741059d1f567f4704bf11815b3170d8f4df2a9909fcc138fc6d940b45ef5ff615853f93118a2f93
6
+ metadata.gz: 5deb006431e8e31ed089fe6e8fba281f3c65487fc6996a853b05bb0d9600ff034232e7319fb44912a69dc89b60b160900b9b503a17991d19da2bf02f5803c5f8
7
+ data.tar.gz: a6a20f8aa21b2a2c9e75a4c7e83db5b28556516296d16f55eedd0113da807cbf0c3a827fbeda7eaf9fb95d2a29a9f0c4ddc9090d638e00469989520d2c492f1d
@@ -12,7 +12,7 @@ jobs:
12
12
  - uses: actions/checkout@master
13
13
  - uses: ruby/setup-ruby@v1
14
14
  with:
15
- ruby-version: 3.0
15
+ ruby-version: '3.1'
16
16
  bundler-cache: true
17
17
  - name: Test
18
18
  run: bundle exec rake test
data/CHANGELOG.md CHANGED
@@ -6,11 +6,25 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.0] - 2023-06-27
10
+
11
+ ### Added
12
+
13
+ - Support for the `default` and `without_accessors` options for `T::Struct`.
14
+
15
+ ## [0.2.0] - 2023-06-26
16
+
17
+ ### Added
18
+
19
+ - Better support for `T::Struct` subclasses.
20
+
9
21
  ## [0.1.1] - 2021-11-17
10
22
 
11
23
  ### Changed
12
24
 
13
25
  - Require MFA for releasing.
14
26
 
15
- [unreleased]: https://github.com/kddnewton/sorbet-eraser/compare/v0.1.1...HEAD
27
+ [unreleased]: https://github.com/kddnewton/sorbet-eraser/compare/v0.3.0...HEAD
28
+ [0.3.0]: https://github.com/kddnewton/sorbet-eraser/compare/v0.2.0...v0.3.0
29
+ [0.2.0]: https://github.com/kddnewton/sorbet-eraser/compare/v0.1.1...v0.2.0
16
30
  [0.1.1]: https://github.com/kddnewton/sorbet-eraser/compare/f6a712...v0.1.1
data/Gemfile.lock CHANGED
@@ -1,16 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sorbet-eraser (0.1.1)
4
+ sorbet-eraser (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- minitest (5.14.4)
9
+ minitest (5.18.1)
10
10
  rake (13.0.6)
11
11
 
12
12
  PLATFORMS
13
+ arm64-darwin-22
13
14
  x86_64-darwin-19
15
+ x86_64-darwin-21
16
+ x86_64-linux
14
17
 
15
18
  DEPENDENCIES
16
19
  bundler
data/README.md CHANGED
@@ -27,17 +27,19 @@ Or install it yourself as:
27
27
 
28
28
  ## Usage
29
29
 
30
- Before any code is loaded that would require a `sorbet-runtime` construct, call `require "sorbet/eraser/autoload"`. This will hook into the autoload process to erase all `sorbet-runtime` code before it gets passed to Ruby to parse.
30
+ There are two ways to use this gem, depending on your needs.
31
31
 
32
- Alternatively, you can programmatically use this gem through the `Sorbet::Eraser.erase(source)` API, where `source` is a string that represents valid Ruby code. Ruby code without the listed constructs will be returned.
32
+ The first is that you can hook into the Ruby compilation process and do just-in-time erasure. To do this before any code is loaded that would require a `sorbet-runtime` construct call `require "sorbet/eraser/autoload"`. This will hook into the autoload process to erase all `sorbet-runtime` code before it gets passed to Ruby to parse. This eliminates the need for a build step, but slows down your parse/boot time.
33
33
 
34
- Finally, this gem ships with a CLI that you can use to modify source files. This is useful for development of this gem itself, but could be useful for others to ensure they see what this gem actually will be doing in production. To run it, run:
34
+ The second is that you can preprocess your Ruby files using either the CLI or the Ruby API. With the CLI, you would run:
35
35
 
36
- ```sh
36
+ ```bash
37
37
  bundle exec sorbet-eraser '**/*.rb'
38
38
  ```
39
39
 
40
- It accepts any number of filepaths/patterns on the command line and will modify the source files with their erased contents.
40
+ It accepts any number of filepaths/patterns on the command line and will modify the source files with their erased contents. Alternatively, you can programmatically use this gem through the `Sorbet::Eraser.erase(source)` API, where `source` is a string that represents valid Ruby code. Ruby code without the listed constructs will be returned.
41
+
42
+ If you used any runtime structures like `T::Struct` or `T::Enum` you'll need a runtime shim. For that, you can add `require "t"` to your codebase, which ships with this gem, or in your gemfile `gem "sorbet-eraser", require: "t"`.
41
43
 
42
44
  ### Status
43
45
 
@@ -50,7 +52,12 @@ Below is a table of the status of each `sorbet-runtime` construct and its curren
50
52
  | `mixes_in_class_methods(*)`, `requires_ancestor(*)` | ✅ | Shimmed |
51
53
  | `type_member(*)`, `type_template(*)` | ✅ | Shimmed |
52
54
  | `class Foo < T::Enum` | ✅ | Shimmed |
55
+ | `class Foo < T::InexactStruct` | 🛠 | Shimmed |
53
56
  | `class Foo < T::Struct` | 🛠 | Shimmed |
57
+ | `class Foo < T::ImmutableStruct` | 🛠 | Shimmed |
58
+ | `include T::Props` | 🛠 | Shimmed |
59
+ | `include T::Props::Serializable` | 🛠 | Shimmed |
60
+ | `include T::Props::Constructor` | 🛠 | Shimmed |
54
61
  | `sig` | ✅ | Removed |
55
62
  | `T.absurd(foo)` | ✅ | Shimmed |
56
63
  | `T.assert_type!(foo, bar)` | ✅ | `foo` |
@@ -62,7 +69,12 @@ Below is a table of the status of each `sorbet-runtime` construct and its curren
62
69
  | `T.type_alias { foo }` | ✅ | Shimmed |
63
70
  | `T.unsafe(foo)` | ✅ | `foo` |
64
71
 
65
- In the above table:
72
+ In the above table, for `Status`:
73
+
74
+ * ✅ means that we are confident this is replaced 1:1.
75
+ * 🛠 means there may be APIs that are not entirely supported. If you run into something that is missing, please open an issue.
76
+
77
+ In the above table, for `Replacement`:
66
78
 
67
79
  * `Shimmed` means that this gem provides a replacement module that will simply do nothing when its respective methods are called. We do this in order to maintain the same interface in the case that someone is doing runtime reflection. Also because anything that is shimmed will not be called that much/will not be in a hot path so performance is not really a consideration for those cases.
68
80
  * `Removed` means that the construct is removed entirely from the source.
@@ -59,7 +59,10 @@ module Sorbet
59
59
  end
60
60
 
61
61
  def to_s
62
- @to_s ||= "<#{event} #{body.map(&:to_s).join(" ")}>"
62
+ @repr ||= begin
63
+ children = body.map { |child| child.is_a?(Array) ? child.map(&:to_s) : child }
64
+ "<#{event} #{children.join(" ")}>"
65
+ end
63
66
  end
64
67
  end
65
68
 
@@ -108,25 +111,83 @@ module Sorbet
108
111
  line_counts[lineno - 1][column]
109
112
  end
110
113
 
114
+ def find_loc(args)
115
+ ranges = []
116
+
117
+ args.each do |arg|
118
+ case arg
119
+ when Node
120
+ ranges << arg.range if arg.range
121
+ when Array
122
+ ranges << find_loc(arg)
123
+ end
124
+ end
125
+
126
+ case ranges.length
127
+ when 0
128
+ nil
129
+ when 1
130
+ ranges.first
131
+ else
132
+ ranges.first.begin...ranges.last.end
133
+ end
134
+ end
135
+
111
136
  # Loop through all of the scanner events and define a basic method that
112
137
  # wraps everything into a node class.
113
138
  SCANNER_EVENTS.each do |event|
114
139
  define_method(:"on_#{event}") do |value|
115
- range = loc.then { |start| start..(start + (value&.size || 0)) }
140
+ range = loc.then { |start| start...(start + (value&.size || 0)) }
116
141
  Node.new(:"@#{event}", [value], range)
117
142
  end
118
143
  end
119
144
 
145
+ # Better location information for aref.
146
+ def on_aref(recv, arg)
147
+ rend = arg.range.end + source[arg.range.end..].index("]") + 1
148
+ Node.new(:aref, [recv, arg], recv.range.begin...rend)
149
+ end
150
+
151
+ # Better location information for arg_paren.
152
+ def on_arg_paren(arg)
153
+ rbegin = source[..arg.range.begin].rindex("(")
154
+ rend = arg.range.end + source[arg.range.end..].index(")") + 1
155
+ Node.new(:arg_paren, [arg], rbegin...rend)
156
+ end
157
+
158
+ # Better location information for brace_block.
159
+ def on_brace_block(params, body)
160
+ rbegin = source[...(params || body).range.begin].rindex("{")
161
+ rend = body.range.end + source[body.range.end..].index("}") + 1
162
+ Node.new(:brace_block, [params, body], rbegin...rend)
163
+ end
164
+
165
+ # Better location information for do_block.
166
+ def on_do_block(params, body)
167
+ rbegin = source[...(params || body).range.begin].rindex("do")
168
+ rend = body.range.end + source[body.range.end..].index("end") + 3
169
+ Node.new(:do_block, [params, body], rbegin...rend)
170
+ end
171
+
172
+ # Track the parsing errors for nicer error messages.
173
+ def on_parse_error(error)
174
+ errors << "line #{lineno}: #{error}"
175
+ end
176
+
120
177
  # Loop through the parser events and generate a method for each event. If
121
178
  # it's one of the _new methods, then use arrays like SexpBuilderPP. If
122
179
  # it's an _add method then just append to the array. If it's a normal
123
180
  # method, then create a new node and determine its bounds.
181
+ handled = private_instance_methods(false)
182
+
124
183
  PARSER_EVENT_TABLE.each do |event, arity|
184
+ next if handled.include?(:"on_#{event}")
185
+
125
186
  if event =~ /\A(.+)_new\z/ && event != :assoc_new
126
187
  prefix = $1.to_sym
127
188
 
128
189
  define_method(:"on_#{event}") do
129
- Node.new(prefix, [], loc.then { |start| start..start })
190
+ Node.new(prefix, [], nil)
130
191
  end
131
192
  elsif event =~ /_add\z/
132
193
  define_method(:"on_#{event}") do |node, value|
@@ -134,29 +195,17 @@ module Sorbet
134
195
  if node.body.empty?
135
196
  value.range
136
197
  else
137
- (node.range.begin..value.range.end)
198
+ (node.range.begin...value.range.end)
138
199
  end
139
200
 
140
201
  node.class.new(node.event, node.body + [value], range)
141
202
  end
142
- elsif event == :parse_error
143
- # skip this, as we're going to define it below
144
203
  else
145
204
  define_method(:"on_#{event}") do |*args|
146
- first, *, last = args.grep(Node).map(&:range)
147
-
148
- first ||= loc.then { |start| start..start }
149
- last ||= first
150
-
151
- Node.new(event, args, first.begin..[last.end, loc].max)
205
+ Node.new(event, args, find_loc(args))
152
206
  end
153
207
  end
154
208
  end
155
-
156
- # Track the parsing errors for nicer error messages.
157
- def on_parse_error(error)
158
- errors << "line #{lineno}: #{error}"
159
- end
160
209
  end
161
210
  end
162
211
  end
@@ -89,75 +89,104 @@ module Sorbet
89
89
  end
90
90
  end
91
91
 
92
+ # T.must foo => foo
93
+ # T.reveal_type foo => foo
94
+ # T.unsafe foo => foo
95
+ class TMustNoParensPattern < Pattern
96
+ def replace(segment)
97
+ segment.gsub(/(T\s*\.(?:must|reveal_type|unsafe)\s*)(.+)/) do
98
+ "#{blank($1)}#{$2}"
99
+ end
100
+ end
101
+ end
102
+
92
103
  def on_method_add_arg(call, arg_paren)
93
- # T.must(foo)
94
- # T.reveal_type(foo)
95
- # T.unsafe(foo)
96
- if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident (?:must|reveal_type|unsafe)>>/) &&
97
- arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
104
+ if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident (?:must|reveal_type|unsafe)>>/) && arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
105
+ # T.must(foo)
106
+ # T.reveal_type(foo)
107
+ # T.unsafe(foo)
98
108
  patterns << TOneArgMethodCallParensPattern.new(call.range.begin...arg_paren.range.end)
109
+ elsif call.match?(/\A<call <var_ref <@const T>> <@period \.> <@ident (?:assert_type!|cast|let)>>\z/) && arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
110
+ # T.assert_type!(foo, bar)
111
+ # T.cast(foo, bar)
112
+ # T.let(foo, bar)
113
+ patterns << TTwoArgMethodCallParensPattern.new(
114
+ call.range.begin...arg_paren.range.end,
115
+ comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
116
+ )
117
+ elsif call.match?(/<call <var_ref <@const T>> <@period \.> <@ident bind>>/) && arg_paren.match?(/<arg_paren <args_add_block <args <var_ref <@kw self>> .+> false>>/)
118
+ # T.bind(self, foo)
119
+ patterns << TTwoArgMethodCallParensPattern.new(
120
+ call.range.begin...arg_paren.range.end,
121
+ comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
122
+ )
123
+ elsif call.match?(/<fcall <@ident (?:abstract|final|interface)!>>/) && arg_paren.match?("<args >")
124
+ # abstract!
125
+ # final!
126
+ # interface!
127
+ patterns << DeclarationPattern.new(call.range.begin...arg_paren.range.end)
128
+ elsif call.match?("<fcall <@ident mixes_in_class_methods>>") && arg_paren.match?(/<arg_paren <args_add_block <args <.+>>> false>>/)
129
+ # mixes_in_class_methods(foo)
130
+ patterns << MixesInClassMethodsPattern.new(call.range.begin...arg_paren.range.end)
99
131
  end
100
132
 
101
- # T.assert_type!(foo, bar)
102
- # T.cast(foo, bar)
103
- # T.let(foo, bar)
104
- if call.match?(/\A<call <var_ref <@const T>> <@period \.> <@ident (?:assert_type!|cast|let)>>\z/) &&
105
- arg_paren.match?(/<arg_paren <args_add_block <args .+> false>>/)
106
- patterns <<
107
- TTwoArgMethodCallParensPattern.new(
108
- call.range.begin...arg_paren.range.end,
109
- comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
110
- )
111
- end
133
+ super
134
+ end
112
135
 
113
- # T.bind(self, foo)
114
- if call.match?(/<call <var_ref <@const T>> <@period \.> <@ident bind>>/) &&
115
- arg_paren.match?(/<arg_paren <args_add_block <args <var_ref <@kw self>> .+> false>>/)
116
- patterns <<
117
- TTwoArgMethodCallParensPattern.new(
118
- call.range.begin...arg_paren.range.end,
119
- comma: arg_paren.body[0].body[0].body[0].range.end - call.range.begin
120
- )
136
+ # prop :foo, String => prop :foo
137
+ # const :foo, String => const :foo
138
+ class PropWithoutOptionsPattern < Pattern
139
+ def replace(segment)
140
+ segment.dup.tap do |replacement|
141
+ range = metadata.fetch(:comma)..-1
142
+ replacement[range] = blank(replacement[range])
143
+ end
121
144
  end
145
+ end
122
146
 
123
- # abstract!
124
- # final!
125
- # interface!
126
- if call.match?(/<fcall <@ident (?:abstract|final|interface)!>>/) &&
127
- arg_paren.match?("<args >")
128
- patterns << DeclarationPattern.new(call.range.begin...arg_paren.range.end)
129
- end
147
+ # prop :foo, String, default: "" => prop :foo, default: ""
148
+ # const :foo, String, default: "" => const :foo, default: ""
149
+ class PropWithOptionsPattern < Pattern
150
+ def replace(segment)
151
+ segment.dup.tap do |replacement|
152
+ first_comma = metadata.fetch(:first_comma)
153
+ second_comma = metadata.fetch(:second_comma)
130
154
 
131
- # mixes_in_class_methods(foo)
132
- if call.match?("<fcall <@ident mixes_in_class_methods>>") &&
133
- arg_paren.match?(/<arg_paren <args_add_block <args <.+>>> false>>/)
134
- patterns << MixesInClassMethodsPattern.new(call.range.begin...arg_paren.range.end)
155
+ range = (first_comma + 1)..second_comma
156
+ replacement[range] = blank(replacement[range])
157
+ end
135
158
  end
136
-
137
- super
138
159
  end
139
160
 
140
- # T.must foo => foo
141
- # T.reveal_type foo => foo
142
- # T.unsafe foo => foo
143
- class TMustNoParensPattern < Pattern
144
- def replace(segment)
145
- segment.gsub(/(T\s*\.(?:must|reveal_type|unsafe)\s*)(.+)/) do
146
- "#{blank($1)}#{$2}"
161
+ def on_command(ident, args_add_block)
162
+ if ident.match?(/<@ident (?:const|prop)>/)
163
+ if args_add_block.match?(/<args_add_block <args <symbol_literal <symbol <@ident .+?>>> <.+> <bare_assoc_hash .+> false>/)
164
+ # prop :foo, String, default: ""
165
+ # const :foo, String, default: ""
166
+ patterns << PropWithOptionsPattern.new(
167
+ ident.range.begin..args_add_block.range.end,
168
+ first_comma: args_add_block.body[0].body[0].range.end - ident.range.begin,
169
+ second_comma: args_add_block.body[0].body[1].range.end - ident.range.begin
170
+ )
171
+ elsif args_add_block.match?(/<args_add_block <args <symbol_literal <symbol <@ident .+?>>> <.+> false>/)
172
+ # prop :foo, String
173
+ # const :foo, String
174
+ patterns << PropWithoutOptionsPattern.new(
175
+ ident.range.begin..args_add_block.range.end,
176
+ comma: args_add_block.body[0].body[0].range.end - ident.range.begin
177
+ )
147
178
  end
148
179
  end
180
+
181
+ super
149
182
  end
150
183
 
151
184
  def on_command_call(var_ref, period, ident, args_add_block)
152
- if var_ref.match?("<var_ref <@const T>>") && period.match?("<@period .>")
185
+ if var_ref.match?("<var_ref <@const T>>") && period.match?("<@period .>") && ident.match?(/<@ident (?:must|reveal_type|unsafe)>/) && args_add_block.match?(/<args_add_block <args <.+>> false>/) && args_add_block.body[0].body.length == 1
153
186
  # T.must foo
154
187
  # T.reveal_type foo
155
188
  # T.unsafe foo
156
- if ident.match?(/<@ident (?:must|reveal_type|unsafe)>/) &&
157
- args_add_block.match?(/<args_add_block <args <.+>> false>/) &&
158
- args_add_block.body[0].body.length == 1
159
- patterns << TMustNoParensPattern.new(var_ref.range.begin...args_add_block.range.end)
160
- end
189
+ patterns << TMustNoParensPattern.new(var_ref.range.begin..args_add_block.range.end)
161
190
  end
162
191
 
163
192
  super
@@ -172,10 +201,22 @@ module Sorbet
172
201
  end
173
202
  end
174
203
 
204
+ # sig do foo end =>
205
+ class SigBlockPattern < Pattern
206
+ def replace(segment)
207
+ segment.gsub(/(sig\s*do.+end)(.*)/m) do
208
+ "#{blank($1)}#{$2}"
209
+ end
210
+ end
211
+ end
212
+
175
213
  def on_stmts_add(node, value)
176
- # sig { foo }
177
214
  if value.match?(/<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <brace_block <stmts .+>>>/)
215
+ # sig { foo }
178
216
  patterns << SigBracesPattern.new(value.range)
217
+ elsif value.match?(/<method_add_block <method_add_arg <fcall <@ident sig>> <args >> <do_block <bodystmt .+>>>/)
218
+ # sig do foo end
219
+ patterns << SigBlockPattern.new(value.range)
179
220
  end
180
221
 
181
222
  super
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sorbet
4
4
  module Eraser
5
- VERSION = "0.1.1"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
data/lib/t/enum.rb CHANGED
@@ -226,7 +226,7 @@ module T
226
226
  @values.freeze
227
227
  @mapping.freeze
228
228
 
229
- orphaned_instances = T.must(@values) - @mapping.values
229
+ orphaned_instances = @values - @mapping.values
230
230
  if !orphaned_instances.empty?
231
231
  raise "Enum values must be assigned to constants: #{orphaned_instances.map {|v| v.instance_variable_get('@serialized_val')}}"
232
232
  end
data/lib/t/props.rb ADDED
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module T
4
+ # Here is the place we don't match up to sorbet because we simply don't
5
+ # implement as much as they do in the runtime. If there are pieces of this
6
+ # that folks would like implemented I'd be happy to include them. This is here
7
+ # just to get a baseline for folks using T::Struct with basic const/prop
8
+ # calls.
9
+ module Props
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ # Here we're implementing a very basic version of the prop/const methods
15
+ # that are in sorbet-runtime. These are only here to allow consumers to call
16
+ # them and not raise errors and for bookkeeping.
17
+ module ClassMethods
18
+ def props
19
+ @props ||= []
20
+ end
21
+
22
+ def prop(name, rules = {})
23
+ create_prop(name, rules)
24
+ attr_accessor(name) unless rules[:without_accessors]
25
+ end
26
+
27
+ def const(name, rules = {})
28
+ create_prop(name, rules)
29
+ attr_reader(name) unless rules[:without_accessors]
30
+ end
31
+
32
+ private
33
+
34
+ def create_prop(name, rules)
35
+ props << [name, rules]
36
+ props.sort_by!(&:first)
37
+ end
38
+ end
39
+
40
+ # Here we're going to check against the props that have been defined on the
41
+ # class level and set appropriate values.
42
+ def initialize(hash = {})
43
+ self.class.props.each do |name, rules|
44
+ if hash.key?(name)
45
+ instance_variable_set("@#{name}", hash.delete(name))
46
+ elsif rules.key?(:default)
47
+ instance_variable_set("@#{name}", rules[:default])
48
+ else
49
+ raise ArgumentError, "missing keyword: #{name}"
50
+ end
51
+ end
52
+
53
+ raise ArgumentError, "unknown keyword: #{hash.keys.first}" unless hash.empty?
54
+ end
55
+
56
+ # This module is entirely empty because we haven't implemented anything from
57
+ # sorbet-runtime here.
58
+ module Serializable
59
+ end
60
+
61
+ # This is empty for the same reason.
62
+ module Constructor
63
+ end
64
+ end
65
+ end
data/lib/t/struct.rb CHANGED
@@ -1,12 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module T
4
+ # This is the actual parent class of T::Struct. It's here to match up the
5
+ # inheritance chain in case someone is doing reflection.
6
+ class InexactStruct
7
+ include Props
8
+ include Props::Serializable
9
+ include Props::Constructor
10
+ end
11
+
4
12
  # This is a shim for the T::Struct class because since you're actually
5
13
  # inheriting from the class there's not really a way to remove it from the
6
14
  # source.
7
- class Struct
8
- # include T::Props
9
- # include T::Props::Serializable
10
- # include T::Props::Constructor
15
+ class Struct < InexactStruct
16
+ def self.inherited(child)
17
+ super(child)
18
+
19
+ child.define_singleton_method(:inherited) do |grandchild|
20
+ super(grandchild)
21
+ raise "#{grandchild.name} is a subclass of T::Struct and cannot be subclassed"
22
+ end
23
+ end
24
+ end
25
+
26
+ class ImmutableStruct < InexactStruct
27
+ def self.inherited(child)
28
+ super(child)
29
+
30
+ child.define_singleton_method(:inherited) do |grandchild|
31
+ super(grandchild)
32
+ raise "#{grandchild.name} is a subclass of T::ImmutableStruct and cannot be subclassed"
33
+ end
34
+ end
35
+
36
+ def initialize(hash = {})
37
+ super
38
+ freeze
39
+ end
40
+
41
+ # Matches the signature in Props, but raises since this is an immutable struct and only const is allowed
42
+ def self.prop(name, rules = {})
43
+ return super if rules[:immutable]
44
+
45
+ raise "Cannot use `prop` in #{self.name} because it is an immutable struct. Use `const` instead"
46
+ end
47
+
48
+ def with(changed_props)
49
+ raise "Cannot use `with` in #{self.class.name} because it is an immutable struct"
50
+ end
11
51
  end
12
52
  end
data/lib/t.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "t/enum"
4
+ require "t/props"
4
5
  require "t/struct"
5
6
 
6
7
  # For some constructs, it doesn't make as much sense to entirely remove them
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sorbet-eraser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-18 00:00:00.000000000 Z
11
+ date: 2023-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,7 @@ files:
80
80
  - lib/sorbet/eraser/version.rb
81
81
  - lib/t.rb
82
82
  - lib/t/enum.rb
83
+ - lib/t/props.rb
83
84
  - lib/t/struct.rb
84
85
  - sorbet-eraser.gemspec
85
86
  homepage: https://github.com/kddnewton/sorbet-eraser
@@ -87,7 +88,7 @@ licenses:
87
88
  - MIT
88
89
  metadata:
89
90
  bug_tracker_uri: https://github.com/kddnewton/sorbet-eraser/issues
90
- changelog_uri: https://github.com/kddnewton/sorbet-eraser/blob/v0.1.1/CHANGELOG.md
91
+ changelog_uri: https://github.com/kddnewton/sorbet-eraser/blob/v0.3.0/CHANGELOG.md
91
92
  source_code_uri: https://github.com/kddnewton/sorbet-eraser
92
93
  rubygems_mfa_required: 'true'
93
94
  post_install_message:
@@ -105,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
106
  - !ruby/object:Gem::Version
106
107
  version: '0'
107
108
  requirements: []
108
- rubygems_version: 3.2.3
109
+ rubygems_version: 3.4.1
109
110
  signing_key:
110
111
  specification_version: 4
111
112
  summary: Erase all traces of sorbet-runtime code.