From 04233fd9bfe9632b5e7291847b5d4fb43d9149ce Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Wed, 30 Oct 2024 09:14:38 -0400 Subject: [PATCH] Use Prism to determine valid names --- lib/tapioca/helpers/rbi_helper.rb | 38 ++++++++++--------------------- lib/tapioca/internal.rb | 1 + 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/lib/tapioca/helpers/rbi_helper.rb b/lib/tapioca/helpers/rbi_helper.rb index a2249280a..b67bb2520 100644 --- a/lib/tapioca/helpers/rbi_helper.rb +++ b/lib/tapioca/helpers/rbi_helper.rb @@ -107,36 +107,22 @@ def as_non_nilable_type(type) sig { params(name: String).returns(T::Boolean) } def valid_method_name?(name) - # try to parse a method definition with this name - iseq = RubyVM::InstructionSequence.compile("def #{name}; end", nil, nil, 0, false) - # pull out the first operation in the instruction sequence and its first argument - op, arg, _data = iseq.to_a.dig(-1, 0) - # make sure that the operation is a method definition and the method that was - # defined has the expected name, for example, for `def !foo; end` we don't get - # a syntax error but instead get a method defined as `"foo"` - op == :definemethod && arg == name.to_sym - rescue SyntaxError - false + # Special case: Prism supports parsing `def @foo; end`, but the Sorbet parser doesn't. This condition can go away + # once Sorbet is using Prism under the hood as it will no longer result in an RBI that Sorbet can't parse + return false if name.start_with?("@") + + result = Prism.parse("def #{name}(a); end") + return false unless result.success? + + # We don't consider `def foo.bar` as valid for generating RBIs since only `def self.bar` is supported + method_def = result.value.statements.body.first + receiver = method_def.receiver + !receiver || receiver.is_a?(Prism::SelfNode) end sig { params(name: String).returns(T::Boolean) } def valid_parameter_name?(name) - sentinel_method_name = :sentinel_method_name - # try to parse a method definition with this name as the name of a - # keyword parameter. If we use a positional parameter, then parameter names - # like `&` (and maybe others) will be treated like `def foo(&); end` and will - # thus be considered valid. Using a required keyword parameter prevents that - # confusion between Ruby syntax and parameter name. - iseq = RubyVM::InstructionSequence.compile("def #{sentinel_method_name}(#{name}:); end", nil, nil, 0, false) - # pull out the first operation in the instruction sequence and its first argument and data - op, arg, data = iseq.to_a.dig(-1, 0) - # make sure that: - # 1. a method was defined, and - # 2. the method has the expected method name, and - # 3. the method has a keyword parameter with the expected name - op == :definemethod && arg == sentinel_method_name && data.dig(11, :keyword, 0) == name.to_sym - rescue SyntaxError - false + Prism.parse("def sentinel_method_name(#{name}:); end").success? end end end diff --git a/lib/tapioca/internal.rb b/lib/tapioca/internal.rb index 1ad533b6d..3867ff54c 100644 --- a/lib/tapioca/internal.rb +++ b/lib/tapioca/internal.rb @@ -23,6 +23,7 @@ require "thor" require "yaml" require "yard-sorbet" +require "prism" require "tapioca/runtime/dynamic_mixin_compiler" require "tapioca/sorbet_ext/backcompat_patches"