Consider types during symbol lookup

SystemVerilog places user-defined types and data identifiers in the same
namespace. Symbol lookup cannot currently return a type, so a value lookup can
skip a nearer typedef and incorrectly bind a value in an outer scope when an
ambiguous identifier is resolved during elaboration.

Add typedef results to `symbol_search()` and check visible typedefs before
continuing to enclosing scopes. Use the lexical visibility limit selected by
the caller. This is normally the reference position, while function and task
call lookup uses the end of the current scope as required by the LRM section
26.3.

Let callers that require a value or another non-type symbol diagnose the type
result and report its declaration location. This includes function and task
calls, expressions with and without a required type, procedural l-values,
named-event triggers, and disable targets. Callers that only probe for a
particular kind of symbol, including port-connection matching, ignore a type
result. Also ensure scope-only callers do not confuse the typedef's owning scope
with the resolved object.

This prepares expression elaboration to defer the type-versus-value decision
while preserving lexical shadowing.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
This commit is contained in:
Lars-Peter Clausen 2026-08-10 22:06:08 -07:00
parent 3ed306bab8
commit c5ab989dc7
8 changed files with 99 additions and 18 deletions

View File

@ -488,6 +488,9 @@ bool PEIdent::has_aa_term(Design*des, NetScope*scope) const
if (!symbol_search(this, des, scope, path_, lexical_pos(), &sr))
return false;
if (sr.type_def)
return false;
// Class properties are not considered automatic since a non-blocking
// assignment to an object stored in an automatic variable is supposed to
// capture a reference to the object, not the variable.

View File

