resolver: Error when calling shadowed functions

The fix involves unifying the dependency logic of DependencyScanner, which also cleans up the code quite a bit.

Also:
* Correctly resolve parameter type before declaring the parameter variable.
* Fix bonkers typos in utils::Lookup.

Fixed: tint:1442
Change-Id: I77b548e148b461f87ec10e8272c398f6fee297bd
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/81102
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
Ben Clayton 2022-02-18 21:57:53 +00:00 committed by Tint LUCI CQ
parent 3fac602d6c
commit 911944cc8d
5 changed files with 60 additions and 46 deletions

View File

@ -265,6 +265,24 @@ TEST_F(ResolverCallValidationTest, CallVariable) {
note: 'v' declared here)"); note: 'v' declared here)");
} }
TEST_F(ResolverCallValidationTest, CallVariableShadowsFunction) {
// fn x() {}
// fn f() {
// var x : i32;
// x();
// }
Func("x", {}, ty.void_(), {});
Func("f", {}, ty.void_(),
{
Decl(Var(Source{{56, 78}}, "x", ty.i32())),
CallStmt(Call(Source{{12, 34}}, "x")),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: cannot call variable 'x'
56:78 note: 'x' declared here)");
}
} // namespace } // namespace
} // namespace resolver } // namespace resolver
} // namespace tint } // namespace tint

View File

