From e08c128417fd840c72f8e7d6cbfa59970fb7a5b8 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sat, 10 Feb 2024 10:46:39 -0500 Subject: [PATCH] [ruby/prism] Error messages closer to CRuby https://github.com/ruby/prism/commit/19ffa0b980 --- prism/diagnostic.c | 14 ++-- prism/diagnostic.h | 2 + prism/prism.c | 88 ++++++++++++++------ prism/templates/src/token_type.c.erb | 4 +- test/prism/errors_test.rb | 119 +++++++++++++-------------- test/prism/format_errors_test.rb | 4 +- test/prism/parser_test.rb | 3 + 7 files changed, 137 insertions(+), 97 deletions(-) diff --git a/prism/diagnostic.c b/prism/diagnostic.c index c718246c80..4f9b3bf75b 100644 --- a/prism/diagnostic.c +++ b/prism/diagnostic.c @@ -159,7 +159,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = { [PM_ERR_ESCAPE_INVALID_UNICODE_LONG] = { "invalid Unicode escape sequence; maximum length is 6 digits", PM_ERROR_LEVEL_FATAL }, [PM_ERR_ESCAPE_INVALID_UNICODE_TERM] = { "invalid Unicode escape sequence; needs closing `}`", PM_ERROR_LEVEL_FATAL }, [PM_ERR_EXPECT_ARGUMENT] = { "expected an argument", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_EXPECT_EOL_AFTER_STATEMENT] = { "expected a newline or semicolon after the statement", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_EXPECT_EOL_AFTER_STATEMENT] = { "unexpected %s, expecting end-of-input", PM_ERROR_LEVEL_FATAL }, [PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ] = { "expected an expression after `&&=`", PM_ERROR_LEVEL_FATAL }, [PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ] = { "expected an expression after `||=`", PM_ERROR_LEVEL_FATAL }, [PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA] = { "expected an expression after `,`", PM_ERROR_LEVEL_FATAL }, @@ -190,8 +190,8 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = { [PM_ERR_HASH_VALUE] = { "expected a value in the hash literal", PM_ERROR_LEVEL_FATAL }, [PM_ERR_HEREDOC_TERM] = { "could not find a terminator for the heredoc", PM_ERROR_LEVEL_FATAL }, [PM_ERR_INCOMPLETE_QUESTION_MARK] = { "incomplete expression at `?`", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_VARIABLE_CLASS] = { "incomplete class variable", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE] = { "incomplete instance variable", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_INCOMPLETE_VARIABLE_CLASS] = { "`%.*s' is not allowed as an class variable name", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_INCOMPLETE_VARIABLE_INSTANCE] = { "`%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_FATAL }, [PM_ERR_INVALID_FLOAT_EXPONENT] = { "invalid exponent", PM_ERROR_LEVEL_FATAL }, [PM_ERR_INVALID_NUMBER_BINARY] = { "invalid binary number", PM_ERROR_LEVEL_FATAL }, [PM_ERR_INVALID_NUMBER_DECIMAL] = { "invalid decimal number", PM_ERROR_LEVEL_FATAL }, @@ -202,7 +202,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = { [PM_ERR_INVALID_MULTIBYTE_CHARACTER] = { "invalid multibyte character 0x%X", PM_ERROR_LEVEL_FATAL }, [PM_ERR_INVALID_PRINTABLE_CHARACTER] = { "invalid character `%c`", PM_ERROR_LEVEL_FATAL }, [PM_ERR_INVALID_PERCENT] = { "invalid `%` token", PM_ERROR_LEVEL_FATAL }, // TODO WHAT? - [PM_ERR_INVALID_VARIABLE_GLOBAL] = { "invalid global variable", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_INVALID_VARIABLE_GLOBAL] = { "`%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_FATAL }, [PM_ERR_IT_NOT_ALLOWED] = { "`it` is not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_FATAL }, [PM_ERR_LAMBDA_OPEN] = { "expected a `do` keyword or a `{` to open the lambda block", PM_ERROR_LEVEL_FATAL }, [PM_ERR_LAMBDA_TERM_BRACE] = { "expected a lambda block beginning with `{` to end with `}`", PM_ERROR_LEVEL_FATAL }, @@ -221,6 +221,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = { [PM_ERR_MODULE_NAME] = { "expected a constant name after `module`", PM_ERROR_LEVEL_FATAL }, [PM_ERR_MODULE_TERM] = { "expected an `end` to close the `module` statement", PM_ERROR_LEVEL_FATAL }, [PM_ERR_MULTI_ASSIGN_MULTI_SPLATS] = { "multiple splats in multiple assignment", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST] = { "unexpected '%.*s' resulting in multiple splats in multiple assignment", PM_ERROR_LEVEL_FATAL }, [PM_ERR_NOT_EXPRESSION] = { "expected an expression after `not`", PM_ERROR_LEVEL_FATAL }, [PM_ERR_NO_LOCAL_VARIABLE] = { "%.*s: no such local variable", PM_ERROR_LEVEL_FATAL }, [PM_ERR_NUMBER_LITERAL_UNDERSCORE] = { "number literal ending with a `_`", PM_ERROR_LEVEL_FATAL }, @@ -274,7 +275,8 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = { [PM_ERR_STATEMENT_UNDEF] = { "unexpected an `undef` at a non-statement position", PM_ERROR_LEVEL_FATAL }, [PM_ERR_STRING_CONCATENATION] = { "expected a string for concatenation", PM_ERROR_LEVEL_FATAL }, [PM_ERR_STRING_INTERPOLATED_TERM] = { "expected a closing delimiter for the interpolated string", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_STRING_LITERAL_TERM] = { "expected a closing delimiter for the string literal", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_STRING_LITERAL_EOF] = { "unterminated string meets end of file", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_STRING_LITERAL_TERM] = { "unexpected %s, expected a string literal terminator", PM_ERROR_LEVEL_FATAL }, [PM_ERR_SYMBOL_INVALID] = { "invalid symbol", PM_ERROR_LEVEL_FATAL }, // TODO expected symbol? prism.c ~9719 [PM_ERR_SYMBOL_TERM_DYNAMIC] = { "expected a closing delimiter for the dynamic symbol", PM_ERROR_LEVEL_FATAL }, [PM_ERR_SYMBOL_TERM_INTERPOLATED] = { "expected a closing delimiter for the interpolated symbol", PM_ERROR_LEVEL_FATAL }, @@ -292,7 +294,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = { [PM_ERR_VOID_EXPRESSION] = { "unexpected void value expression", PM_ERROR_LEVEL_FATAL }, [PM_ERR_WHILE_TERM] = { "expected an `end` to close the `while` statement", PM_ERROR_LEVEL_FATAL }, [PM_ERR_WRITE_TARGET_IN_METHOD] = { "dynamic constant assignment", PM_ERROR_LEVEL_FATAL }, - [PM_ERR_WRITE_TARGET_READONLY] = { "immutable variable as a write target", PM_ERROR_LEVEL_FATAL }, + [PM_ERR_WRITE_TARGET_READONLY] = { "Can't set variable %.*s", PM_ERROR_LEVEL_FATAL }, [PM_ERR_WRITE_TARGET_UNEXPECTED] = { "unexpected write target", PM_ERROR_LEVEL_FATAL }, [PM_ERR_XSTRING_TERM] = { "expected a closing delimiter for the `%x` or backtick string", PM_ERROR_LEVEL_FATAL }, diff --git a/prism/diagnostic.h b/prism/diagnostic.h index 019afb96b3..ebd4a8bb7d 100644 --- a/prism/diagnostic.h +++ b/prism/diagnostic.h @@ -219,6 +219,7 @@ typedef enum { PM_ERR_MODULE_NAME, PM_ERR_MODULE_TERM, PM_ERR_MULTI_ASSIGN_MULTI_SPLATS, + PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST, PM_ERR_NOT_EXPRESSION, PM_ERR_NO_LOCAL_VARIABLE, PM_ERR_NUMBER_LITERAL_UNDERSCORE, @@ -272,6 +273,7 @@ typedef enum { PM_ERR_STATEMENT_UNDEF, PM_ERR_STRING_CONCATENATION, PM_ERR_STRING_INTERPOLATED_TERM, + PM_ERR_STRING_LITERAL_EOF, PM_ERR_STRING_LITERAL_TERM, PM_ERR_SYMBOL_INVALID, PM_ERR_SYMBOL_TERM_DYNAMIC, diff --git a/prism/prism.c b/prism/prism.c index 4ee21300b9..7e1fae5ed6 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -492,7 +492,8 @@ pm_parser_err(pm_parser_t *parser, const uint8_t *start, const uint8_t *end, pm_ /** * Append an error to the list of errors on the parser using a format string. */ -#define PM_PARSER_ERR_FORMAT(parser, start, end, diag_id, ...) pm_diagnostic_list_append_format(&parser->error_list, start, end, diag_id, __VA_ARGS__) +#define PM_PARSER_ERR_FORMAT(parser, start, end, diag_id, ...) \ + pm_diagnostic_list_append_format(&parser->error_list, start, end, diag_id, __VA_ARGS__) /** * Append an error to the list of errors on the parser using the location of the @@ -507,7 +508,8 @@ pm_parser_err_current(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { * Append an error to the list of errors on the parser using the given location * using a format string. */ -#define PM_PARSER_ERR_LOCATION_FORMAT(parser, location, diag_id, ...) pm_diagnostic_list_append_format(&parser->error_list, (location)->start, (location)->end, diag_id, __VA_ARGS__) +#define PM_PARSER_ERR_LOCATION_FORMAT(parser, location, diag_id, ...) \ + PM_PARSER_ERR_FORMAT(parser, (location)->start, (location)->end, diag_id, __VA_ARGS__) /** * Append an error to the list of errors on the parser using the location of the @@ -522,7 +524,15 @@ pm_parser_err_node(pm_parser_t *parser, const pm_node_t *node, pm_diagnostic_id_ * Append an error to the list of errors on the parser using the location of the * given node and a format string. */ -#define PM_PARSER_ERR_NODE_FORMAT(parser, node, diag_id, ...) pm_diagnostic_list_append_format(&parser->error_list, node->location.start, node->location.end, diag_id, __VA_ARGS__) +#define PM_PARSER_ERR_NODE_FORMAT(parser, node, diag_id, ...) \ + PM_PARSER_ERR_FORMAT(parser, (node)->location.start, (node)->location.end, diag_id, __VA_ARGS__) + +/** + * Append an error to the list of errors on the parser using the location of the + * given node and a format string, and add on the content of the node. + */ +#define PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, node, diag_id) \ + PM_PARSER_ERR_NODE_FORMAT(parser, node, diag_id, (int) ((node)->location.end - (node)->location.start), (const char *) (node)->location.start) /** * Append an error to the list of errors on the parser using the location of the @@ -546,7 +556,15 @@ pm_parser_err_token(pm_parser_t *parser, const pm_token_t *token, pm_diagnostic_ * Append an error to the list of errors on the parser using the location of the * given token and a format string. */ -#define PM_PARSER_ERR_TOKEN_FORMAT(parser, token, diag_id, ...) pm_diagnostic_list_append_format(&parser->error_list, (token).start, (token).end, diag_id, __VA_ARGS__) +#define PM_PARSER_ERR_TOKEN_FORMAT(parser, token, diag_id, ...) \ + PM_PARSER_ERR_FORMAT(parser, (token).start, (token).end, diag_id, __VA_ARGS__) + +/** + * Append an error to the list of errors on the parser using the location of the + * given token and a format string, and add on the content of the token. + */ +#define PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, token, diag_id) \ + PM_PARSER_ERR_TOKEN_FORMAT(parser, token, diag_id, (int) ((token).end - (token).start), (const char *) (token).start) /** * Append a warning to the list of warnings on the parser. @@ -4643,13 +4661,20 @@ pm_multi_target_node_create(pm_parser_t *parser) { */ static void pm_multi_target_node_targets_append(pm_parser_t *parser, pm_multi_target_node_t *node, pm_node_t *target) { - if (PM_NODE_TYPE_P(target, PM_SPLAT_NODE) || PM_NODE_TYPE_P(target, PM_IMPLICIT_REST_NODE)) { + if (PM_NODE_TYPE_P(target, PM_SPLAT_NODE)) { if (node->rest == NULL) { node->rest = target; } else { pm_parser_err_node(parser, target, PM_ERR_MULTI_ASSIGN_MULTI_SPLATS); pm_node_list_append(&node->rights, target); } + } else if (PM_NODE_TYPE_P(target, PM_IMPLICIT_REST_NODE)) { + if (node->rest == NULL) { + node->rest = target; + } else { + PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST); + pm_node_list_append(&node->rights, target); + } } else if (node->rest == NULL) { pm_node_list_append(&node->lefts, target); } else { @@ -7173,7 +7198,7 @@ lex_numeric(pm_parser_t *parser) { static pm_token_type_t lex_global_variable(pm_parser_t *parser) { if (parser->current.end >= parser->end) { - pm_parser_err_current(parser, PM_ERR_INVALID_VARIABLE_GLOBAL); + PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_ERR_INVALID_VARIABLE_GLOBAL); return PM_TOKEN_GLOBAL_VARIABLE; } @@ -7214,7 +7239,7 @@ lex_global_variable(pm_parser_t *parser) { } while (parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0); // $0 isn't allowed to be followed by anything. - pm_parser_err_current(parser, PM_ERR_INVALID_VARIABLE_GLOBAL); + PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_ERR_INVALID_VARIABLE_GLOBAL); } return PM_TOKEN_GLOBAL_VARIABLE; @@ -7245,7 +7270,7 @@ lex_global_variable(pm_parser_t *parser) { } else { // If we get here, then we have a $ followed by something that isn't // recognized as a global variable. - pm_parser_err_current(parser, PM_ERR_INVALID_VARIABLE_GLOBAL); + PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->current, PM_ERR_INVALID_VARIABLE_GLOBAL); } return PM_TOKEN_GLOBAL_VARIABLE; @@ -8149,10 +8174,10 @@ lex_at_variable(pm_parser_t *parser) { while (parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0) { parser->current.end += width; } - } else if (type == PM_TOKEN_CLASS_VARIABLE) { - pm_parser_err_current(parser, PM_ERR_INCOMPLETE_VARIABLE_CLASS); } else { - pm_parser_err_current(parser, PM_ERR_INCOMPLETE_VARIABLE_INSTANCE); + pm_diagnostic_id_t diag_id = (type == PM_TOKEN_CLASS_VARIABLE) ? PM_ERR_INCOMPLETE_VARIABLE_CLASS : PM_ERR_INCOMPLETE_VARIABLE_INSTANCE; + size_t width = parser->encoding->char_width(parser->current.end, parser->end - parser->current.end); + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, diag_id, (int) ((parser->current.end + width) - parser->current.start), (const char *) parser->current.start); } // If we're lexing an embedded variable, then we need to pop back into the @@ -11055,7 +11080,7 @@ parse_target(pm_parser_t *parser, pm_node_t *target) { return target; case PM_BACK_REFERENCE_READ_NODE: case PM_NUMBERED_REFERENCE_READ_NODE: - pm_parser_err_node(parser, target, PM_ERR_WRITE_TARGET_READONLY); + PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, target, PM_ERR_WRITE_TARGET_READONLY); return target; case PM_GLOBAL_VARIABLE_READ_NODE: assert(sizeof(pm_global_variable_target_node_t) == sizeof(pm_global_variable_read_node_t)); @@ -11193,7 +11218,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod } case PM_BACK_REFERENCE_READ_NODE: case PM_NUMBERED_REFERENCE_READ_NODE: - pm_parser_err_node(parser, target, PM_ERR_WRITE_TARGET_READONLY); + PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, target, PM_ERR_WRITE_TARGET_READONLY); /* fallthrough */ case PM_GLOBAL_VARIABLE_READ_NODE: { pm_global_variable_write_node_t *node = pm_global_variable_write_node_create(parser, target, operator, value); @@ -11368,7 +11393,7 @@ parse_targets(pm_parser_t *parser, pm_node_t *first_target, pm_binding_power_t b pm_multi_target_node_targets_append(parser, result, target); } else if (!match1(parser, PM_TOKEN_EOF)) { // If we get here, then we have a trailing , in a multi target node. - // We'll set the implicit rest flag to indicate this. + // We'll add an implicit rest node to represent this. pm_node_t *rest = (pm_node_t *) pm_implicit_rest_node_create(parser, &parser->previous); pm_multi_target_node_targets_append(parser, result, rest); break; @@ -11458,8 +11483,13 @@ parse_statements(pm_parser_t *parser, pm_context_t context) { while (accept2(parser, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON)); if (context_terminator(context, &parser->current)) break; - } else { - expect1(parser, PM_TOKEN_NEWLINE, PM_ERR_EXPECT_EOL_AFTER_STATEMENT); + } else if (!accept1(parser, PM_TOKEN_NEWLINE)) { + // This is an inlined version of accept1 because the error that we + // want to add has varargs. If this happens again, we should + // probably extract a helper function. + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_EXPECT_EOL_AFTER_STATEMENT, pm_token_type_human(parser->current.type)); + parser->previous.start = parser->previous.end; + parser->previous.type = PM_TOKEN_MISSING; } } @@ -13853,7 +13883,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_diagnostic_id_t diag_id) { pm_constant_id_t name_id = pm_parser_constant_id_constant(parser, "0it", 3); variable = (pm_node_t *) pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0); } else { - PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->previous, PM_ERR_NO_LOCAL_VARIABLE, (int) (parser->previous.end - parser->previous.start), parser->previous.start); + PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, parser->previous, PM_ERR_NO_LOCAL_VARIABLE); variable = (pm_node_t *) pm_local_variable_read_node_create(parser, &parser->previous, 0); } } @@ -14162,7 +14192,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current) { parser_lex(parser); if (match2(parser, PM_TOKEN_STRING_END, PM_TOKEN_EOF)) { - expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_TERM); + expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_EOF); // If we get here, then we have an end immediately after a // start. In that case we'll create an empty content token and // return an uninterpolated string. @@ -14219,15 +14249,19 @@ parse_strings(pm_parser_t *parser, pm_node_t *current) { parser_lex(parser); } while (match1(parser, PM_TOKEN_STRING_CONTENT)); - expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_TERM); + expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_EOF); node = (pm_node_t *) pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous); } else if (accept1(parser, PM_TOKEN_LABEL_END) && !state_is_arg_labeled) { node = (pm_node_t *) pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &unescaped)); } else if (match1(parser, PM_TOKEN_EOF)) { - pm_parser_err_token(parser, &opening, PM_ERR_STRING_LITERAL_TERM); + pm_parser_err_token(parser, &opening, PM_ERR_STRING_LITERAL_EOF); node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->current, &unescaped); + } else if (accept1(parser, PM_TOKEN_STRING_END)) { + node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped); } else { - expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_TERM); + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->previous, PM_ERR_STRING_LITERAL_TERM, pm_token_type_human(parser->previous.type)); + parser->previous.start = parser->previous.end; + parser->previous.type = PM_TOKEN_MISSING; node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped); } } else if (match1(parser, PM_TOKEN_STRING_CONTENT)) { @@ -14242,7 +14276,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current) { if (match2(parser, PM_TOKEN_STRING_END, PM_TOKEN_EOF)) { node = (pm_node_t *) pm_string_node_create_unescaped(parser, &opening, &content, &parser->current, &unescaped); pm_node_flag_set(node, parse_unescaped_encoding(parser)); - expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_TERM); + expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_EOF); } else if (accept1(parser, PM_TOKEN_LABEL_END)) { node = (pm_node_t *) pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &unescaped)); } else { @@ -14517,7 +14551,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // If we didn't find a terminator and we didn't find a right // parenthesis, then this is a syntax error. if (!terminator_found) { - pm_parser_err(parser, parser->current.start, parser->current.start, PM_ERR_EXPECT_EOL_AFTER_STATEMENT); + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_EXPECT_EOL_AFTER_STATEMENT, pm_token_type_human(parser->current.type)); } // Parse each statement within the parentheses. @@ -14546,7 +14580,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } else if (match1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { break; } else { - pm_parser_err(parser, parser->current.start, parser->current.start, PM_ERR_EXPECT_EOL_AFTER_STATEMENT); + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_EXPECT_EOL_AFTER_STATEMENT, pm_token_type_human(parser->current.type)); } } @@ -16897,7 +16931,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t switch (PM_NODE_TYPE(node)) { case PM_BACK_REFERENCE_READ_NODE: case PM_NUMBERED_REFERENCE_READ_NODE: - pm_parser_err_node(parser, node, PM_ERR_WRITE_TARGET_READONLY); + PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, node, PM_ERR_WRITE_TARGET_READONLY); /* fallthrough */ case PM_GLOBAL_VARIABLE_READ_NODE: { parser_lex(parser); @@ -17008,7 +17042,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t switch (PM_NODE_TYPE(node)) { case PM_BACK_REFERENCE_READ_NODE: case PM_NUMBERED_REFERENCE_READ_NODE: - pm_parser_err_node(parser, node, PM_ERR_WRITE_TARGET_READONLY); + PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, node, PM_ERR_WRITE_TARGET_READONLY); /* fallthrough */ case PM_GLOBAL_VARIABLE_READ_NODE: { parser_lex(parser); @@ -17129,7 +17163,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t switch (PM_NODE_TYPE(node)) { case PM_BACK_REFERENCE_READ_NODE: case PM_NUMBERED_REFERENCE_READ_NODE: - pm_parser_err_node(parser, node, PM_ERR_WRITE_TARGET_READONLY); + PM_PARSER_ERR_NODE_FORMAT_CONTENT(parser, node, PM_ERR_WRITE_TARGET_READONLY); /* fallthrough */ case PM_GLOBAL_VARIABLE_READ_NODE: { parser_lex(parser); diff --git a/prism/templates/src/token_type.c.erb b/prism/templates/src/token_type.c.erb index 99f5d1b254..ebcbb5e051 100644 --- a/prism/templates/src/token_type.c.erb +++ b/prism/templates/src/token_type.c.erb @@ -138,7 +138,7 @@ pm_token_type_human(pm_token_type_t token_type) { case PM_TOKEN_HEREDOC_START: return "heredoc beginning"; case PM_TOKEN_IDENTIFIER: - return "local variable or method identifier"; + return "local variable or method"; case PM_TOKEN_IGNORED_NEWLINE: return "ignored newline"; case PM_TOKEN_INSTANCE_VARIABLE: @@ -248,7 +248,7 @@ pm_token_type_human(pm_token_type_t token_type) { case PM_TOKEN_LABEL: return "label"; case PM_TOKEN_LABEL_END: - return "':'"; + return "label terminator"; case PM_TOKEN_LAMBDA_BEGIN: return "'{'"; case PM_TOKEN_LESS: diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index f7231b6a44..b37a6e1d26 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -9,7 +9,7 @@ module Prism def test_constant_path_with_invalid_token_after assert_error_messages "A::$b", [ "expected a constant after the `::` operator", - "expected a newline or semicolon after the statement" + "unexpected global variable, expecting end-of-input" ] end @@ -151,7 +151,7 @@ module Prism def test_unterminated_interpolated_string expr = expression('"hello') assert_errors expr, '"hello', [ - ["expected a closing delimiter for the string literal", 6..6] + ["unterminated string meets end of file", 6..6] ] assert_equal expr.unescaped, "hello" assert_equal expr.closing, "" @@ -160,7 +160,7 @@ module Prism def test_unterminated_string expr = expression("'hello") assert_errors expr, "'hello", [ - ["expected a closing delimiter for the string literal", 0..1] + ["unterminated string meets end of file", 0..1] ] assert_equal expr.unescaped, "hello" assert_equal expr.closing, "" @@ -169,7 +169,7 @@ module Prism def test_unterminated_empty_string expr = expression('"') assert_errors expr, '"', [ - ["expected a closing delimiter for the string literal", 1..1] + ["unterminated string meets end of file", 1..1] ] assert_equal expr.unescaped, "" assert_equal expr.closing, "" @@ -177,8 +177,8 @@ module Prism def test_incomplete_instance_var_string assert_errors expression('%@#@@#'), '%@#@@#', [ - ["incomplete instance variable", 4..5], - ["expected a newline or semicolon after the statement", 4..4] + ["`@#' is not allowed as an instance variable name", 4..5], + ["unexpected instance variable, expecting end-of-input", 4..5] ] end @@ -190,7 +190,7 @@ module Prism def test_unterminated_parenthesized_expression assert_errors expression('(1 + 2'), '(1 + 2', [ - ["expected a newline or semicolon after the statement", 6..6], + ["unexpected end of file, expecting end-of-input", 6..6], ["unexpected end of file, assuming it is closing the parent top level context", 6..6], ["expected a matching `)`", 6..6] ] @@ -198,7 +198,7 @@ module Prism def test_missing_terminator_in_parentheses assert_error_messages "(0 0)", [ - "expected a newline or semicolon after the statement" + "unexpected integer, expecting end-of-input" ] end @@ -224,24 +224,24 @@ module Prism def test_1_2_3 assert_errors expression("(1, 2, 3)"), "(1, 2, 3)", [ - ["expected a newline or semicolon after the statement", 2..2], + ["unexpected ',', expecting end-of-input", 2..3], ["unexpected ',', ignoring it", 2..3], ["expected a matching `)`", 2..2], - ["expected a newline or semicolon after the statement", 2..2], + ["unexpected ',', expecting end-of-input", 2..3], ["unexpected ',', ignoring it", 2..3], - ["expected a newline or semicolon after the statement", 5..5], + ["unexpected ',', expecting end-of-input", 5..6], ["unexpected ',', ignoring it", 5..6], - ["expected a newline or semicolon after the statement", 8..8], + ["unexpected ')', expecting end-of-input", 8..9], ["unexpected ')', ignoring it", 8..9] ] end def test_return_1_2_3 assert_error_messages "return(1, 2, 3)", [ - "expected a newline or semicolon after the statement", + "unexpected ',', expecting end-of-input", "unexpected ',', ignoring it", "expected a matching `)`", - "expected a newline or semicolon after the statement", + "unexpected ')', expecting end-of-input", "unexpected ')', ignoring it" ] end @@ -254,10 +254,10 @@ module Prism def test_next_1_2_3 assert_errors expression("next(1, 2, 3)"), "next(1, 2, 3)", [ - ["expected a newline or semicolon after the statement", 6..6], + ["unexpected ',', expecting end-of-input", 6..7], ["unexpected ',', ignoring it", 6..7], ["expected a matching `)`", 6..6], - ["expected a newline or semicolon after the statement", 12..12], + ["unexpected ')', expecting end-of-input", 12..13], ["unexpected ')', ignoring it", 12..13] ] end @@ -270,10 +270,10 @@ module Prism def test_break_1_2_3 assert_errors expression("break(1, 2, 3)"), "break(1, 2, 3)", [ - ["expected a newline or semicolon after the statement", 7..7], + ["unexpected ',', expecting end-of-input", 7..8], ["unexpected ',', ignoring it", 7..8], ["expected a matching `)`", 7..7], - ["expected a newline or semicolon after the statement", 13..13], + ["unexpected ')', expecting end-of-input", 13..14], ["unexpected ')', ignoring it", 13..14] ] end @@ -300,14 +300,14 @@ module Prism def test_top_level_constant_with_downcased_identifier assert_error_messages "::foo", [ "expected a constant after the `::` operator", - "expected a newline or semicolon after the statement" + "unexpected local variable or method, expecting end-of-input" ] end def test_top_level_constant_starting_with_downcased_identifier assert_error_messages "::foo::A", [ "expected a constant after the `::` operator", - "expected a newline or semicolon after the statement" + "unexpected local variable or method, expecting end-of-input" ] end @@ -354,7 +354,7 @@ module Prism def test_block_beginning_with_brace_and_ending_with_end assert_error_messages "x.each { x end", [ - "expected a newline or semicolon after the statement", + "unexpected 'end', expecting end-of-input", "unexpected 'end', ignoring it", "unexpected end of file, assuming it is closing the parent top level context", "expected a block beginning with `{` to end with `}`" @@ -403,7 +403,7 @@ module Prism def test_arguments_binding_power_for_and assert_error_messages "foo(*bar and baz)", [ "expected a `)` to close the arguments", - "expected a newline or semicolon after the statement", + "unexpected ')', expecting end-of-input", "unexpected ')', ignoring it" ] end @@ -1124,8 +1124,8 @@ module Prism ) assert_errors expected, "begin\n$+ = nil\n$1466 = nil\nend", [ - ["immutable variable as a write target", 6..8], - ["immutable variable as a write target", 15..20] + ["Can't set variable $+", 6..8], + ["Can't set variable $1466", 15..20] ] end @@ -1258,20 +1258,19 @@ module Prism def test_unterminated_global_variable assert_errors expression("$"), "$", [ - ["invalid global variable", 0..1] + ["`$' is not allowed as a global variable name", 0..1] ] end def test_invalid_global_variable_write assert_errors expression("$',"), "$',", [ - ["immutable variable as a write target", 0..2], + ["Can't set variable $'", 0..2], ["unexpected write target", 0..2] ] end def test_invalid_multi_target error_messages = ["unexpected write target"] - immutable = "immutable variable as a write target" assert_error_messages "foo,", error_messages assert_error_messages "foo = 1; foo,", error_messages @@ -1280,8 +1279,8 @@ module Prism assert_error_messages "@foo,", error_messages assert_error_messages "@@foo,", error_messages assert_error_messages "$foo,", error_messages - assert_error_messages "$1,", [immutable, *error_messages] - assert_error_messages "$+,", [immutable, *error_messages] + assert_error_messages "$1,", ["Can't set variable $1", *error_messages] + assert_error_messages "$+,", ["Can't set variable $+", *error_messages] assert_error_messages "Foo,", error_messages assert_error_messages "::Foo,", error_messages assert_error_messages "Foo::Foo,", error_messages @@ -1471,9 +1470,10 @@ module Prism def test_shadow_args_in_lambda source = "->a;b{}" + assert_errors expression(source), source, [ ["expected a `do` keyword or a `{` to open the lambda block", 3..3], - ["expected a newline or semicolon after the statement", 7..7], + ["unexpected end of file, expecting end-of-input", 7..7], ["unexpected end of file, assuming it is closing the parent top level context", 7..7], ["expected a lambda block beginning with `do` to end with `end`", 7..7] ] @@ -1517,14 +1517,14 @@ module Prism def test_symbol_in_keyword_parameter source = "def foo(x:'y':); end" assert_errors expression(source), source, [ - ["expected a closing delimiter for the string literal", 14..14], + ["unexpected label terminator, expected a string literal terminator", 12..14] ] end def test_symbol_in_hash source = "{x:'y':}" assert_errors expression(source), source, [ - ["expected a closing delimiter for the string literal", 7..7], + ["unexpected label terminator, expected a string literal terminator", 5..7] ] end @@ -1545,19 +1545,19 @@ module Prism RUBY assert_errors expression(source), source, [ - ["expected a newline or semicolon after the statement", 6..6], + ["unexpected '+', expecting end-of-input", 7..8], ["unexpected '+', ignoring it", 7..8], - ["expected a newline or semicolon after the statement", 17..17], + ["unexpected '+', expecting end-of-input", 18..19], ["unexpected '+', ignoring it", 18..19] ] end def test_rational_number_with_exponential_portion source = '1e1r; 1e1ri' - message = 'expected a newline or semicolon after the statement' + assert_errors expression(source), source, [ - [message, 3..3], - [message, 9..9] + ["unexpected local variable or method, expecting end-of-input", 3..4], + ["unexpected local variable or method, expecting end-of-input", 9..11] ] end @@ -1845,7 +1845,7 @@ module Prism source = '1....2' assert_errors expression(source), source, [ - ["expected a newline or semicolon after the statement", 4..4], + ["unexpected '.', expecting end-of-input", 4..5], ["unexpected '.', ignoring it", 4..5] ] end @@ -1879,21 +1879,21 @@ module Prism RUBY assert_errors expression(source), source, [ - ["expected a newline or semicolon after the statement", 9..9], + ["unexpected '+', expecting end-of-input", 10..11], ["unexpected '+', ignoring it", 10..11], - ["expected a newline or semicolon after the statement", 23..23], + ["unexpected '.', expecting end-of-input", 23..24], ["unexpected '.', ignoring it", 23..24], - ["expected a newline or semicolon after the statement", 39..39], + ["unexpected '+', expecting end-of-input", 40..41], ["unexpected '+', ignoring it", 40..41], - ["expected a newline or semicolon after the statement", 57..57], + ["unexpected '.', expecting end-of-input", 57..58], ["unexpected '.', ignoring it", 57..58], - ["expected a newline or semicolon after the statement", 71..71], + ["unexpected '+', expecting end-of-input", 72..73], ["unexpected '+', ignoring it", 72..73], - ["expected a newline or semicolon after the statement", 87..87], + ["unexpected '.', expecting end-of-input", 87..88], ["unexpected '.', ignoring it", 87..88], - ["expected a newline or semicolon after the statement", 97..97], + ["unexpected '+', expecting end-of-input", 98..99], ["unexpected '+', ignoring it", 98..99], - ["expected a newline or semicolon after the statement", 109..109], + ["unexpected '.', expecting end-of-input", 109..110], ["unexpected '.', ignoring it", 109..110] ] end @@ -1920,9 +1920,9 @@ module Prism RUBY assert_errors expression(source), source, [ - ["expected a newline or semicolon after the statement", 3..3], + ["unexpected '..', expecting end-of-input", 3..5], ["unexpected '..', ignoring it", 3..5], - ["expected a newline or semicolon after the statement", 10..10], + ["unexpected '..', expecting end-of-input", 10..12], ["unexpected '..', ignoring it", 10..12] ] end @@ -2004,13 +2004,12 @@ module Prism foo 1 in a a = foo 2 in b RUBY - message1 = 'unexpected `in` keyword in arguments' - message2 = 'expected a newline or semicolon after the statement' + assert_errors expression(source), source, [ - [message1, 9..10], - [message2, 8..8], - [message1, 24..25], - [message2, 23..23], + ["unexpected `in` keyword in arguments", 9..10], + ["unexpected local variable or method, expecting end-of-input", 9..10], + ["unexpected `in` keyword in arguments", 24..25], + ["unexpected local variable or method, expecting end-of-input", 24..25] ] end @@ -2032,17 +2031,17 @@ module Prism RUBY assert_errors expression(source), source, [ - ["expected a newline or semicolon after the statement", 6..6], + ["unexpected '==', expecting end-of-input", 7..9], ["unexpected '==', ignoring it", 7..9], - ["expected a newline or semicolon after the statement", 18..18], + ["unexpected '!=', expecting end-of-input", 19..21], ["unexpected '!=', ignoring it", 19..21], - ["expected a newline or semicolon after the statement", 31..31], + ["unexpected '===', expecting end-of-input", 32..35], ["unexpected '===', ignoring it", 32..35], - ["expected a newline or semicolon after the statement", 44..44], + ["unexpected '=~', expecting end-of-input", 45..47], ["unexpected '=~', ignoring it", 45..47], - ["expected a newline or semicolon after the statement", 56..56], + ["unexpected '!~', expecting end-of-input", 57..59], ["unexpected '!~', ignoring it", 57..59], - ["expected a newline or semicolon after the statement", 69..69], + ["unexpected '<=>', expecting end-of-input", 70..73], ["unexpected '<=>', ignoring it", 70..73] ] end diff --git a/test/prism/format_errors_test.rb b/test/prism/format_errors_test.rb index a142e8eee1..79dac3d927 100644 --- a/test/prism/format_errors_test.rb +++ b/test/prism/format_errors_test.rb @@ -15,9 +15,9 @@ module Prism assert_equal <<~'ERROR', Debug.format_errors('"%W"\u"', false) > 1 | "%W"\u" - | ^ expected a newline or semicolon after the statement | ^ invalid character `\` - | ^ expected a closing delimiter for the string literal + | ^ unexpected local variable or method, expecting end-of-input + | ^ unterminated string meets end of file ERROR end end diff --git a/test/prism/parser_test.rb b/test/prism/parser_test.rb index e13ee39c6d..853ac536ee 100644 --- a/test/prism/parser_test.rb +++ b/test/prism/parser_test.rb @@ -3,11 +3,14 @@ require_relative "test_helper" begin + verbose, $VERBOSE = $VERBOSE, nil require "parser/current" rescue LoadError # In CRuby's CI, we're not going to test against the parser gem because we # don't want to have to install it. So in this case we'll just skip this test. return +ensure + $VERBOSE = verbose end # First, opt in to every AST feature.