@ -3271,6 +3271,9 @@ NetExpr* PECallFunction::elaborate_expr_(Design*des, NetScope*scope,
return 0;
}
if (!search_results.require_non_type(this, des, "in a function call"))
return 0;
// If the symbol is found, but is not a scope...
if (! search_results.is_scope() && !test_function_return_value(search_results)) {
@ -5146,6 +5149,9 @@ NetExpr* PEIdent::elaborate_expr(Design*des, NetScope*scope,
symbol_search_results sr;
symbol_search(this, des, scope, path_, lexical_pos(), &sr);
if (!sr.require_non_type(this, des, "in an expression"))
return nullptr;
if (!sr.net) {
cerr << get_fileline() << ": error: Unable to bind variable `"
<< path_ << "' in `" << scope_path(scope) << "'" << endl;
@ -5466,6 +5472,9 @@ NetExpr* PEIdent::elaborate_expr_(Design*des, NetScope*scope,
symbol_search_results sr;
symbol_search(this, des, scope, path_, lexical_pos(), &sr);
if (!sr.require_non_type(this, des, "in an expression"))
return 0;
// If the identifier name is a parameter name, then return
// the parameter value.
if (sr.par_val != 0) {

View File

@ -178,6 +178,9 @@ NetAssign_* PEIdent::elaborate_lval(Design*des,
symbol_search_results sr;
symbol_search(this, des, scope, path_, lexical_pos(), &sr);
if (!sr.require_non_type(this, des, "as a procedural l-value"))
return nullptr;
NetNet *reg = sr.net;
const pform_name_t &member_path = sr.path_tail;

View File

@ -1274,7 +1274,7 @@ bool PGModule::match_module_ports_(Design*des, const Module*rmod,
wildcard.lexical_pos(), &sr);
if (sr.net != 0 ||
(rmod->ports[j]->is_interface_port() &&
sr.scope != 0 && sr.scope->is_interface())) {
sr.is_scope() && sr.scope->is_interface())) {
pins[j] = new PEIdent(
rmod->ports[j]->name,
wildcard.lexical_pos(), true);
@ -1450,7 +1450,8 @@ static bool resolve_interface_actual_scope(const PExpr*actual,
symbol_search_results sr;
symbol_search(actual, des, parent_scope, actual_ident->path(),
actual_ident->lexical_pos(), &sr);
res.scope = sr.scope;
if (sr.is_scope())
res.scope = sr.scope;
if (sr.through_interface_alias())
res.modport = sr.interface_alias_modport;
}
@ -1462,7 +1463,8 @@ static bool resolve_interface_actual_scope(const PExpr*actual,
symbol_search(actual, des, parent_scope, actual_ident->path(),
actual_ident->lexical_pos(), &sr);
res.scope = sr.scope;
if (sr.is_scope())
res.scope = sr.scope;
if (sr.through_interface_alias())
res.modport = sr.interface_alias_modport;
else if (NetScope*child = parent_scope->child(hname_t(res.display_name)))
@ -3956,6 +3958,10 @@ NetProc* PCallTask::elaborate_usr(Design*des, NetScope*scope) const
NetScope *func_scope = nullptr;
if (symbol_search(this, des, scope, call_path, lexical_pos(),
&search_results, true)) {
if (!search_results.require_non_type(
this, des, "in a task or function call"))
return nullptr;
if (search_results.is_scope()) {
if (search_results.scope->type() == NetScope::TASK)
task = search_results.scope;
@ -5264,6 +5270,10 @@ NetProc* PDisable::elaborate(Design*des, NetScope*scope) const
des->errors += 1;
return 0;
}
if (!search_results.require_non_type(this, des,
"as a disable target"))
return nullptr;
if (!search_results.is_scope()) {
cerr << get_fileline() << ": error: Cannot disable "
<< search_results.result_type() << " `" << scope_ << "'."
@ -6669,6 +6679,8 @@ NetProc* PTrigger::elaborate(Design*des, NetScope*scope) const
des->errors += 1;
return 0;
}
if (!sr.require_non_type(this, des, "as a named event"))
return nullptr;
if (!sr.eve) {
cerr << get_fileline() << ": error: <" << event_ << ">"
@ -6698,6 +6710,8 @@ NetProc* PNBTrigger::elaborate(Design*des, NetScope*scope) const
des->errors += 1;
return 0;
}
if (!sr.require_non_type(this, des, "as a named event"))
return nullptr;
if (sr.eve == 0) {
cerr << get_fileline() << ": error: <" << event_ << ">"

View File

@ -244,6 +244,19 @@ void NetScope::add_typedefs(const map<perm_string,typedef_t*>*typedefs)
typedefs_ = *typedefs;
}
typedef_t *NetScope::lookup_typedef(perm_string name,
unsigned int lexical_pos) const
{
auto type = typedefs_.find(name);
if (type == typedefs_.end())
return nullptr;
if (type->second->lexical_pos() > lexical_pos)
return nullptr;
return type->second;
}
/*
* Type names are resolved to typedef_t objects during parsing. Locate the
* scope that owns that exact object instead of resolving its name again

View File

@ -1006,6 +1006,11 @@ class NetScope : public Definitions, public Attrib {
void add_typedefs(const std::map<perm_string,typedef_t*>*typedefs);
/* Find a type declared in this scope and visible at the given source
position. */
typedef_t *lookup_typedef(perm_string name,
unsigned int lexical_pos) const;
/* Locate the scope that owns the resolved typedef object. */
NetScope*find_typedef_scope(const Design*des, const typedef_t*type_i);

View File

@ -48,6 +48,7 @@ struct symbol_search_results {
net = 0;
par_val = 0;
type = 0;
type_def = nullptr;
eve = 0;
decl_after_use = 0;
interface_alias_scope = 0;
@ -59,6 +60,7 @@ struct symbol_search_results {
if (net) return false;
if (eve) return false;
if (par_val) return false;
if (type_def) return false;
if (scope) return true;
return false;
}
@ -67,6 +69,7 @@ struct symbol_search_results {
if (net) return true;
if (eve) return true;
if (par_val) return true;
if (type_def) return true;
if (scope) return true;
return false;
}
@ -75,10 +78,14 @@ struct symbol_search_results {
if (net) return "net";
if (eve) return "named event";
if (par_val) return "parameter";
if (type_def) return "type";
if (scope) return "scope";
return "nothing found";
}
bool require_non_type(const LineInfo *li, Design *des,
const char *use) const;
inline bool through_interface_alias() const {
return interface_alias_target != 0;
}
@ -92,6 +99,8 @@ struct symbol_search_results {
// optional value dimensions.
const NetExpr*par_val;
ivl_type_t type;
// If this is a type, the parsed type declaration.
typedef_t *type_def;
// If this is a named event, ...
NetEvent*eve;
// If a symbol was located but skipped because its lexical position

View File

@ -30,6 +30,21 @@
using namespace std;
bool symbol_search_results::require_non_type(const LineInfo *li,
Design *des,
const char *use) const
{
if (!type_def)
return true;
cerr << li->get_fileline() << ": error: Type name `" << path_head
<< "' cannot be used " << use << "." << endl;
cerr << type_def->get_fileline() << ": : The type was declared here."
<< endl;
des->errors += 1;
return false;
}
enum class scope_object_search_result_t {
not_found,
found,
@ -39,8 +54,8 @@ enum class scope_object_search_result_t {
static scope_object_search_result_t symbol_search_scope_objects(
const LineInfo *li, Design *des, NetScope *scope,
NetScope *start_scope, const pform_name_t &path,
const name_component_t &path_tail, unsigned int lexical_pos,
bool check_lexical_order, struct symbol_search_results *res)
const name_component_t &path_tail, unsigned int visibility_pos,
struct symbol_search_results *res)
{
// Special case `super` keyword. Return the `this` object, but
// with the type of the base class.
@ -70,8 +85,7 @@ static scope_object_search_result_t symbol_search_scope_objects(
}
if (NetNet *net = scope->find_signal(path_tail.name)) {
bool decl_after_use = check_lexical_order
&& !(net->lexical_pos() <= lexical_pos);
bool decl_after_use = !(net->lexical_pos() <= visibility_pos);
if (!gn_strict_net_var_declaration || !decl_after_use) {
pform_name_t path_head = path;
path_head.push_back(path_tail);
@ -87,7 +101,7 @@ static scope_object_search_result_t symbol_search_scope_objects(
<< ": : the net/variable is declared here."
<< endl;
// suppress further warnings for this net
net->lexical_pos(lexical_pos);
net->lexical_pos(visibility_pos);
}
return scope_object_search_result_t::found;
} else if (!res->decl_after_use) {
@ -96,8 +110,7 @@ static scope_object_search_result_t symbol_search_scope_objects(
}
if (NetEvent *eve = scope->find_event(path_tail.name)) {
bool decl_after_use = check_lexical_order
&& !(eve->lexical_pos() <= lexical_pos);
bool decl_after_use = !(eve->lexical_pos() <= visibility_pos);
if (!gn_strict_net_var_declaration || !decl_after_use) {
pform_name_t path_head = path;
path_head.push_back(path_tail);
@ -111,7 +124,7 @@ static scope_object_search_result_t symbol_search_scope_objects(
cerr << eve->get_fileline()
<< ": : the event is declared here." << endl;
// suppress further warnings for this event
eve->lexical_pos(lexical_pos);
eve->lexical_pos(visibility_pos);
}
return scope_object_search_result_t::found;
} else if (!res->decl_after_use) {
@ -119,14 +132,26 @@ static scope_object_search_result_t symbol_search_scope_objects(
}
}
// Types and data objects share a namespace. Return a visible
// type even when the caller requires a value so it cannot
// incorrectly continue searching an outer scope.
if (auto type_def = scope->lookup_typedef(
path_tail.name, visibility_pos)) {
pform_name_t path_head = path;
path_head.push_back(path_tail);
res->scope = scope;
res->type_def = type_def;
res->path_head = path_head;
return scope_object_search_result_t::found;
}
if (const NetExpr *par = scope->get_parameter(
des, path_tail.name, res->type)) {
bool is_enum_name;
unsigned int declaration_pos =
scope->get_constant_lexical_pos(path_tail.name,
is_enum_name);
bool decl_after_use = check_lexical_order
&& !(declaration_pos <= lexical_pos);
bool decl_after_use = !(declaration_pos <= visibility_pos);
if (!gn_strict_parameter_declaration || !decl_after_use) {
pform_name_t path_head = path;
path_head.push_back(path_tail);
@ -145,7 +170,7 @@ static scope_object_search_result_t symbol_search_scope_objects(
<< " is declared here." << endl;
// suppress further warnings for this constant
scope->set_constant_lexical_pos(
path_tail.name, lexical_pos);
path_tail.name, visibility_pos);
}
return scope_object_search_result_t::found;
} else if (!res->decl_after_use) {
@ -180,7 +205,7 @@ static scope_object_search_result_t symbol_search_scope_objects(
// Finally check the rare case of a signal that hasn't
// been elaborated yet.
if (PWire *wire = scope->find_signal_placeholder(path_tail.name)) {
if (!check_lexical_order || (wire->lexical_pos() <= lexical_pos)) {
if (wire->lexical_pos() <= visibility_pos) {
NetNet *net = wire->elaborate_sig(des, scope);
if (!net)
return scope_object_search_result_t::failed;
@ -413,8 +438,8 @@ static bool symbol_search_(const LineInfo *li, Design *des, NetScope *scope,
// An explicit $unit:: prefix only disambiguates the name and does
// not allow forward references (LRM 3.12.1).
const bool check_lexical_order =
!scope_is_bound || scope->is_unit();
const unsigned int visibility_pos =
(!scope_is_bound || scope->is_unit()) ? lexical_pos : UINT_MAX;
if (scope->genvar_tmp.str() && path_tail.name == scope->genvar_tmp)
return false;
@ -435,7 +460,7 @@ static bool symbol_search_(const LineInfo *li, Design *des, NetScope *scope,
scope_object_search_result_t object_result =
symbol_search_scope_objects(
li, des, scope, start_scope, path, path_tail,
lexical_pos, check_lexical_order, res);
visibility_pos, res);
if (object_result == scope_object_search_result_t::found)
return true;
if (object_result == scope_object_search_result_t::failed)