Resolve named types during expression elaboration

Named types in expression-like contexts are currently converted to
`PETypename` by the parser. This commits to the type interpretation before
symbol lookup, although the same syntax can represent an ordinary identifier.

Keep atomic types as `PETypename`, but parse local and package-qualified type
identifier candidates as `PEIdent`. Preserve any bracket suffixes so they can
be interpreted as packed dimensions for a type or as selects for a value.

Add `PExpr::test_type()` for non-elaborating type detection and
`PExpr::elaborate_type()` to return the elaborated type. Use these interfaces
for `$bits()`, `$sizeof()` and type parameters. Callers test the
interpretation before elaborating it, so a null type reports an elaboration
failure without incorrectly falling back to value elaboration.

Type testing is normally followed by elaboration, and both operations need
the same symbol lookup. Let `PEIdent::test_type()` cache the result and its
declaration scope, keyed by the lookup scope. Keep `elaborate_type()` const and
perform a local lookup when type testing was skipped or the cached result is
from another scope. This avoids repeated lookup without reusing a
scope-dependent result.

Search the complete identifier path before deciding whether it names a type.
Stop lookup at signal placeholders so they still hide matching types in outer
scopes without being elaborated by the type probe.

The LRM section 6.18 does not allow hierarchical references to type
identifiers, so reject a dotted type reference after lookup and report where
the type was declared. This distinguishes an invalid `test.T` type reference
from a path that does not name a type.

Use the unified symbol lookup result to choose the type or value
interpretation. Report an error when a type reaches an expression or l-value
context. This preserves normal lexical shadowing while deferring only the
syntactic decision.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
This commit is contained in:
Lars-Peter Clausen 2026-08-10 22:07:47 -07:00
parent a7e258fad1
commit db90f0ef06
4 changed files with 227 additions and 19 deletions

27
PExpr.h
View File

