8000 Recover from unclosed block args pipe by jez · Pull Request #5284 · sorbet/sorbet · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Recover from unclosed block args pipe #5284

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

Merged
merged 9 commits into from
Feb 10, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions parser/Builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,11 @@ class Builder::Impl {
}

if (driver_->lex.is_declared(name_str)) {
checkCircularArgumentReferences(node.get(), name_str);
return make_unique<LVar>(node->loc, id->name);
if (!hasCircularArgumentReferences(node.get(), name_str)) {
return make_unique<LVar>(node->loc, id->name);
} else {
return error_node(node->loc.beginPos(), node->loc.endPos());
}
} else {
return make_unique<Send>(node->loc, nullptr, id->name, node->loc, sorbet::parser::NodeVec());
}
Expand Down Expand Up @@ -618,6 +621,19 @@ class Builder::Impl {
std::move(elseBody));
}

unique_ptr<Node> case_error(const token *case_, unique_ptr<Node> cond, const token *end) {
auto loc = tokLoc(case_);
if (end != nullptr) {
loc = loc.join(tokLoc(end));
}
auto zloc = loc.copyWithZeroLength();
auto whenPatterns = NodeVec{};
whenPatterns.emplace_back(error_node(loc.beginPos(), loc.beginPos()));
auto whens = NodeVec{};
whens.emplace_back(make_unique<When>(zloc, std::move(whenPatterns), nullptr));
return make_unique<Case>(loc, std::move(cond), std::move(whens), nullptr);
}

