validation: Improve continue-bypasses-decl message

Attach the error to the continue statement, and add notes to show
where the variable is both declared and used.

Change-Id: Ie9939a5ca674e7216069bbb1d8dc82ab6949367c
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/65521
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
James Price 2021-09-30 17:29:50 +00:00
parent 7166f6ba93
commit 5ffae32177
4 changed files with 80 additions and 45 deletions

View File

@ -2043,15 +2043,15 @@ bool Resolver::Statement(ast::Statement* stmt) {
} }
return true; return true;
} }
if (stmt->Is<ast::ContinueStatement>()) { if (auto* c = stmt->As<ast::ContinueStatement>()) {
// Set if we've hit the first continue statement in our parent loop // Set if we've hit the first continue statement in our parent loop
if (auto* block = if (auto* block =
current_block_->FindFirstParent< current_block_->FindFirstParent<
sem::LoopBlockStatement, sem::LoopContinuingBlockStatement>()) { sem::LoopBlockStatement, sem::LoopContinuingBlockStatement>()) {
if (auto* loop_block = block->As<sem::LoopBlockStatement>()) { if (auto* loop_block = block->As<sem::LoopBlockStatement>()) {
if (loop_block->FirstContinue() == size_t(~0)) { if (!loop_block->FirstContinue()) {
const_cast<sem::LoopBlockStatement*>(loop_block) const_cast<sem::LoopBlockStatement*>(loop_block)
->SetFirstContinue(loop_block->Decls().size()); ->SetFirstContinue(c, loop_block->Decls().size());
} }
} else { } else {
AddError("continuing blocks must not contain a continue statement", AddError("continuing blocks must not contain a continue statement",
@ -2980,7 +2980,7 @@ bool Resolver::Identifier(ast::IdentifierExpression* expr) {
->FindFirstParent<sem::LoopContinuingBlockStatement>()) { ->FindFirstParent<sem::LoopContinuingBlockStatement>()) {
auto* loop_block = auto* loop_block =
continuing_block->FindFirstParent<sem::LoopBlockStatement>(); continuing_block->FindFirstParent<sem::LoopBlockStatement>();
if (loop_block->FirstContinue() != size_t(~0)) { if (loop_block->FirstContinue()) {
auto& decls = loop_block->Decls(); auto& decls = loop_block->Decls();
// If our identifier is in loop_block->decls, make sure its index is // If our identifier is in loop_block->decls, make sure its index is
// less than first_continue // less than first_continue
@ -2990,11 +2990,16 @@ bool Resolver::Identifier(ast::IdentifierExpression* expr) {
if (iter != decls.end()) { if (iter != decls.end()) {
auto var_decl_index = auto var_decl_index =
static_cast<size_t>(std::distance(decls.begin(), iter)); static_cast<size_t>(std::distance(decls.begin(), iter));
if (var_decl_index >= loop_block->FirstContinue()) { if (var_decl_index >= loop_block->NumDeclsAtFirstContinue()) {
AddError("continue statement bypasses declaration of '" + AddError("continue statement bypasses declaration of '" +
builder_->Symbols().NameFor(symbol) + builder_->Symbols().NameFor(symbol) + "'",
"' in continuing block", loop_block->FirstContinue()->source());
expr->source()); AddNote("identifier '" + builder_->Symbols().NameFor(symbol) +
"' declared here",
(*iter)->source());
AddNote("identifier '" + builder_->Symbols().NameFor(symbol) +
"' referenced in continuing block here",
expr->source());
return false; return false;
} }
} }

View File

@ -572,17 +572,21 @@ TEST_F(ResolverValidationTest,
// } // }
// } // }
auto error_loc = Source{Source::Location{12, 34}}; auto cont_loc = Source{Source::Location{12, 34}};
auto* body = Block(If(Expr(true), Block(create<ast::ContinueStatement>())), auto decl_loc = Source{Source::Location{56, 78}};
Decl(Var("z", ty.i32(), ast::StorageClass::kNone))); auto ref_loc = Source{Source::Location{90, 12}};
auto* continuing = Block(Assign(Expr(error_loc, "z"), 2)); auto* body =
Block(If(Expr(true), Block(create<ast::ContinueStatement>(cont_loc))),
Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
auto* continuing = Block(Assign(Expr(ref_loc, "z"), 2));
auto* loop_stmt = Loop(body, continuing); auto* loop_stmt = Loop(body, continuing);
WrapInFunction(loop_stmt); WrapInFunction(loop_stmt);
EXPECT_FALSE(r()->Resolve()) << r()->error(); EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:34 error: continue statement bypasses declaration of 'z' in " R"(12:34 error: continue statement bypasses declaration of 'z'
"continuing block"); 56:78 note: identifier 'z' declared here
90:12 note: identifier 'z' referenced in continuing block here)");
} }
TEST_F( TEST_F(
@ -600,19 +604,23 @@ TEST_F(
// } // }
// } // }
auto error_loc = Source{Source::Location{12, 34}}; auto cont_loc = Source{Source::Location{12, 34}};
auto* body = Block(If(Expr(true), Block(create<ast::ContinueStatement>())), auto decl_loc = Source{Source::Location{56, 78}};
Decl(Var("z", ty.i32(), ast::StorageClass::kNone))); auto ref_loc = Source{Source::Location{90, 12}};
auto* body =
Block(If(Expr(true), Block(create<ast::ContinueStatement>(cont_loc))),
Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
auto* continuing = auto* continuing =
Block(If(Expr(true), Block(Assign(Expr(error_loc, "z"), 2)))); Block(If(Expr(true), Block(Assign(Expr(ref_loc, "z"), 2))));
auto* loop_stmt = Loop(body, continuing); auto* loop_stmt = Loop(body, continuing);
WrapInFunction(loop_stmt); WrapInFunction(loop_stmt);
EXPECT_FALSE(r()->Resolve()) << r()->error(); EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:34 error: continue statement bypasses declaration of 'z' in " R"(12:34 error: continue statement bypasses declaration of 'z'
"continuing block"); 56:78 note: identifier 'z' declared here
90:12 note: identifier 'z' referenced in continuing block here)");
} }
TEST_F(ResolverValidationTest, TEST_F(ResolverValidationTest,
@ -630,19 +638,23 @@ TEST_F(ResolverValidationTest,
// } // }
// } // }
auto error_loc = Source{Source::Location{12, 34}}; auto cont_loc = Source{Source::Location{12, 34}};
auto* body = Block(If(Expr(true), Block(create<ast::ContinueStatement>())), auto decl_loc = Source{Source::Location{56, 78}};
Decl(Var("z", ty.i32(), ast::StorageClass::kNone))); auto ref_loc = Source{Source::Location{90, 12}};
auto* body =
Block(If(Expr(true), Block(create<ast::ContinueStatement>(cont_loc))),
Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
auto* compare = create<ast::BinaryExpression>(ast::BinaryOp::kLessThan, auto* compare = create<ast::BinaryExpression>(ast::BinaryOp::kLessThan,
Expr(error_loc, "z"), Expr(2)); Expr(ref_loc, "z"), Expr(2));
auto* continuing = Block(If(compare, Block())); auto* continuing = Block(If(compare, Block()));
auto* loop_stmt = Loop(body, continuing); auto* loop_stmt = Loop(body, continuing);
WrapInFunction(loop_stmt); WrapInFunction(loop_stmt);
EXPECT_FALSE(r()->Resolve()) << r()->error(); EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:34 error: continue statement bypasses declaration of 'z' in " R"(12:34 error: continue statement bypasses declaration of 'z'
"continuing block"); 56:78 note: identifier 'z' declared here
90:12 note: identifier 'z' referenced in continuing block here)");
} }
TEST_F(ResolverValidationTest, TEST_F(ResolverValidationTest,
@ -659,18 +671,22 @@ TEST_F(ResolverValidationTest,
// } // }
// } // }
auto error_loc = Source{Source::Location{12, 34}}; auto cont_loc = Source{Source::Location{12, 34}};
auto* body = Block(If(Expr(true), Block(create<ast::ContinueStatement>())), auto decl_loc = Source{Source::Location{56, 78}};
Decl(Var("z", ty.i32(), ast::StorageClass::kNone))); auto ref_loc = Source{Source::Location{90, 12}};
auto* body =
Block(If(Expr(true), Block(create<ast::ContinueStatement>(cont_loc))),
Decl(Var(decl_loc, "z", ty.i32(), ast::StorageClass::kNone)));
auto* continuing = Block(Loop(Block(Assign(Expr(error_loc, "z"), 2)))); auto* continuing = Block(Loop(Block(Assign(Expr(ref_loc, "z"), 2))));
auto* loop_stmt = Loop(body, continuing); auto* loop_stmt = Loop(body, continuing);
WrapInFunction(loop_stmt); WrapInFunction(loop_stmt);
EXPECT_FALSE(r()->Resolve()) << r()->error(); EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:34 error: continue statement bypasses declaration of 'z' in " R"(12:34 error: continue statement bypasses declaration of 'z'
"continuing block"); 56:78 note: identifier 'z' declared here
90:12 note: identifier 'z' referenced in continuing block here)");
} }
TEST_F(ResolverValidationTest, TEST_F(ResolverValidationTest,

View File

@ -51,8 +51,11 @@ LoopBlockStatement::LoopBlockStatement(const ast::BlockStatement* declaration,
} }
LoopBlockStatement::~LoopBlockStatement() = default; LoopBlockStatement::~LoopBlockStatement() = default;
void LoopBlockStatement::SetFirstContinue(size_t first_continue) { void LoopBlockStatement::SetFirstContinue(
const ast::ContinueStatement* first_continue,
size_t num_decls) {
first_continue_ = first_continue; first_continue_ = first_continue;
num_decls_at_first_continue_ = num_decls;
} }
} // namespace sem } // namespace sem

View File

@ -24,6 +24,7 @@
namespace tint { namespace tint {
namespace ast { namespace ast {
class BlockStatement; class BlockStatement;
class ContinueStatement;
class Function; class Function;
class Variable; class Variable;
} // namespace ast } // namespace ast
@ -90,21 +91,31 @@ class LoopBlockStatement : public Castable<LoopBlockStatement, BlockStatement> {
/// Destructor /// Destructor
~LoopBlockStatement() override; ~LoopBlockStatement() override;
/// @returns the index of the first variable declared after the first continue /// @returns the first continue statement in this loop block, or nullptr if
/// statement /// there are no continue statements in the block
size_t FirstContinue() const { return first_continue_; } const ast::ContinueStatement* FirstContinue() const {
return first_continue_;
}
/// Requires that this is a loop block. /// @returns the number of variables declared before the first continue
/// Allows the resolver to set the index of the first variable declared after /// statement
/// the first continue statement. size_t NumDeclsAtFirstContinue() const {
/// @param first_continue index of the relevant variable return num_decls_at_first_continue_;
void SetFirstContinue(size_t first_continue); }
/// Allows the resolver to record the first continue statement in the block
/// and the number of variables declared prior to that statement.
/// @param first_continue the first continue statement in the block
/// @param num_decls the number of variable declarations before that continue
void SetFirstContinue(const ast::ContinueStatement* first_continue,
size_t num_decls);
private: private:
// first_continue is set to the index of the first variable in decls /// The first continue statement in this loop block.
// declared after the first continue statement in a loop block, if any. const ast::ContinueStatement* first_continue_ = nullptr;
constexpr static size_t kNoContinue = size_t(~0);
size_t first_continue_ = kNoContinue; /// The number of variables declared before the first continue statement.
size_t num_decls_at_first_continue_ = 0;
}; };
} // namespace sem } // namespace sem