Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a reflection API for determining the fields of a node #2709

Merged
merged 1 commit into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ out.svg
/lib/prism/dsl.rb
/lib/prism/mutation_compiler.rb
/lib/prism/node.rb
/lib/prism/reflection.rb
/lib/prism/serialize.rb
/lib/prism/visitor.rb
/sorbet/
Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ A lot of code in prism's repository is templated from a single configuration fil
* `lib/prism/dsl.rb` - for defining the DSL for the nodes in Ruby
* `lib/prism/mutation_compiler.rb` - for defining the mutation compiler for the nodes in Ruby
* `lib/prism/node.rb` - for defining the nodes in Ruby
* `lib/prism/reflection.rb` - for defining the reflection API in Ruby
* `lib/prism/serialize.rb` - for defining how to deserialize the nodes in Ruby
* `lib/prism/visitor.rb` - for defining the visitor interface for the nodes in Ruby
* `src/diagnostic.c` - for defining how to build diagnostics
Expand Down
30 changes: 28 additions & 2 deletions ext/prism/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -969,7 +969,7 @@ parse_input_success_p(pm_string_t *input, const pm_options_t *options) {

/**
* call-seq:
* Prism::parse_success?(source, **options) -> Array
* Prism::parse_success?(source, **options) -> bool
*
* Parse the given string and return true if it parses without errors. For
* supported options, see Prism::parse.
Expand All @@ -989,7 +989,19 @@ parse_success_p(int argc, VALUE *argv, VALUE self) {

/**
* call-seq:
* Prism::parse_file_success?(filepath, **options) -> Array
* Prism::parse_failure?(source, **options) -> bool
*
* Parse the given string and return true if it parses with errors. For
* supported options, see Prism::parse.
*/
static VALUE
parse_failure_p(int argc, VALUE *argv, VALUE self) {
return RTEST(parse_success_p(argc, argv, self)) ? Qfalse : Qtrue;
}

/**
* call-seq:
* Prism::parse_file_success?(filepath, **options) -> bool
*
* Parse the given file and return true if it parses without errors. For
* supported options, see Prism::parse.
Expand All @@ -1008,6 +1020,18 @@ parse_file_success_p(int argc, VALUE *argv, VALUE self) {
return result;
}

/**
* call-seq:
* Prism::parse_file_failure?(filepath, **options) -> bool
*
* Parse the given file and return true if it parses with errors. For
* supported options, see Prism::parse.
*/
static VALUE
parse_file_failure_p(int argc, VALUE *argv, VALUE self) {
return RTEST(parse_file_success_p(argc, argv, self)) ? Qfalse : Qtrue;
}

/******************************************************************************/
/* Utility functions exposed to make testing easier */
/******************************************************************************/
Expand Down Expand Up @@ -1366,7 +1390,9 @@ Init_prism(void) {
rb_define_singleton_method(rb_cPrism, "parse_lex", parse_lex, -1);
rb_define_singleton_method(rb_cPrism, "parse_lex_file", parse_lex_file, -1);
rb_define_singleton_method(rb_cPrism, "parse_success?", parse_success_p, -1);
rb_define_singleton_method(rb_cPrism, "parse_failure?", parse_failure_p, -1);
rb_define_singleton_method(rb_cPrism, "parse_file_success?", parse_file_success_p, -1);
rb_define_singleton_method(rb_cPrism, "parse_file_failure?", parse_file_failure_p, -1);

#ifndef PRISM_EXCLUDE_SERIALIZATION
rb_define_singleton_method(rb_cPrism, "dump", dump, -1);
Expand Down
17 changes: 1 addition & 16 deletions lib/prism.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module Prism
autoload :NodeInspector, "prism/node_inspector"
autoload :Pack, "prism/pack"
autoload :Pattern, "prism/pattern"
autoload :Reflection, "prism/reflection"
autoload :Serialize, "prism/serialize"
autoload :Translation, "prism/translation"
autoload :Visitor, "prism/visitor"
Expand Down Expand Up @@ -64,22 +65,6 @@ def self.lex_ripper(source)
def self.load(source, serialized)
Serialize.load(source, serialized)
end

# :call-seq:
# Prism::parse_failure?(source, **options) -> bool
#
# Returns true if the source parses with errors.
def self.parse_failure?(source, **options)
!parse_success?(source, **options)
end

# :call-seq:
# Prism::parse_file_failure?(filepath, **options) -> bool
#
# Returns true if the file at filepath parses with errors.
def self.parse_file_failure?(filepath, **options)
!parse_file_success?(filepath, **options)
end
end

require_relative "prism/node"
Expand Down
10 changes: 10 additions & 0 deletions lib/prism/ffi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,22 @@ def parse_success?(code, **options)
LibRubyParser::PrismString.with_string(code) { |string| parse_file_success_common(string, options) }
end

# Mirror the Prism.parse_failure? API by using the serialization API.
def parse_failure?(code, **options)
!parse_success?(code, **options)
end

# Mirror the Prism.parse_file_success? API by using the serialization API.
def parse_file_success?(filepath, **options)
options[:filepath] = filepath
LibRubyParser::PrismString.with_file(filepath) { |string| parse_file_success_common(string, options) }
end

# Mirror the Prism.parse_file_failure? API by using the serialization API.
def parse_file_failure?(filepath, **options)
!parse_file_success?(filepath, **options)
end

private

def dump_common(string, options) # :nodoc:
Expand Down
3 changes: 3 additions & 0 deletions prism.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Gem::Specification.new do |spec|
"lib/prism/parse_result/newlines.rb",
"lib/prism/pattern.rb",
"lib/prism/polyfill/string.rb",
"lib/prism/reflection.rb",
"lib/prism/serialize.rb",
"lib/prism/translation.rb",
"lib/prism/translation/parser.rb",
Expand Down Expand Up @@ -136,6 +137,7 @@ Gem::Specification.new do |spec|
"sig/prism/pack.rbs",
"sig/prism/parse_result.rbs",
"sig/prism/pattern.rbs",
"sig/prism/reflection.rbs",
"sig/prism/serialize.rbs",
"sig/prism/visitor.rbs",
"rbi/prism.rbi",
Expand All @@ -145,6 +147,7 @@ Gem::Specification.new do |spec|
"rbi/prism/node_ext.rbi",
"rbi/prism/node.rbi",
"rbi/prism/parse_result.rbi",
"rbi/prism/reflection.rbi",
"rbi/prism/translation/parser/compiler.rbi",
"rbi/prism/translation/ripper.rbi",
"rbi/prism/translation/ripper/ripper_compiler.rbi",
Expand Down
64 changes: 32 additions & 32 deletions rbi/prism.rbi
Original file line number Diff line number Diff line change
@@ -1,57 +1,57 @@
# typed: strict

module Prism
sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(String) }
def self.dump(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(String) }
def self.dump(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(String) }
def self.dump_file(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(String) }
def self.dump_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex_file(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex_compat(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, options: T::Hash[Symbol, T.untyped]).returns(Prism::ParseResult[T::Array[T.untyped]]) }
def self.lex_compat(source, **options); end

sig { params(source: String).returns(T::Array[T.untyped]) }
def self.lex_ripper(source); end

sig { params(source: String, serialized: String).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.load(source, serialized); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse_file(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(stream: T.any(IO, StringIO), filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse_stream(stream, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(stream: T.any(IO, StringIO), command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[Prism::ProgramNode]) }
def self.parse_stream(stream, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Array[Prism::Comment]) }
def self.parse_comments(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Array[Prism::Comment]) }
def self.parse_comments(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Array[Prism::Comment]) }
def self.parse_file_comments(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Array[Prism::Comment]) }
def self.parse_file_comments(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[[Prism::ProgramNode, T::Array[T.untyped]]]) }
def self.parse_lex(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[[Prism::ProgramNode, T::Array[T.untyped]]]) }
def self.parse_lex(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(Prism::ParseResult[[Prism::ProgramNode, T::Array[T.untyped]]]) }
def self.parse_lex_file(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult[[Prism::ProgramNode, T::Array[T.untyped]]]) }
def self.parse_lex_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Boolean) }
def self.parse_success?(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Boolean) }
def self.parse_success?(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(source: String, filepath: T.nilable(String), line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Boolean) }
def self.parse_failure?(source, filepath: nil, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Boolean) }
def self.parse_failure?(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Boolean) }
def self.parse_file_success?(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Boolean) }
def self.parse_file_success?(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end

sig { params(filepath: String, line: T.nilable(Integer), offset: T.nilable(Integer), encoding: T.nilable(Encoding), frozen_string_literal: T.nilable(T::Boolean), verbose: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]])).returns(T::Boolean) }
def self.parse_file_failure?(filepath, line: nil, offset: nil, encoding: nil, frozen_string_literal: nil, verbose: nil, scopes: nil); end
sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(String, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(T::Boolean) }
def self.parse_file_failure?(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end
end