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:
parent
a7e258fad1
commit
db90f0ef06
27
PExpr.h
27
PExpr.h
|
|
@ -128,6 +128,13 @@ class PExpr : public LineInfo {
|
|||
virtual unsigned test_width(Design*des, NetScope*scope,
|
||||
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
|
||||
// return valid results.
|
||||
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,
|
||||
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*,
|
||||
unsigned expr_wid,
|
||||
unsigned flags) const override;
|
||||
|
|
@ -396,10 +405,22 @@ class PEIdent : public PExpr {
|
|||
const pform_scoped_name_t& path() const { return path_; }
|
||||
|
||||
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_;
|
||||
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
|
||||
// selects. These methods return true if the expressions
|
||||
// elaborate/calculate, or false if there is some sort of
|
||||
|
|
@ -710,8 +731,8 @@ class PETypename : public PExpr {
|
|||
width_mode_t&mode) override;
|
||||
virtual NetExpr*elaborate_expr(Design*des, NetScope*scope,
|
||||
ivl_type_t type, unsigned flags) const override;
|
||||
|
||||
inline data_type_t* get_type() const { return data_type_; }
|
||||
bool test_type(Design *des, NetScope *scope) override;
|
||||
ivl_type_t elaborate_type(Design *des, NetScope *scope) const override;
|
||||
|
||||
private:
|
||||
data_type_t*data_type_;
|
||||
|
|
|
|||
160
elab_expr.cc
160
elab_expr.cc
|
|
@ -488,6 +488,16 @@ unsigned PExpr::test_width(Design*des, NetScope*, width_mode_t&)
|
|||
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
|
||||
{
|
||||
// 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)
|
||||
return 0;
|
||||
|
||||
if (! dynamic_cast<PETypename*>(expr)) {
|
||||
if (!expr->test_type(des, scope)) {
|
||||
// The argument type/width is self-determined and doesn't
|
||||
// affect the result type/width. Note that if the
|
||||
// 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;
|
||||
|
||||
uint64_t use_width = 0;
|
||||
if (const PETypename*type_expr = dynamic_cast<PETypename*>(expr)) {
|
||||
ivl_type_t data_type = type_expr->get_type()->elaborate_type(des, scope);
|
||||
ivl_assert(*this, data_type);
|
||||
if (expr->test_type(des, scope)) {
|
||||
ivl_type_t data_type = expr->elaborate_type(des, scope);
|
||||
if (!data_type)
|
||||
return nullptr;
|
||||
use_width = 1;
|
||||
while (const netuarray_t *utype =
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
* 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&)
|
||||
{
|
||||
cerr << get_fileline() << ": error: "
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
const PETypename *type_expr = dynamic_cast<const PETypename*>(cur->second.val_expr);
|
||||
if (!type_expr) {
|
||||
if (!cur->second.val_expr->test_type(des, cur->second.val_scope)) {
|
||||
cerr << this->get_fileline() << ": error: "
|
||||
<< "Type parameter `" << cur->first << "` value `"
|
||||
<< *cur->second.val_expr << "` is not a type."
|
||||
|
|
@ -835,14 +834,13 @@ void NetScope::evaluate_type_parameter_(Design *des, param_ref_t cur)
|
|||
return;
|
||||
}
|
||||
|
||||
data_type_t *ptype = type_expr->get_type();
|
||||
NetScope *type_scope = cur->second.val_scope;
|
||||
cur->second.ivl_type = ptype->elaborate_type(des, type_scope);
|
||||
cur->second.ivl_type = cur->second.val_expr->elaborate_type(
|
||||
des, cur->second.val_scope);
|
||||
if (!cur->second.ivl_type)
|
||||
return;
|
||||
|
||||
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 `"
|
||||
<< cur->second.type_restrict << "` type, got `"
|
||||
<< *cur->second.ivl_type << "`." << endl;
|
||||
|
|
|
|||
49
parse.y
49
parse.y
|
|
@ -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,
|
||||
std::list<index_component_t> *components)
|
||||
{
|
||||
|
|
@ -234,10 +234,37 @@ static void append_hierarchy_identifier_component(
|
|||
if (!component_list)
|
||||
return;
|
||||
|
||||
validate_hierarchy_index_components(*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> *
|
||||
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 <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> net_decl_initializer_opt var_decl_initializer_opt initializer_opt
|
||||
%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
|
||||
expression may be a type name. Let the elaborator sort this out. */
|
||||
| data_type
|
||||
{ PETypename*tmp = new PETypename($1);
|
||||
| type_value
|
||||
;
|
||||
|
||||
type_value
|
||||
: atomic_type
|
||||
{ auto tmp = new PETypename($1);
|
||||
FILE_NAME(tmp, @1);
|
||||
$$ = 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
|
||||
|
|
|
|||
Loading…
Reference in New Issue