@ -173,6 +173,16 @@ class DependencyScanner {
/// Traverses the function, performing symbol resolution and determining /// Traverses the function, performing symbol resolution and determining
/// global dependencies. /// global dependencies.
void TraverseFunction(const ast::Function* func) { void TraverseFunction(const ast::Function* func) {
// Perform symbol resolution on all the parameter types before registering
// the parameters themselves. This allows the case of declaring a parameter
// with the same identifier as its type.
for (auto* param : func->params) {
TraverseType(param->type);
}
// Resolve the return type
TraverseType(func->return_type);
// Push the scope stack for the parameters and function body.
scope_stack_.Push(); scope_stack_.Push();
TINT_DEFER(scope_stack_.Pop()); TINT_DEFER(scope_stack_.Pop());
@ -181,12 +191,10 @@ class DependencyScanner {
graph_.shadows.emplace(param, shadows); graph_.shadows.emplace(param, shadows);
} }
Declare(param->symbol, param); Declare(param->symbol, param);
TraverseType(param->type);
} }
if (func->body) { if (func->body) {
TraverseStatements(func->body->statements); TraverseStatements(func->body->statements);
} }
TraverseType(func->return_type);
} }
/// Traverses the statements, performing symbol resolution and determining /// Traverses the statements, performing symbol resolution and determining
@ -295,38 +303,15 @@ class DependencyScanner {
ast::TraverseExpressions( ast::TraverseExpressions(
root, diagnostics_, [&](const ast::Expression* expr) { root, diagnostics_, [&](const ast::Expression* expr) {
if (auto* ident = expr->As<ast::IdentifierExpression>()) { if (auto* ident = expr->As<ast::IdentifierExpression>()) {
auto* node = scope_stack_.Get(ident->symbol); AddDependency(ident, ident->symbol, "identifier", "references");
if (node == nullptr) {
if (!IsBuiltin(ident->symbol)) {
UnknownSymbol(ident->symbol, ident->source, "identifier");
}
return ast::TraverseAction::Descend;
}
auto global_it = globals_.find(ident->symbol);
if (global_it != globals_.end() &&
node == global_it->second->node) {
AddGlobalDependency(ident, ident->symbol, "identifier",
"references");
} else {
graph_.resolved_symbols.emplace(ident, node);
}
} }
if (auto* call = expr->As<ast::CallExpression>()) { if (auto* call = expr->As<ast::CallExpression>()) {
if (call->target.name) { if (call->target.name) {
if (!IsBuiltin(call->target.name->symbol)) { AddDependency(call->target.name, call->target.name->symbol,
AddGlobalDependency(call->target.name, "function", "calls");
call->target.name->symbol, "function",
"calls");
graph_.resolved_symbols.emplace(
call,
utils::Lookup(graph_.resolved_symbols, call->target.name));
}
} }
if (call->target.type) { if (call->target.type) {
TraverseType(call->target.type); TraverseType(call->target.type);
graph_.resolved_symbols.emplace(
call,
utils::Lookup(graph_.resolved_symbols, call->target.type));
} }
} }
if (auto* cast = expr->As<ast::BitcastExpression>()) { if (auto* cast = expr->As<ast::BitcastExpression>()) {
@ -360,7 +345,7 @@ class DependencyScanner {
return; return;
} }
if (auto* tn = ty->As<ast::TypeName>()) { if (auto* tn = ty->As<ast::TypeName>()) {
AddGlobalDependency(tn, tn->name, "type", "references"); AddDependency(tn, tn->name, "type", "references");
return; return;
} }
if (auto* vec = ty->As<ast::Vector>()) { if (auto* vec = ty->As<ast::Vector>()) {
@ -416,24 +401,31 @@ class DependencyScanner {
UnhandledNode(diagnostics_, attr); UnhandledNode(diagnostics_, attr);
} }
/// Adds the dependency to the currently processed global /// Adds the dependency from `from` to `to`, erroring if `to` cannot be
void AddGlobalDependency(const ast::Node* from, /// resolved.
void AddDependency(const ast::Node* from,
Symbol to, Symbol to,
const char* use, const char* use,
const char* action) { const char* action) {
auto global_it = globals_.find(to); auto* resolved = scope_stack_.Get(to);
if (global_it != globals_.end()) { if (!resolved) {
auto* global = global_it->second; if (!IsBuiltin(to)) {
UnknownSymbol(to, from->source, use);
return;
}
}
if (auto* global = utils::Lookup(globals_, to);
global && global->node == resolved) {
if (dependency_edges_ if (dependency_edges_
.emplace(DependencyEdge{current_global_, global}, .emplace(DependencyEdge{current_global_, global},
DependencyInfo{from->source, action}) DependencyInfo{from->source, action})
.second) { .second) {
current_global_->deps.emplace_back(global); current_global_->deps.emplace_back(global);
} }
graph_.resolved_symbols.emplace(from, global->node);
} else {
UnknownSymbol(to, from->source, use);
} }
graph_.resolved_symbols.emplace(from, resolved);
} }
/// @returns true if `name` is the name of a builtin function /// @returns true if `name` is the name of a builtin function

View File

@ -993,9 +993,9 @@ TEST_F(ResolverDependencyGraphCyclicRefTest, Mixed_RecursiveDependencies) {
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
R"(3:1 error: cyclic dependency found: 'S' -> 'A' -> 'S' R"(2:1 error: cyclic dependency found: 'A' -> 'S' -> 'A'
3:10 note: struct 'S' references alias 'A' here
2:10 note: alias 'A' references struct 'S' here 2:10 note: alias 'A' references struct 'S' here
3:10 note: struct 'S' references alias 'A' here
4:1 error: cyclic dependency found: 'Z' -> 'L' -> 'Z' 4:1 error: cyclic dependency found: 'Z' -> 'L' -> 'Z'
4:10 note: var 'Z' references let 'L' here 4:10 note: var 'Z' references let 'L' here
5:10 note: let 'L' references var 'Z' here)"); 5:10 note: let 'L' references var 'Z' here)");

View File

@ -268,8 +268,9 @@ sem::Type* Resolver::Type(const ast::Type* ty) {
return builder_->create<sem::ExternalTexture>(); return builder_->create<sem::ExternalTexture>();
}, },
[&](Default) -> sem::Type* { [&](Default) -> sem::Type* {
auto* resolved = ResolvedSymbol(ty);
return Switch( return Switch(
ResolvedSymbol(ty), // resolved, //
[&](sem::Type* type) { return type; }, [&](sem::Type* type) { return type; },
[&](sem::Variable* var) { [&](sem::Variable* var) {
auto name = auto name =
@ -291,7 +292,10 @@ sem::Type* Resolver::Type(const ast::Type* ty) {
}, },
[&](Default) { [&](Default) {
TINT_UNREACHABLE(Resolver, diagnostics_) TINT_UNREACHABLE(Resolver, diagnostics_)
<< "Unhandled ast::Type: " << ty->TypeInfo().name; << "Unhandled resolved type '"
<< (resolved ? resolved->TypeInfo().name : "<null>")
<< "' resolved from ast::Type '" << ty->TypeInfo().name
<< "'";
return nullptr; return nullptr;
}); });
}); });

View File

@ -29,9 +29,9 @@ namespace utils {
/// @return the map item value, or `if_missing` if the map does not contain the /// @return the map item value, or `if_missing` if the map does not contain the
/// given key /// given key
template <typename K, typename V, typename H, typename C, typename KV = K> template <typename K, typename V, typename H, typename C, typename KV = K>
V Lookup(std::unordered_map<K, V, H, C>& map, V Lookup(const std::unordered_map<K, V, H, C>& map,
const KV& key, const KV& key,
const KV& if_missing = {}) { const V& if_missing = {}) {
auto it = map.find(key); auto it = map.find(key);
return it != map.end() ? it->second : if_missing; return it != map.end() ? it->second : if_missing;
} }