From f1845fd5614be631ef373caccee88a2098e00ebc Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 13 Sep 2024 14:49:45 -0400 Subject: [PATCH 1/2] Reverse-sync from ruby/ruby --- ext/prism/extension.c | 4 ++ include/prism/options.h | 15 +++++ src/options.c | 8 +++ src/prism.c | 87 +++++++++++++++++++---------- test/prism/api/command_line_test.rb | 4 +- test/prism/api/parse_test.rb | 4 +- test/prism/result/warnings_test.rb | 35 ++++++++---- 7 files changed, 113 insertions(+), 44 deletions(-) diff --git a/ext/prism/extension.c b/ext/prism/extension.c index 79761770f64..94fc5a104bb 100644 --- a/ext/prism/extension.c +++ b/ext/prism/extension.c @@ -31,6 +31,7 @@ ID rb_id_option_encoding; ID rb_id_option_filepath; ID rb_id_option_frozen_string_literal; ID rb_id_option_line; +ID rb_id_option_main_script; ID rb_id_option_scopes; ID rb_id_option_version; ID rb_id_source_for; @@ -179,6 +180,8 @@ build_options_i(VALUE key, VALUE value, VALUE argument) { pm_options_command_line_set(options, command_line); } + } else if (key_id == rb_id_option_main_script) { + if (!NIL_P(value)) pm_options_main_script_set(options, RTEST(value)); } else { rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, key); } @@ -1165,6 +1168,7 @@ Init_prism(void) { rb_id_option_filepath = rb_intern_const("filepath"); rb_id_option_frozen_string_literal = rb_intern_const("frozen_string_literal"); rb_id_option_line = rb_intern_const("line"); + rb_id_option_main_script = rb_intern_const("main_script"); rb_id_option_scopes = rb_intern_const("scopes"); rb_id_option_version = rb_intern_const("version"); rb_id_source_for = rb_intern("for"); diff --git a/include/prism/options.h b/include/prism/options.h index 52b5380965a..3cb73049516 100644 --- a/include/prism/options.h +++ b/include/prism/options.h @@ -139,6 +139,13 @@ typedef struct pm_options { * but ignore any encoding magic comments at the top of the file. */ bool encoding_locked; + + /** + * When the file being parsed is the main script, the shebang will be + * considered for command-line flags (or for implicit -x). The caller needs + * to pass this information to the parser so that it can behave correctly. + */ + bool main_script; } pm_options_t; /** @@ -248,6 +255,14 @@ PRISM_EXPORTED_FUNCTION void pm_options_command_line_set(pm_options_t *options, */ PRISM_EXPORTED_FUNCTION bool pm_options_version_set(pm_options_t *options, const char *version, size_t length); +/** + * Set the main script option on the given options struct. + * + * @param options The options struct to set the main script value on. + * @param main_script The main script value to set. + */ +PRISM_EXPORTED_FUNCTION void pm_options_main_script_set(pm_options_t *options, bool main_script); + /** * Allocate and zero out the scopes array on the given options struct. * diff --git a/src/options.c b/src/options.c index 643de9d95a3..e0b4735e4ae 100644 --- a/src/options.c +++ b/src/options.c @@ -100,6 +100,14 @@ pm_options_version_set(pm_options_t *options, const char *version, size_t length } } +/** + * Set the main script option on the given options struct. + */ +PRISM_EXPORTED_FUNCTION void +pm_options_main_script_set(pm_options_t *options, bool main_script) { + options->main_script = main_script; +} + // For some reason, GCC analyzer thinks we're leaking allocated scopes and // locals here, even though we definitely aren't. This is a false positive. // Ideally we wouldn't need to suppress this. diff --git a/src/prism.c b/src/prism.c index b3d843e6724..6d4d2f81990 100644 --- a/src/prism.c +++ b/src/prism.c @@ -11277,7 +11277,16 @@ parser_lex(pm_parser_t *parser) { pm_token_type_t type = PM_TOKEN_AMPERSAND; if (lex_state_spcarg_p(parser, space_seen)) { - pm_parser_warn_token(parser, &parser->current, PM_WARN_AMBIGUOUS_PREFIX_AMPERSAND); + if ((peek(parser) != ':') || (peek_offset(parser, 1) == '\0')) { + pm_parser_warn_token(parser, &parser->current, PM_WARN_AMBIGUOUS_PREFIX_AMPERSAND); + } else { + const uint8_t delim = peek_offset(parser, 1); + + if ((delim != '\'') && (delim != '"') && !char_is_identifier(parser, parser->current.end + 1)) { + pm_parser_warn_token(parser, &parser->current, PM_WARN_AMBIGUOUS_PREFIX_AMPERSAND); + } + } + type = PM_TOKEN_UAMPERSAND; } else if (lex_state_beg_p(parser)) { type = PM_TOKEN_UAMPERSAND; @@ -14836,7 +14845,7 @@ token_column(const pm_parser_t *parser, size_t newline_index, const pm_token_t * * function warns if the indentation of the two tokens does not match. */ static void -parser_warn_indentation_mismatch(pm_parser_t *parser, size_t opening_newline_index, const pm_token_t *opening_token, bool if_after_else) { +parser_warn_indentation_mismatch(pm_parser_t *parser, size_t opening_newline_index, const pm_token_t *opening_token, bool if_after_else, bool allow_indent) { // If these warnings are disabled (unlikely), then we can just return. if (!parser->warn_mismatched_indentation) return; @@ -14858,6 +14867,10 @@ parser_warn_indentation_mismatch(pm_parser_t *parser, size_t opening_newline_ind int64_t closing_column = token_column(parser, closing_newline_index, closing_token, true); if ((closing_column == -1) || (opening_column == closing_column)) return; + // If the closing column is greater than the opening column and we are + // allowing indentation, then we do not warn. + if (allow_indent && (closing_column > opening_column)) return; + // Otherwise, add a warning. PM_PARSER_WARN_FORMAT( parser, @@ -14891,7 +14904,7 @@ parse_rescues(pm_parser_t *parser, size_t opening_newline_index, const pm_token_ pm_rescue_node_t *current = NULL; while (match1(parser, PM_TOKEN_KEYWORD_RESCUE)) { - if (opening != NULL) parser_warn_indentation_mismatch(parser, opening_newline_index, opening, false); + if (opening != NULL) parser_warn_indentation_mismatch(parser, opening_newline_index, opening, false, false); parser_lex(parser); pm_rescue_node_t *rescue = pm_rescue_node_create(parser, &parser->previous); @@ -14997,7 +15010,7 @@ parse_rescues(pm_parser_t *parser, size_t opening_newline_index, const pm_token_ pm_token_t else_keyword; if (match1(parser, PM_TOKEN_KEYWORD_ELSE)) { - if (opening != NULL) parser_warn_indentation_mismatch(parser, opening_newline_index, opening, false); + if (opening != NULL) parser_warn_indentation_mismatch(parser, opening_newline_index, opening, false, false); opening_newline_index = token_newline_index(parser); else_keyword = parser->current; @@ -15037,7 +15050,7 @@ parse_rescues(pm_parser_t *parser, size_t opening_newline_index, const pm_token_ } if (match1(parser, PM_TOKEN_KEYWORD_ENSURE)) { - if (opening != NULL) parser_warn_indentation_mismatch(parser, opening_newline_index, opening, false); + if (opening != NULL) parser_warn_indentation_mismatch(parser, opening_newline_index, opening, false, false); pm_token_t ensure_keyword = parser->current; parser_lex(parser); @@ -15070,7 +15083,7 @@ parse_rescues(pm_parser_t *parser, size_t opening_newline_index, const pm_token_ } if (match1(parser, PM_TOKEN_KEYWORD_END)) { - if (opening != NULL) parser_warn_indentation_mismatch(parser, opening_newline_index, opening, false); + if (opening != NULL) parser_warn_indentation_mismatch(parser, opening_newline_index, opening, false, false); pm_begin_node_end_keyword_set(parent_node, &parser->current); } else { pm_token_t end_keyword = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end }; @@ -15703,7 +15716,7 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl PM_PARSER_WARN_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_WARN_KEYWORD_EOL); } - parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false, false); pm_token_t elsif_keyword = parser->current; parser_lex(parser); @@ -15721,7 +15734,7 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl } if (match1(parser, PM_TOKEN_KEYWORD_ELSE)) { - parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false, false); opening_newline_index = token_newline_index(parser); parser_lex(parser); @@ -15732,7 +15745,7 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl pm_accepts_block_stack_pop(parser); accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); - parser_warn_indentation_mismatch(parser, opening_newline_index, &else_keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &else_keyword, false, false); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CONDITIONAL_TERM_ELSE); pm_else_node_t *else_node = pm_else_node_create(parser, &else_keyword, else_statements, &parser->previous); @@ -15749,7 +15762,7 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl break; } } else { - parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, if_after_else); + parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, if_after_else, false); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CONDITIONAL_TERM); } @@ -18537,7 +18550,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } if (match1(parser, PM_TOKEN_KEYWORD_END)) { - parser_warn_indentation_mismatch(parser, opening_newline_index, &case_keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &case_keyword, false, false); parser_lex(parser); pop_block_exits(parser, previous_block_exits); @@ -18560,7 +18573,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // case-when node. We will continue to parse the when nodes // until we hit the end of the list. while (match1(parser, PM_TOKEN_KEYWORD_WHEN)) { - parser_warn_indentation_mismatch(parser, opening_newline_index, &case_keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &case_keyword, false, true); parser_lex(parser); pm_token_t when_keyword = parser->previous; @@ -18635,7 +18648,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // will continue to parse the in nodes until we hit the end of // the list. while (match1(parser, PM_TOKEN_KEYWORD_IN)) { - parser_warn_indentation_mismatch(parser, opening_newline_index, &case_keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &case_keyword, false, true); bool previous_pattern_matching_newlines = parser->pattern_matching_newlines; parser->pattern_matching_newlines = true; @@ -18722,7 +18735,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } } - parser_warn_indentation_mismatch(parser, opening_newline_index, &case_keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &case_keyword, false, false); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CASE_TERM); if (PM_NODE_TYPE_P(node, PM_CASE_NODE)) { @@ -18899,7 +18912,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); statements = (pm_node_t *) parse_rescues_implicit_begin(parser, opening_newline_index, &class_keyword, class_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_SCLASS); } else { - parser_warn_indentation_mismatch(parser, opening_newline_index, &class_keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &class_keyword, false, false); } expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM); @@ -18957,7 +18970,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); statements = (pm_node_t *) parse_rescues_implicit_begin(parser, opening_newline_index, &class_keyword, class_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_CLASS); } else { - parser_warn_indentation_mismatch(parser, opening_newline_index, &class_keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &class_keyword, false, false); } expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM); @@ -19228,7 +19241,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); statements = (pm_node_t *) parse_rescues_implicit_begin(parser, opening_newline_index, &def_keyword, def_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_DEF); } else { - parser_warn_indentation_mismatch(parser, opening_newline_index, &def_keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &def_keyword, false, false); } pm_accepts_block_stack_pop(parser); @@ -19381,7 +19394,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b statements = parse_statements(parser, PM_CONTEXT_FOR); } - parser_warn_indentation_mismatch(parser, opening_newline_index, &for_keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &for_keyword, false, false); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_FOR_TERM); return (pm_node_t *) pm_for_node_create(parser, index, collection, statements, &for_keyword, &in_keyword, &do_keyword, &parser->previous); @@ -19511,7 +19524,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b assert(statements == NULL || PM_NODE_TYPE_P(statements, PM_STATEMENTS_NODE)); statements = (pm_node_t *) parse_rescues_implicit_begin(parser, opening_newline_index, &module_keyword, module_keyword.start, (pm_statements_node_t *) statements, PM_RESCUES_MODULE); } else { - parser_warn_indentation_mismatch(parser, opening_newline_index, &module_keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &module_keyword, false, false); } pm_constant_id_list_t locals; @@ -19577,7 +19590,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); } - parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false, false); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_UNTIL_TERM); return (pm_node_t *) pm_until_node_create(parser, &keyword, &parser->previous, predicate, statements, 0); @@ -19605,7 +19618,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON); } - parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &keyword, false, false); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_WHILE_TERM); return (pm_node_t *) pm_while_node_create(parser, &keyword, &parser->previous, predicate, statements, 0); @@ -20298,7 +20311,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b body = (pm_node_t *) parse_statements(parser, PM_CONTEXT_LAMBDA_BRACES); } - parser_warn_indentation_mismatch(parser, opening_newline_index, &operator, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &operator, false, false); expect1(parser, PM_TOKEN_BRACE_RIGHT, PM_ERR_LAMBDA_TERM_BRACE); } else { expect1(parser, PM_TOKEN_KEYWORD_DO, PM_ERR_LAMBDA_OPEN); @@ -20314,7 +20327,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b assert(body == NULL || PM_NODE_TYPE_P(body, PM_STATEMENTS_NODE)); body = (pm_node_t *) parse_rescues_implicit_begin(parser, opening_newline_index, &operator, opening.start, (pm_statements_node_t *) body, PM_RESCUES_LAMBDA); } else { - parser_warn_indentation_mismatch(parser, opening_newline_index, &operator, false); + parser_warn_indentation_mismatch(parser, opening_newline_index, &operator, false, false); } expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_LAMBDA_TERM_END); @@ -21926,6 +21939,7 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm // scopes option parser->parsing_eval = options->scopes_count > 0; + if (parser->parsing_eval) parser->warn_mismatched_indentation = false; for (size_t scope_index = 0; scope_index < options->scopes_count; scope_index++) { const pm_options_scope_t *scope = pm_options_scope_get(options, scope_index); @@ -21968,25 +21982,42 @@ pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm // "ruby" and start parsing from there. bool search_shebang = PM_PARSER_COMMAND_LINE_OPTION_X(parser); - // If the first two bytes of the source are a shebang, then we'll indicate - // that the encoding comment is at the end of the shebang. + // If the first two bytes of the source are a shebang, then we will do a bit + // of extra processing. + // + // First, we'll indicate that the encoding comment is at the end of the + // shebang. This means that when a shebang is present the encoding comment + // can begin on the second line. + // + // Second, we will check if the shebang includes "ruby". If it does, then we + // we will start parsing from there. We will also potentially warning the + // user if there is a carriage return at the end of the shebang. We will + // also potentially call the shebang callback if this is the main script to + // allow the caller to parse the shebang and find any command-line options. + // If the shebang does not include "ruby" and this is the main script being + // parsed, then we will start searching the file for a shebang that does + // contain "ruby" as if -x were passed on the command line. const uint8_t *newline = next_newline(parser->start, parser->end - parser->start); size_t length = (size_t) ((newline != NULL ? newline : parser->end) - parser->start); if (length > 2 && parser->current.end[0] == '#' && parser->current.end[1] == '!') { const char *engine; + if ((engine = pm_strnstr((const char *) parser->start, "ruby", length)) != NULL) { if (newline != NULL) { - pm_parser_warn_shebang_carriage_return(parser, parser->start, length + 1); parser->encoding_comment_start = newline + 1; + + if (options == NULL || options->main_script) { + pm_parser_warn_shebang_carriage_return(parser, parser->start, length + 1); + } } - if (options != NULL && options->shebang_callback != NULL) { + if (options != NULL && options->main_script && options->shebang_callback != NULL) { pm_parser_init_shebang(parser, options, engine, length - ((size_t) (engine - (const char *) parser->start))); } search_shebang = false; - } else if (!parser->parsing_eval) { + } else if (options->main_script && !parser->parsing_eval) { search_shebang = true; } } diff --git a/test/prism/api/command_line_test.rb b/test/prism/api/command_line_test.rb index a313845ead7..a8c43551522 100644 --- a/test/prism/api/command_line_test.rb +++ b/test/prism/api/command_line_test.rb @@ -67,7 +67,7 @@ def test_command_line_e end def test_command_line_x_implicit - result = Prism.parse_statement(<<~RUBY) + result = Prism.parse_statement(<<~RUBY, main_script: true) #!/bin/bash exit 1 @@ -90,7 +90,7 @@ def test_command_line_x_explicit end def test_command_line_x_implicit_fail - result = Prism.parse(<<~RUBY) + result = Prism.parse(<<~RUBY, main_script: true) #!/bin/bash exit 1 RUBY diff --git a/test/prism/api/parse_test.rb b/test/prism/api/parse_test.rb index 58962aab6a0..49a2b2bfb9a 100644 --- a/test/prism/api/parse_test.rb +++ b/test/prism/api/parse_test.rb @@ -87,9 +87,7 @@ def test_parse_directory rescue SystemCallError => error end - refute_nil error - return if error.is_a?(Errno::ENOMEM) - + return if error.nil? || error.is_a?(Errno::ENOMEM) assert_kind_of Errno::EISDIR, error end diff --git a/test/prism/result/warnings_test.rb b/test/prism/result/warnings_test.rb index b3817b631ff..5cff2d2d2b8 100644 --- a/test/prism/result/warnings_test.rb +++ b/test/prism/result/warnings_test.rb @@ -22,6 +22,15 @@ def test_ambiguous_regexp assert_warning("a /b/", "wrap regexp in parentheses") end + def test_ambiguous_ampersand + assert_warning("a &b", "argument prefix") + assert_warning("a &:+", "argument prefix") + + refute_warning("a &:b") + refute_warning("a &:'b'") + refute_warning("a &:\"b\"") + end + def test_binary_operator [ [:**, "argument prefix"], @@ -115,6 +124,9 @@ def test_indentation_mismatch assert_warning("case 1; when 2\n end", "mismatched indentations at 'end' with 'case'") assert_warning("case 1; in 2\n end", "mismatched indentations at 'end' with 'case'") + assert_warning(" case 1\nwhen 2\n end", "mismatched indentations at 'when' with 'case'") + refute_warning("case 1\n when 2\n when 3\nend") # case/when allows more indentation + assert_warning("-> {\n }", "mismatched indentations at '}' with '->'") assert_warning("-> do\n end", "mismatched indentations at 'end' with '->'") assert_warning("-> do\n rescue\nend", "mismatched indentations at 'rescue' with '->'") @@ -333,23 +345,24 @@ def test_shebang_ending_with_carriage_return def test_shebang_ending_with_carriage_return msg = "shebang line ending with \\r may cause problems" - assert_warning(<<~RUBY, msg, compare: false) + assert_warning(<<~RUBY, msg, compare: false, main_script: true) #!ruby\r p(123) RUBY - assert_warning(<<~RUBY, msg, compare: false) + assert_warning(<<~RUBY, msg, compare: false, main_script: true) #!ruby \r p(123) RUBY - assert_warning(<<~RUBY, msg, compare: false) + assert_warning(<<~RUBY, msg, compare: false, main_script: true) #!ruby -Eutf-8\r p(123) RUBY - # Used with the `-x` object, to ignore the script up until the first shebang that mentioned "ruby". - assert_warning(<<~SCRIPT, msg, compare: false) + # Used with the `-x` object, to ignore the script up until the first + # shebang that mentioned "ruby". + assert_warning(<<~SCRIPT, msg, compare: false, main_script: true) #!/usr/bin/env bash # Some initial shell script or other content # that Ruby should ignore @@ -361,11 +374,11 @@ def test_shebang_ending_with_carriage_return puts "Hello from Ruby!" SCRIPT - refute_warning("#ruby not_a_shebang\r\n", compare: false) + refute_warning("#ruby not_a_shebang\r\n", compare: false, main_script: true) - # CRuby doesn't emit the warning if a malformed file only has `\r` and not `\n`. - # https://bugs.ruby-lang.org/issues/20700 - refute_warning("#!ruby\r", compare: false) + # CRuby doesn't emit the warning if a malformed file only has `\r` and + # not `\n`. https://bugs.ruby-lang.org/issues/20700. + refute_warning("#!ruby\r", compare: false, main_script: true) end end @@ -381,8 +394,8 @@ def test_warnings_verbosity private - def assert_warning(source, *messages, compare: true) - warnings = Prism.parse(source).warnings + def assert_warning(source, *messages, compare: true, **options) + warnings = Prism.parse(source, **options).warnings assert_equal messages.length, warnings.length, "Expected #{messages.length} warning(s) in #{source.inspect}, got #{warnings.map(&:message).inspect}" warnings.zip(messages).each do |warning, message| From 0b527ca93f544e6057d4fd9276e5da1300b51f5f Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 13 Sep 2024 15:00:03 -0400 Subject: [PATCH 2/2] Expose main_script in serialization API --- ext/prism/extension.c | 5 ++ .../src/test/java/org/prism/DummyTest.java | 2 + java/org/prism/ParsingOptions.java | 6 +- javascript/src/parsePrism.js | 5 +- lib/prism/ffi.rb | 3 + rbi/prism.rbi | 68 +++++++++---------- src/options.c | 1 + templates/sig/prism.rbs.erb | 27 ++++---- 8 files changed, 69 insertions(+), 48 deletions(-) diff --git a/ext/prism/extension.c b/ext/prism/extension.c index 94fc5a104bb..f08682e0f3e 100644 --- a/ext/prism/extension.c +++ b/ext/prism/extension.c @@ -756,6 +756,11 @@ parse_input(pm_string_t *input, const pm_options_t *options) { * has been set. This should be a boolean or nil. * * `line` - the line number that the parse starts on. This should be an * integer or nil. Note that this is 1-indexed. + * * `main_script` - a boolean indicating whether or not the source being parsed + * is the main script being run by the interpreter. This controls whether + * or not shebangs are parsed for additional flags and whether or not the + * parser will attempt to find a matching shebang if the first one does + * not contain the word "ruby". * * `scopes` - the locals that are in scope surrounding the code that is being * parsed. This should be an array of arrays of symbols or nil. Scopes are * ordered from the outermost scope to the innermost one. diff --git a/java-wasm/src/test/java/org/prism/DummyTest.java b/java-wasm/src/test/java/org/prism/DummyTest.java index de99af79046..eaa977579d6 100644 --- a/java-wasm/src/test/java/org/prism/DummyTest.java +++ b/java-wasm/src/test/java/org/prism/DummyTest.java @@ -41,6 +41,7 @@ public void test1() { EnumSet.noneOf(ParsingOptions.CommandLine.class), ParsingOptions.SyntaxVersion.LATEST, false, + false, new byte[][][] {} ); @@ -93,6 +94,7 @@ public void test2() { EnumSet.noneOf(ParsingOptions.CommandLine.class), ParsingOptions.SyntaxVersion.LATEST, false, + false, new byte[][][] {} ); diff --git a/java/org/prism/ParsingOptions.java b/java/org/prism/ParsingOptions.java index 330c2df449f..cb5baa4056d 100644 --- a/java/org/prism/ParsingOptions.java +++ b/java/org/prism/ParsingOptions.java @@ -44,10 +44,11 @@ public enum CommandLine { A, E, L, N, P, X }; * @param commandLine the set of flags that were set on the command line * @param version code of Ruby version which syntax will be used to parse * @param encodingLocked whether the encoding is locked (should almost always be false) + * @param mainScript whether the file is the main script * @param scopes scopes surrounding the code that is being parsed with local variable names defined in every scope * ordered from the outermost scope to the innermost one */ - public static byte[] serialize(byte[] filepath, int line, byte[] encoding, boolean frozenStringLiteral, EnumSet commandLine, SyntaxVersion version, boolean encodingLocked, byte[][][] scopes) { + public static byte[] serialize(byte[] filepath, int line, byte[] encoding, boolean frozenStringLiteral, EnumSet commandLine, SyntaxVersion version, boolean encodingLocked, boolean mainScript, byte[][][] scopes) { final ByteArrayOutputStream output = new ByteArrayOutputStream(); // filepath @@ -73,6 +74,9 @@ public static byte[] serialize(byte[] filepath, int line, byte[] encoding, boole // encodingLocked output.write(encodingLocked ? 1 : 0); + // mainScript + output.write(mainScript ? 1 : 0); + // scopes // number of scopes diff --git a/javascript/src/parsePrism.js b/javascript/src/parsePrism.js index 22db25ad066..a85f49e8c3e 100644 --- a/javascript/src/parsePrism.js +++ b/javascript/src/parsePrism.js @@ -92,7 +92,7 @@ function dumpOptions(options) { } template.push("C"); - values.push(options.frozen_string_literal === undefined ? 0 : 1); + values.push((options.frozen_string_literal === undefined || options.frozen_string_literal === false || options.frozen_string_literal === null) ? 0 : 1); template.push("C"); values.push(dumpCommandLineOptions(options)); @@ -109,6 +109,9 @@ function dumpOptions(options) { template.push("C"); values.push(options.encoding === false ? 1 : 0); + template.push("C"); + values.push((options.main_script === undefined || options.main_script === false || options.main_script === null) ? 0 : 1); + template.push("L"); if (options.scopes) { const scopes = options.scopes; diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index b972520be10..40a4b32621d 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -448,6 +448,9 @@ def dump_options(options) template << "C" values << (options[:encoding] == false ? 1 : 0) + template << "C" + values << (options.fetch(:main_script, false) ? 1 : 0) + template << "L" if (scopes = options[:scopes]) values << scopes.length diff --git a/rbi/prism.rbi b/rbi/prism.rbi index 4b1eb23df92..fa9cd6c40c6 100644 --- a/rbi/prism.rbi +++ b/rbi/prism.rbi @@ -1,17 +1,17 @@ # typed: strict module Prism - sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), 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, main_script: nil, scopes: nil, version: nil); end - sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), 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, main_script: nil, scopes: nil, version: nil); end - sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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::LexResult) } - def self.lex(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end + sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::LexResult) } + def self.lex(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, main_script: nil, scopes: nil, version: nil); end - sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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::LexResult) } - def self.lex_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end + sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::LexResult) } + def self.lex_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, main_script: nil, scopes: nil, version: nil); end sig { params(source: String, options: T::Hash[Symbol, T.untyped]).returns(Prism::LexCompat::Result) } def self.lex_compat(source, **options); end @@ -22,42 +22,42 @@ module Prism sig { params(source: String, serialized: String).returns(Prism::ParseResult) } def self.load(source, serialized); end - sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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) } - def self.parse(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end + sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult) } + def self.parse(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, main_script: nil, scopes: nil, version: nil); end - sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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) } - def self.parse_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end + sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult) } + def self.parse_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, main_script: nil, scopes: nil, version: nil); end - sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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)).void } - def self.profile(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end + sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).void } + def self.profile(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, main_script: nil, scopes: nil, version: nil); end - sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).void } - def self.profile_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end + sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).void } + def self.profile_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, main_script: nil, scopes: nil, version: nil); end - sig { params(stream: T.any(IO, StringIO), command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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) } - 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(stream: T.any(IO, StringIO), command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseResult) } + def self.parse_stream(stream, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, main_script: nil, scopes: nil, version: 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(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), main_script: T.nilable(T::Boolean), 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, main_script: nil, scopes: nil, version: nil); end - sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), 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, main_script: nil, scopes: nil, version: nil); end - sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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::ParseLexResult) } - 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(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseLexResult) } + def self.parse_lex(source, command_line: nil, encoding: nil, filepath: nil, frozen_string_literal: nil, line: nil, main_script: nil, scopes: nil, version: nil); end - sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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::ParseLexResult) } - def self.parse_lex_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, scopes: nil, version: nil); end + sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), scopes: T.nilable(T::Array[T::Array[Symbol]]), version: T.nilable(String)).returns(Prism::ParseLexResult) } + def self.parse_lex_file(filepath, command_line: nil, encoding: nil, frozen_string_literal: nil, line: nil, main_script: nil, scopes: nil, version: nil); end - sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), 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, main_script: nil, scopes: nil, version: nil); end - sig { params(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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(source: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), filepath: T.nilable(String), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), 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, main_script: nil, scopes: nil, version: nil); end - sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), 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, main_script: nil, scopes: nil, version: nil); end - sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, 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 + sig { params(filepath: String, command_line: T.nilable(String), encoding: T.nilable(T.any(FalseClass, Encoding)), frozen_string_literal: T.nilable(T::Boolean), line: T.nilable(Integer), main_script: T.nilable(T::Boolean), 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, main_script: nil, scopes: nil, version: nil); end end diff --git a/src/options.c b/src/options.c index e0b4735e4ae..67b257138cb 100644 --- a/src/options.c +++ b/src/options.c @@ -241,6 +241,7 @@ pm_options_read(pm_options_t *options, const char *data) { options->command_line = (uint8_t) *data++; options->version = (pm_options_version_t) *data++; options->encoding_locked = ((uint8_t) *data++) > 0; + options->main_script = ((uint8_t) *data++) > 0; uint32_t scopes_count = pm_options_read_u32(data); data += 4; diff --git a/templates/sig/prism.rbs.erb b/templates/sig/prism.rbs.erb index 56d66a516a0..7c03218ab50 100644 --- a/templates/sig/prism.rbs.erb +++ b/templates/sig/prism.rbs.erb @@ -19,13 +19,14 @@ module Prism def self.<%= method %>: ( String source, + ?encoding: Encoding | false, ?filepath: String, + ?frozen_string_literal: bool, ?line: Integer, + ?main_script: bool, ?offset: Integer, - ?encoding: Encoding, - ?frozen_string_literal: bool, - ?verbose: bool, - ?scopes: Array[Array[Symbol]] + ?scopes: Array[Array[Symbol]], + ?verbose: bool ) -> <%= return_type %> <%- end -%> @@ -54,12 +55,13 @@ module Prism def self.<%= method %>: ( String filepath, + ?encoding: Encoding | false, + ?frozen_string_literal: bool, ?line: Integer, + ?main_script: bool, ?offset: Integer, - ?encoding: Encoding, - ?frozen_string_literal: bool, - ?verbose: bool, - ?scopes: Array[Array[Symbol]] + ?scopes: Array[Array[Symbol]], + ?verbose: bool ) -> <%= return_type %> <%- end -%> @@ -69,12 +71,13 @@ module Prism def self.parse_stream: ( _Stream stream, + ?encoding: Encoding | false, ?filepath: String, + ?frozen_string_literal: bool, ?line: Integer, + ?main_script: bool, ?offset: Integer, - ?encoding: Encoding, - ?frozen_string_literal: bool, - ?verbose: bool, - ?scopes: Array[Array[Symbol]] + ?scopes: Array[Array[Symbol]], + ?verbose: bool ) -> ParseResult end