Files
iverilog/pform_types.cc
T
Lars-Peter Clausen 7616d47c11 Resolve type identifiers during elaboration
`typeref_t` stores the `typedef_t` selected by the parser. This binds a type
identifier before the elaborated scope is available and can select the wrong
declaration. Function and task declarations are visible throughout their
scope, so a later subroutine named `T` in the current scope hides an earlier
`T` type in an outer scope even when the type reference appears before the
subroutine declaration. Parser-time lookup can not handle this correctly.

For example:

    typedef int T;

    module test;
      T value;

      function T;
        T = 0;
      endfunction
    endmodule

The declaration of `value` must be rejected because the local function named
`T` hides the outer typedef. The same applies to a task named `T`.

Type lookup can also depend on information that is not available to the parser.
For example, a parameterized class can extend a type parameter and inherit `T`
from the selected base class:

    class Derived #(type B = DefaultBase) extends B;
      T value;
    endclass

Parameterized classes are not supported yet, but resolving named types during
elaboration is a prerequisite for supporting this case.

Replace `typeref_t` with `type_identifier_t`, which owns a `PEIdent`. Construct
the identifier in the parser, then resolve it through
`PEIdent::elaborate_type()` and `symbol_search()` while elaborating the data
type. Parser-time type lookup is only used to select grammar paths.
`pform_new_ident()` preserves wildcard import activation for the identifier
expression.

Enforce declaration ordering when an identifier occurs in a grammar position
that requires a data type, even when compatibility options relax it for
ordinary variable lookup. A later variable therefore does not hide an outer
type in a declaration, and a later type declaration can not satisfy an earlier
use. Keep ordinary lookup when an identifier can be either a type or a value,
such as an argument to `$bits()`. Add
`SYMBOL_SEARCH_STRICT_DECLARATION_ORDER` and a required-type elaboration context
for the former case.

For example:

    typedef logic [7:0] T;

    module test;
      T value;
      localparam int A = $bits(value);
      localparam int B = $bits(T);

      integer T;
    endmodule

The declaration of `value` uses the outer typedef, so `A` is 8. With relaxed
variable declaration ordering, the ambiguous reference in `$bits(T)` uses the
later integer, so `B` is 32.

Check a constant's declaration position before `get_parameter()` elaborates it
on demand when declaration order is enforced. This keeps a later declaration in
`parameter T T` from recursively selecting itself as the type.

Recover a failed deferred lookup in `type_identifier_t` with a scalar logic type
after reporting the error. This keeps the existing non-null expectations of
type consumers unchanged.

Signed-off-by: Lars-Peter Clausen <[email protected]>
2026-09-01 16:46:36 -07:00

127 lines
2.9 KiB
C++

/*
* Copyright (c) 2007-2026 Stephen Williams ([email protected])
*
* This source code is free software; you can redistribute it
* and/or modify it in source code form under the terms of the GNU
* General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
# include "pform_types.h"
# include "PExpr.h"
# include "netclass.h"
# include "netenum.h"
data_type_t::~data_type_t()
{
}
type_identifier_t::type_identifier_t(PEIdent *identifier)
: identifier_(identifier)
{
}
type_identifier_t::~type_identifier_t() = default;
PNamedItem::SymbolType data_type_t::symbol_type() const
{
return TYPE;
}
string_type_t::~string_type_t()
{
}
atom_type_t size_type (atom_type_t::INT, true);
PNamedItem::SymbolType enum_type_t::symbol_type() const
{
return ENUM;
}
PNamedItem::SymbolType class_type_t::symbol_type() const
{
return CLASS;
}
bool typedef_t::set_data_type(data_type_t *t)
{
if (data_type.get())
return false;
data_type.reset(t);
return true;
}
bool typedef_t::set_basic_type(type_restrict_t type)
{
return basic_type.merge(type);
}
bool type_restrict_t::merge(type_restrict_t other)
{
if (other.type == ANY)
return true;
if (this->type != ANY && other.type != this->type)
return false;
this->type = other.type;
return true;
}
bool type_restrict_t::matches(ivl_type_t ivl_type) const
{
switch (this->type) {
case ENUM:
return dynamic_cast<const netenum_t *>(ivl_type);
case STRUCT: {
const netstruct_t *struct_type = dynamic_cast<const netstruct_t *>(ivl_type);
return struct_type && !struct_type->union_flag();
}
case UNION: {
const netstruct_t *struct_type = dynamic_cast<const netstruct_t *>(ivl_type);
return struct_type && struct_type->union_flag();
}
case CLASS:
return dynamic_cast<const netclass_t *>(ivl_type);
default:
return true;
}
}
std::ostream& operator<< (std::ostream&out, const type_restrict_t& type)
{
switch (type.type) {
case type_restrict_t::ANY:
out << "any";
break;
case type_restrict_t::ENUM:
out << "enum";
break;
case type_restrict_t::STRUCT:
out << "struct";
break;
case type_restrict_t::UNION:
out << "union";
break;
case type_restrict_t::CLASS:
out << "class";
break;
}
return out;
}