@ -128,6 +128,13 @@ class PExpr : public LineInfo {
virtual unsigned test_width(Design*des, NetScope*scope, virtual unsigned test_width(Design*des, NetScope*scope,
width_mode_t&mode); width_mode_t&mode);
// Return true if this expression represents a type in this scope. This
// may cache lookup state for a subsequent elaborate_type() call.
virtual bool test_type(Design *des, NetScope *scope);
// Elaborate this expression as a type. Return null on failure.
virtual ivl_type_t elaborate_type(Design *des, NetScope *scope) const;
// After the test_width method is complete, these methods // After the test_width method is complete, these methods
// return valid results. // return valid results.
ivl_variable_type_t expr_type() const { return expr_type_; } ivl_variable_type_t expr_type() const { return expr_type_; }
@ -377,6 +384,8 @@ class PEIdent : public PExpr {
virtual NetExpr*elaborate_expr(Design*des, NetScope*scope, virtual NetExpr*elaborate_expr(Design*des, NetScope*scope,
ivl_type_t type, unsigned flags) const override; ivl_type_t type, unsigned flags) const override;
bool test_type(Design *des, NetScope *scope) override;
ivl_type_t elaborate_type(Design *des, NetScope *scope) const override;
virtual NetExpr*elaborate_expr(Design*des, NetScope*, virtual NetExpr*elaborate_expr(Design*des, NetScope*,
unsigned expr_wid, unsigned expr_wid,
unsigned flags) const override; unsigned flags) const override;
@ -396,10 +405,22 @@ class PEIdent : public PExpr {
const pform_scoped_name_t& path() const { return path_; } const pform_scoped_name_t& path() const { return path_; }
private: private:
// Type testing and elaboration normally happen as a pair. Preserve the
// lookup result between them, but only reuse it in the same scope.
struct type_lookup_t {
const NetScope *lookup_scope = nullptr;
NetScope *declaration_scope = nullptr;
typedef_t *type_def = nullptr;
bool valid = false;
};
pform_scoped_name_t path_; pform_scoped_name_t path_;
bool no_implicit_sig_; bool no_implicit_sig_;
type_lookup_t type_lookup_;
bool find_type_(Design *des, NetScope *scope,
struct symbol_search_results &search_results) const;
private:
// Common functions to calculate parts of part/bit // Common functions to calculate parts of part/bit
// selects. These methods return true if the expressions // selects. These methods return true if the expressions
// elaborate/calculate, or false if there is some sort of // elaborate/calculate, or false if there is some sort of
@ -710,8 +731,8 @@ class PETypename : public PExpr {
width_mode_t&mode) override; width_mode_t&mode) override;
virtual NetExpr*elaborate_expr(Design*des, NetScope*scope, virtual NetExpr*elaborate_expr(Design*des, NetScope*scope,
ivl_type_t type, unsigned flags) const override; ivl_type_t type, unsigned flags) const override;
bool test_type(Design *des, NetScope *scope) override;
inline data_type_t* get_type() const { return data_type_; } ivl_type_t elaborate_type(Design *des, NetScope *scope) const override;
private: private:
data_type_t*data_type_; data_type_t*data_type_;

View File

@ -488,6 +488,16 @@ unsigned PExpr::test_width(Design*des, NetScope*, width_mode_t&)
return 1; return 1;
} }
ivl_type_t PExpr::elaborate_type(Design *, NetScope *) const
{
return nullptr;
}
bool PExpr::test_type(Design *, NetScope *)
{
return false;
}
NetExpr* PExpr::elaborate_expr(Design*des, NetScope*scope, ivl_type_t, unsigned flags) const NetExpr* PExpr::elaborate_expr(Design*des, NetScope*scope, ivl_type_t, unsigned flags) const
{ {
// Fall back to the old method. Currently the new method won't be used // Fall back to the old method. Currently the new method won't be used
@ -1735,7 +1745,7 @@ unsigned PECallFunction::test_width_sfunc_(Design*des, NetScope*scope,
if (expr == 0) if (expr == 0)
return 0; return 0;
if (! dynamic_cast<PETypename*>(expr)) { if (!expr->test_type(des, scope)) {
// The argument type/width is self-determined and doesn't // The argument type/width is self-determined and doesn't
// affect the result type/width. Note that if the // affect the result type/width. Note that if the
// argument is a type name (a special case) then // argument is a type name (a special case) then
@ -2332,9 +2342,10 @@ NetExpr* PECallFunction::elaborate_sfunc_(Design*des, NetScope*scope,
PExpr *expr = parms_[0].parm; PExpr *expr = parms_[0].parm;
uint64_t use_width = 0; uint64_t use_width = 0;
if (const PETypename*type_expr = dynamic_cast<PETypename*>(expr)) { if (expr->test_type(des, scope)) {
ivl_type_t data_type = type_expr->get_type()->elaborate_type(des, scope); ivl_type_t data_type = expr->elaborate_type(des, scope);
ivl_assert(*this, data_type); if (!data_type)
return nullptr;
use_width = 1; use_width = 1;
while (const netuarray_t *utype = while (const netuarray_t *utype =
dynamic_cast<const netuarray_t*>(data_type)) { dynamic_cast<const netuarray_t*>(data_type)) {
@ -4991,6 +5002,137 @@ ivl_type_t PEIdent::resolve_type_(Design *des, const symbol_search_results &sr,
return type; return type;
} }
bool PEIdent::find_type_(Design *des, NetScope *scope,
struct symbol_search_results &search_results) const
{
return symbol_search(this, des, scope, path_, lexical_pos(),
&search_results,
SYMBOL_SEARCH_NO_SIGNAL_ELABORATION)
&& search_results.type_def;
}
bool PEIdent::test_type(Design *des, NetScope *scope)
{
if (type_lookup_.valid && type_lookup_.lookup_scope == scope)
return type_lookup_.type_def != nullptr;
symbol_search_results search_results;
find_type_(des, scope, search_results);
type_lookup_.lookup_scope = scope;
type_lookup_.declaration_scope = search_results.scope;
type_lookup_.type_def = search_results.type_def;
type_lookup_.valid = true;
return type_lookup_.type_def != nullptr;
}
static bool elaborate_type_dimensions(Design *des, NetScope *scope,
const std::list<index_component_t> &indices,
netranges_t &dimensions)
{
dimensions.reserve(indices.size());
bool dimensions_ok = true;
for (const auto &index : indices) {
PExpr *range_msb = index.msb;
PExpr *range_lsb = nullptr;
switch (index.sel) {
case index_component_t::SEL_BIT:
break;
case index_component_t::SEL_PART:
range_lsb = index.lsb;
break;
case index_component_t::SEL_NONE:
cerr << index.get_fileline() << ": error: "
<< "An unsized dimension is not allowed here." << endl;
des->errors++;
dimensions_ok = false;
continue;
case index_component_t::SEL_BIT_LAST:
case index_component_t::SEL_QUEUE_BOUND:
cerr << index.get_fileline() << ": error: "
<< "A queue dimension is not allowed here." << endl;
des->errors++;
dimensions_ok = false;
continue;
case index_component_t::SEL_IDX_UP:
case index_component_t::SEL_IDX_DO:
cerr << index.get_fileline() << ": error: "
<< "An indexed part select is not allowed in a dimension."
<< endl;
des->errors++;
dimensions_ok = false;
continue;
}
long range_msb_value = 0;
long range_lsb_value = 0;
pform_range_t range(range_msb, range_lsb);
dimensions_ok &= evaluate_range(des, scope, &index, range,
range_msb_value, range_lsb_value);
dimensions.emplace_back(range_msb_value, range_lsb_value);
}
return dimensions_ok;
}
ivl_type_t PEIdent::elaborate_type(Design *des, NetScope *scope) const
{
symbol_search_results search_results;
NetScope *declaration_scope;
typedef_t *type_def;
if (type_lookup_.valid && type_lookup_.lookup_scope == scope) {
declaration_scope = type_lookup_.declaration_scope;
type_def = type_lookup_.type_def;
} else {
if (!find_type_(des, scope, search_results))
return nullptr;
declaration_scope = search_results.scope;
type_def = search_results.type_def;
}
if (!type_def)
return nullptr;
// Recognize types at the end of hierarchical paths during lookup so
// they are not mistaken for values, but reject the hierarchical type
// reference during elaboration.
if (path_.name.size() != 1) {
cerr << get_fileline() << ": error: Type name `" << path_
<< "' cannot be referenced through a hierarchical path."
<< endl;
cerr << type_def->get_fileline()
<< ": : The type was declared here." << endl;
des->errors++;
return nullptr;
}
ivl_type_t base_type = type_def->elaborate_type(des, declaration_scope);
if (!base_type)
return nullptr;
const auto &name = path_.name.front();
netranges_t packed_dimensions;
if (!elaborate_type_dimensions(des, scope, name.index,
packed_dimensions))
return nullptr;
if (packed_dimensions.empty())
return base_type;
if (!base_type->packed()) {
cerr << get_fileline() << ": error: Packed array base-type `"
<< name.name << "` is not packed." << endl;
des->errors++;
return nullptr;
}
return new netparray_t(packed_dimensions, base_type);
}
unsigned PEIdent::test_width(Design*des, NetScope*scope, width_mode_t&mode) unsigned PEIdent::test_width(Design*des, NetScope*scope, width_mode_t&mode)
{ {
symbol_search_results sr; symbol_search_results sr;
@ -7881,6 +8023,16 @@ NetExpr* PETernary::elab_and_eval_alternative_(Design*des, NetScope*scope,
* A typename expression is only legal in very narrow cases. This is * A typename expression is only legal in very narrow cases. This is
* just a placeholder. * just a placeholder.
*/ */
ivl_type_t PETypename::elaborate_type(Design *des, NetScope *scope) const
{
return data_type_->elaborate_type(des, scope);
}
bool PETypename::test_type(Design *, NetScope *)
{
return true;
}
unsigned PETypename::test_width(Design*des, NetScope*, width_mode_t&) unsigned PETypename::test_width(Design*des, NetScope*, width_mode_t&)
{ {
cerr << get_fileline() << ": error: " cerr << get_fileline() << ": error: "

View File

@ -822,8 +822,7 @@ void NetScope::evaluate_parameter_string_(Design*des, param_ref_t cur)
void NetScope::evaluate_type_parameter_(Design *des, param_ref_t cur) void NetScope::evaluate_type_parameter_(Design *des, param_ref_t cur)
{ {
const PETypename *type_expr = dynamic_cast<const PETypename*>(cur->second.val_expr); if (!cur->second.val_expr->test_type(des, cur->second.val_scope)) {
if (!type_expr) {
cerr << this->get_fileline() << ": error: " cerr << this->get_fileline() << ": error: "
<< "Type parameter `" << cur->first << "` value `" << "Type parameter `" << cur->first << "` value `"
<< *cur->second.val_expr << "` is not a type." << *cur->second.val_expr << "` is not a type."
@ -835,14 +834,13 @@ void NetScope::evaluate_type_parameter_(Design *des, param_ref_t cur)
return; return;
} }
data_type_t *ptype = type_expr->get_type(); cur->second.ivl_type = cur->second.val_expr->elaborate_type(
NetScope *type_scope = cur->second.val_scope; des, cur->second.val_scope);
cur->second.ivl_type = ptype->elaborate_type(des, type_scope);
if (!cur->second.ivl_type) if (!cur->second.ivl_type)
return; return;
if (!cur->second.type_restrict.matches(cur->second.ivl_type)) { if (!cur->second.type_restrict.matches(cur->second.ivl_type)) {
cerr << type_expr->get_fileline() << ": error: " cerr << cur->second.val_expr->get_fileline() << ": error: "
<< "Type parameter `" << cur->first << "` expects a `" << "Type parameter `" << cur->first << "` expects a `"
<< cur->second.type_restrict << "` type, got `" << cur->second.type_restrict << "` type, got `"
<< *cur->second.ivl_type << "`." << endl; << *cur->second.ivl_type << "`." << endl;

49
parse.y
View File

@ -224,7 +224,7 @@ static void validate_hierarchy_index_components(
} }
} }
static void append_hierarchy_identifier_component( static void append_identifier_component(
pform_name_t &path, perm_string name, pform_name_t &path, perm_string name,
std::list<index_component_t> *components) std::list<index_component_t> *components)
{ {
@ -234,10 +234,37 @@ static void append_hierarchy_identifier_component(
if (!component_list) if (!component_list)
return; return;
validate_hierarchy_index_components(*component_list);
path.back().index.splice(path.back().index.end(), *component_list); path.back().index.splice(path.back().index.end(), *component_list);
} }
static void append_hierarchy_identifier_component(
pform_name_t &path, perm_string name,
std::list<index_component_t> *components)
{
if (components)
validate_hierarchy_index_components(*components);
append_identifier_component(path, name, components);
}
static PExpr *make_type_identifier_expression(
const struct vlltype &loc, PPackage *package,
char *text,
std::list<index_component_t> *components)
{
pform_name_t name;
append_identifier_component(name, lex_strings.make(text), components);
PExpr *expr;
if (package)
expr = pform_package_ident(loc, package, &name);
else
expr = pform_new_ident(loc, name, true);
delete[]text;
return expr;
}
static std::list<pform_range_t> * static std::list<pform_range_t> *
make_dimensions(std::list<index_component_t> *components) make_dimensions(std::list<index_component_t> *components)
{ {
@ -1189,7 +1216,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type,
%type <spec_optional_args> timeskew_fullskew_opt_remain_active_flag %type <spec_optional_args> timeskew_fullskew_opt_remain_active_flag
%type <expr> assignment_pattern expression expression_opt expr_mintypmax %type <expr> assignment_pattern expression expression_opt expr_mintypmax
%type <expr> expr_primary_or_typename expr_primary call_chain_expr %type <expr> expr_primary_or_typename expr_primary call_chain_expr type_value
%type <expr> class_new dynamic_array_new %type <expr> class_new dynamic_array_new
%type <expr> net_decl_initializer_opt var_decl_initializer_opt initializer_opt %type <expr> net_decl_initializer_opt var_decl_initializer_opt initializer_opt
%type <expr> inc_or_dec_expression inside_expression lpvalue %type <expr> inc_or_dec_expression inside_expression lpvalue
@ -4540,12 +4567,22 @@ expr_primary_or_typename
/* There are a few special cases (notably $bits argument) where the /* There are a few special cases (notably $bits argument) where the
expression may be a type name. Let the elaborator sort this out. */ expression may be a type name. Let the elaborator sort this out. */
| data_type | type_value
{ PETypename*tmp = new PETypename($1); ;
type_value
: atomic_type
{ auto tmp = new PETypename($1);
FILE_NAME(tmp, @1); FILE_NAME(tmp, @1);
$$ = tmp; $$ = tmp;
} }
| TYPE_IDENTIFIER index_components_opt
{ $$ = make_type_identifier_expression(@1, nullptr, $1.text, $2);
}
| package_scope TYPE_IDENTIFIER index_components_opt
{ lex_in_package_scope(nullptr);
$$ = make_type_identifier_expression(@2, $1, $2.text, $3);
}
; ;
/* SystemVerilog: a().b() — call a function, then invoke a method on the /* SystemVerilog: a().b() — call a function, then invoke a method on the