unique_ptr<Node> case_match(const token *case_, unique_ptr<Node> expr, sorbet::parser::NodeVec inBodies,
const token *elseTok, unique_ptr<Node> elseBody, const token *end) {
if (elseTok != nullptr && elseBody == nullptr) {
Expand Down Expand Up @@ -1603,10 +1619,12 @@ class Builder::Impl {
return parser::isa_node<String>(firstPart) || parser::isa_node<DString>(firstPart);
}

void checkCircularArgumentReferences(const Node *node, std::string_view name) {
bool hasCircularArgumentReferences(const Node *node, std::string_view name) {
if (name == driver_->current_arg_stack.top()) {
error(ruby_parser::dclass::CircularArgumentReference, node->loc, std::string(name));
return true;
}
return false;
}

void checkDuplicateArgs(sorbet::parser::NodeVec &args, UnorderedMap<std::string, core::LocOffsets> &map) {
Expand Down Expand Up @@ -1869,6 +1887,11 @@ ForeignPtr case_(SelfPtr builder, const token *case_, ForeignPtr expr, const nod
build->cast_node(elseBody), end));
}

ForeignPtr case_error(SelfPtr builder, const token *case_, ForeignPtr cond, const token *end) {
auto build = cast_builder(builder);
return build->toForeign(build->case_error(case_, build->cast_node(cond), end));
}

ForeignPtr case_match(SelfPtr builder, const token *case_, ForeignPtr expr, const node_list *inBodies,
const token *elseTok, ForeignPtr elseBody, const token *end) {
auto build = cast_builder(builder);
Expand Down Expand Up @@ -2532,6 +2555,7 @@ struct ruby_parser::builder Builder::interface = {
call_method,
call_method_error,
case_,
case_error,
case_match,
character,
complex,
Expand Down
1 change: 1 addition & 0 deletions parser/Parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ core::Loc rangeToLoc(const core::GlobalState &gs, core::FileRef file, const ruby
core::ErrorClass dclassToErrorClass(ruby_parser::dclass diagClass) {
switch (diagClass) {
case ruby_parser::dclass::DedentedEnd:
case ruby_parser::dclass::BlockArgsUnexpectedNewline:
return core::errors::Parser::ErrorRecoveryHint;
default:
return core::errors::Parser::ParserError;
Expand Down
4 changes: 2 additions & 2 deletions parser/parser/cc/driver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ void base_driver::rewind_and_reset(size_t newPos) {
this->lex.rewind_and_reset_to_expr_end(newPos);
}

void base_driver::rewind_if_dedented(token_t token, token_t kEND) {
if (this->indentationAware && this->lex.compare_indent_level(token, kEND) < 0) {
void base_driver::rewind_if_dedented(token_t token, token_t kEND, bool force) {
if ((force || this->indentationAware) && this->lex.compare_indent_level(token, kEND) < 0) {
this->rewind_and_reset(kEND->start());
const char *token_str_name = this->token_name(token->type());
this->diagnostics.emplace_back(dlevel::ERROR, dclass::DedentedEnd, token, token_str_name, kEND);
Expand Down
67 changes: 64 additions & 3 deletions parser/parser/cc/grammars/typedruby.ypp
Original file line number Diff line number Diff line change
Expand Up @@ -496,9 +496,27 @@ int yylex(parser::semantic_type *lval, ruby_parser::location *lloc, ruby_parser:
lval->token = token;
lloc->begin = token->start();
lloc->end = token->end();
lloc->lineStart = token->lineStart();
return token_type;
}

#define YYLLOC_DEFAULT(Cur, Rhs, N) \
do \
if (N) \
{ \
(Cur).begin = YYRHSLOC(Rhs, 1).begin; \
(Cur).end = YYRHSLOC(Rhs, N).end; \
(Cur).lineStart = YYRHSLOC(Rhs, 1).lineStart; \
} \
else \
{ \
(Cur).begin = YYRHSLOC(Rhs, 0).begin; \
(Cur).end = YYRHSLOC(Rhs, 0).end; \
(Cur).lineStart = YYRHSLOC(Rhs, 0).lineStart; \
} \
while (0)


}}} // namespace
} // %code

Expand Down Expand Up @@ -1935,7 +1953,6 @@ lbrace_cmd_block_start:
delimited_block->args,
delimited_block->body,
delimited_block->end);
DIAGCHECK();
}
| method_call
| method_call brace_block
Expand Down Expand Up @@ -2029,6 +2046,7 @@ lbrace_cmd_block_start:
&case_body->whens,
else_ ? else_->tok : nullptr,
else_ ? else_->nod : nullptr, $5);
driver.rewind_if_dedented($1, $5);
}
| kCASE opt_terms case_body kEND
{
Expand All @@ -2038,15 +2056,40 @@ lbrace_cmd_block_start:
&case_body->whens,
else_ ? else_->tok : nullptr,
else_ ? else_->nod : nullptr, $4);
driver.rewind_if_dedented($1, $4);
}
| kCASE expr_value opt_terms kEND
{
$$ = driver.build.case_error(self, $1, $2, $4);
driver.diagnostics.emplace_back(dlevel::ERROR, dclass::EmptyCase, $1, driver.token_name($1->type()));
// This is an `error` rule in disguise, so we can actually use indentation to rewind
// even if we aren't in indentationAware mode.
driver.rewind_if_dedented($1, $4, /*force*/true);
}
| kCASE opt_terms kEND
{
auto cond = driver.build.error_node(self, @1.end, @3.begin);
$$ = driver.build.case_error(self, $1, cond, $3);
driver.diagnostics.emplace_back(dlevel::ERROR, dclass::EmptyCase, $1, driver.token_name($1->type()));
// This is an `error` rule in disguise, so we can actually use indentation to rewind
// even if we aren't in indentationAware mode.
driver.rewind_if_dedented($1, $3, /*force*/true);
}
| kCASE error
{
auto cond = driver.build.error_node(self, @1.end, @2.end);
$$ = driver.build.case_error(self, $1, cond, nullptr);
driver.replace_last_diagnostic(dlevel::ERROR, dclass::UnexpectedToken, $1, driver.token_name($1->type()));
}
| kCASE expr_value opt_terms p_case_body kEND
| kCASE expr_value opt_terms p_case_body kEND
{
auto &case_body = $4;
auto &else_ = case_body->els;
$$ = driver.build.case_match(self, $1, $2,
&case_body->whens,
else_ ? else_->tok : nullptr,
else_ ? else_->nod : nullptr, $5);
driver.rewind_if_dedented($1, $5);
}
| kFOR for_var kIN expr_value_do compstmt kEND
{
Expand Down Expand Up @@ -2397,6 +2440,11 @@ opt_block_args_tail:
$$ = args;
}
| block_args_tail
| tBDOT3
{
$$ = driver.alloc.node_list();
driver.diagnostics.emplace_back(dlevel::ERROR, dclass::UnexpectedToken, $1, driver.token_name($1->type()));
}

opt_block_param: // nothing
{
Expand All @@ -2422,7 +2470,20 @@ opt_block_args_tail:
params->concat($3);
driver.current_arg_stack.set("");
$$ = driver.build.args(self, $1, params, $4, true);
DIAGCHECK();
}
| tPIPE error
{
driver.numparam_stack.set_ordinary_params();
driver.current_arg_stack.set("");
$$ = driver.build.args(self, nullptr, nullptr, nullptr, true);
if (driver.indentationAware && @1.lineStart < yyla.location.lineStart) {
auto newline_s = yyla.location.lineStart - 1;
driver.rewind_and_reset(newline_s);
driver.replace_last_diagnostic(dlevel::NOTE, dclass::BlockArgsUnexpectedNewline, diagnostic::range(newline_s, newline_s));
} else {
driver.rewind_and_reset(@1.end);
driver.replace_last_diagnostic(dlevel::ERROR, dclass::UnmatchedBlockArgs, $1);
}
}

opt_bv_decl: opt_nl
Expand Down
3 changes: 3 additions & 0 deletions parser/parser/codegen/generate_diagnostics.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,16 @@ tuple<string, string> MESSAGES[] = {
{"PatternDuplicateVariable", "duplicate variable name {}"},
{"PatternDuplicateKey", "duplicate hash pattern key {}"},
{"PositionalAfterKeyword", "positional arg \\\"{}\\\" after keyword arg"},
{"UnmatchedBlockArgs", "unmatched \\\"|\\\" in block argument list"},
{"IfInsteadOfItForTest", "Unexpected token \\\"if\\\"; did you mean \\\"it\\\"?"},
{"MissingCommaBetweenKwargs", "missing \\\",\\\" between keyword args"},
{"MissingOperatorArg", "missing arg to {} operator"},
{"CurlyBracesAroundBlockPass", "block pass should not be enclosed in curly braces"},
{"EmptyCase", "{} statement must at least have one \\\"when\\\" clause"},

// Error recovery hints
{"DedentedEnd", "Hint: this {} token might not be properly closed"},
{"BlockArgsUnexpectedNewline", "Hint: expected \\\"|\\\" token here"},

// Parser warnings
{"UselessElse", "else without rescue is useless"},
Expand Down
1 change: 1 addition & 0 deletions parser/parser/include/ruby_parser/builder.hh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct builder {
ForeignPtr (*call_method_error)(SelfPtr builder, ForeignPtr receiver, const token *dot);
ForeignPtr (*case_)(SelfPtr builder, const token *case_, ForeignPtr expr, const node_list *whenBodies,
const token *elseTok, ForeignPtr elseBody, const token *end);
ForeignPtr (*case_error)(SelfPtr builder, const token *case_, ForeignPtr cond, const token *end);
ForeignPtr (*case_match)(SelfPtr builder, const token *case_, ForeignPtr expr, const node_list *inBodies,
const token *elseTok, ForeignPtr elseBody, const token *end);
ForeignPtr (*character)(SelfPtr builder, const token *char_);
Expand Down
6 changes: 5 additions & 1 deletion parser/parser/include/ruby_parser/driver.hh
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,11 @@ public:
// consumed can be delayed until a later production rule.
//
// This helper essentially implements a crude form of backtracking.
void rewind_if_dedented(token_t token, token_t kEND);
//
// Use `force = true` to ignore checking indentationAware. This probably is only ok to use if
// you're implementing an "`error` rule in disguise" kind of rule (i.e., reducing tokens
// manually for the purpose of emitting an error).
void rewind_if_dedented(token_t token, token_t kEND, bool force = false);
};

class typedruby_release27 : public base_driver {
Expand Down
7 changes: 4 additions & 3 deletions parser/parser/include/ruby_parser/location.hh
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ namespace ruby_parser {
struct location {
size_t begin;
size_t end;
size_t lineStart;

location() : begin(SIZE_MAX), end(SIZE_MAX) {}
location(size_t begin, size_t end) : begin(begin), end(end) {}
location() : begin(SIZE_MAX), end(SIZE_MAX), lineStart(SIZE_MAX) {}
location(size_t begin, size_t end, size_t lineStart) : begin(begin), end(end), lineStart(lineStart) {}

location(const location &other) = default;
location &operator=(const location &other) = default;

bool exists() {
return begin != SIZE_MAX && end != SIZE_MAX;
return begin != SIZE_MAX && end != SIZE_MAX && lineStart != SIZE_MAX;
};
};

Expand Down
8 changes: 5 additions & 3 deletions test/helpers/position_assertions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -343,11 +343,16 @@ vector<shared_ptr<RangeAssertion>> parseAssertionsForFile(const shared_ptr<core:
lastSourceLineNum = lineNum;
}

string assertionType = matches[3].str();
string assertionContents = matches[4].str();

unique_ptr<Range> range;
if (numCarets > 0) {
int caretBeginPos = textBeforeComment.size() + matches[1].str().size();
int caretEndPos = caretBeginPos + numCarets;
range = RangeAssertion::makeRange(lastSourceLineNum, caretBeginPos, caretEndPos);
} else if (assertionContents == "unexpected token tNL") {
range = RangeAssertion::makeRange(lineNum);
} else {
range = RangeAssertion::makeRange(lastSourceLineNum);
}
Expand All @@ -358,9 +363,6 @@ vector<shared_ptr<RangeAssertion>> parseAssertionsForFile(const shared_ptr<core:
lastSourceLineNum = lineNum;
}

string assertionType = matches[3].str();
string assertionContents = matches[4].str();

const auto &findConstructor = assertionConstructors.find(assertionType);
if (findConstructor != assertionConstructors.end()) {
assertions.push_back(
Expand Down
5 changes: 4 additions & 1 deletion test/testdata/deviations/keyword_method_names.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ def method_named_case(x)
x
.case # ok
x.
case # error: unexpected token
case
# ^^^^ error: unexpected token "case"
# ^^^^ error: "case" statement must at least have one "when" clause
# ^^^^ error: Hint: this "case" token might not be properly closed
end

def method_named_class(x)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,52 @@ Begin {
}
]
}
body = NULL
body = Begin {
stmts = [
Send {
receiver = LVar {
name = <U x>
}
method = <U case>
args = [
]
}
Send {
receiver = LVar {
name = <U x>
}
method = <U case>
args = [
]
}
Send {
receiver = LVar {
name = <U x>
}
method = <U <method-name-missing>>
args = [
]
}
Case {
condition = Const {
scope = NULL
name = <C <U <ErrorNode>>>
}
whens = [
When {
patterns = [
Const {
scope = NULL
name = <C <U <ErrorNode>>>
}
]
body = NULL
}
]
else_ = NULL
}
]
}
}
DefMethod {
name = <U method_named_class>
Expand Down
40 changes: 40 additions & 0 deletions test/testdata/lsp/completion/case_1.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# typed: true

module Opus::Log
def self.info(msg); end
end

class MethodCompletion
def self.test1(x)
# TODO(jez) What's up with this completion result?
puts 'before'
case x # error: Hint: this "case" token might not be properly closed
when MethodCompletion.
# ^ completion: class, ...
puts 'hello' # error: unexpected token tSTRING
end

def self.test2(x)
puts 'before'
case x # error: Hint: this "case" token might not be properly closed
when MethodCompletion.
# ^ completion: test1, test2, test3, test4, ...
puts('hello')
end

def self.test3(x)
puts 'before'
case x # error: Hint: this "case" token might not be properly closed
when MethodCompletion. # error: Dynamic constant references are unsupported
# ^ completion: test1, test2, test3, test4, ...
Opus::Log.info('hello') # error: Method `Opus` does not exist on `T.class_of(MethodCompletion)`
end

def self.test4(x)
puts 'before'
case x # error: Hint: this "case" token might not be properly closed
when MethodCompletion.
# ^ completion: test1, test2, test3, test4, ...
y = nil # error: Method `y=` does not exist on `T.class_of(MethodCompletion)`
end
end # error: unexpected token "end of file"
Loading
0