Add error on missing forward declarations (#6207).

This commit is contained in:
Wilson Snyder 2025-07-26 17:11:35 -04:00
parent d8dbb08a95
commit f3560837ec
16 changed files with 234 additions and 33 deletions

View File

@ -17,6 +17,7 @@ Verilator 5.039 devel
* Add NOEFFECT warning, replacing previous `foreach` error.
* Add SPECIFYIGN warning for specify constructs that were previously silently ignored.
* Add enum base data type, and wire data type checking per IEEE.
* Add error on missing forward declarations (#6207). [Alex Solomatnikov]
* Support member-level triggers for virtual interfaces (#5166) (#6148). [Yilou Wang]
* Support disabling a fork in additional contexts (#5432 partial) (#6174) (#6183). [Ryszard Rozak, Antmicro Ltd.]
* Support disable dotted references (#6154). [Ryszard Rozak, Antmicro Ltd.]

View File

@ -58,6 +58,27 @@ const VNTypeInfo VNType::typeInfoTable[] = {
std::ostream& operator<<(std::ostream& os, VNType rhs);
//######################################################################
// VFwdType
bool VFwdType::isNodeCompatible(const AstNode* nodep) const {
const AstNode* defp = nodep;
if (const AstTypedef* const adefp = VN_CAST(defp, Typedef)) defp = adefp->subDTypep();
if (const AstNodeDType* const adefp = VN_CAST(defp, NodeDType))
defp = adefp->skipRefToNonRefp();
switch (m_e) {
case VFwdType::NONE: return true; break;
case VFwdType::ENUM: return VN_IS(defp, EnumDType); break;
case VFwdType::STRUCT: return VN_IS(defp, StructDType); break;
case VFwdType::UNION: return VN_IS(defp, UnionDType); break;
case VFwdType::INTERFACE_CLASS: // FALLTHRU // TODO: Over permissive for now
case VFwdType::CLASS: return VN_IS(defp, ClassRefDType) || VN_IS(defp, Class); break;
default: v3fatalSrc("Bad case");
}
VL_UNREACHABLE;
return false; // LCOV_EXCL_LINE
}
//######################################################################
// VSelfPointerText

View File

@ -304,6 +304,8 @@ public:
explicit VFwdType(int _e)
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
constexpr operator en() const { return m_e; }
// Is a node type compatible with the declaration
bool isNodeCompatible(const AstNode* nodep) const;
};
constexpr bool operator==(const VFwdType& lhs, const VFwdType& rhs) { return lhs.m_e == rhs.m_e; }
constexpr bool operator==(const VFwdType& lhs, VFwdType::en rhs) { return lhs.m_e == rhs; }
@ -2236,6 +2238,8 @@ public:
}
virtual void tag(const string& text) {}
virtual string tag() const { return ""; }
virtual uint32_t declTokenNum() const { return 0; }
virtual void declTokenNumSetMin(uint32_t tokenNum) {}
virtual string verilogKwd() const { return ""; }
string nameProtect() const VL_MT_STABLE; // Name with --protect-id applied
string origNameProtect() const; // origName with --protect-id applied

View File

@ -1829,6 +1829,7 @@ class AstTypedef final : public AstNode {
string m_name;
string m_tag; // Holds the string of the verilator tag -- used in XML output.
uint32_t m_declTokenNum; // Declaration token number
bool m_attrPublic = false;
bool m_isHideLocal : 1; // Verilog local
bool m_isHideProtected : 1; // Verilog protected
@ -1838,6 +1839,7 @@ public:
AstNodeDType* dtp)
: ASTGEN_SUPER_Typedef(fl)
, m_name{name}
, m_declTokenNum{fl->tokenNum()}
, m_isHideLocal{false}
, m_isHideProtected{false} {
childDTypep(dtp); // Only for parser
@ -1852,6 +1854,10 @@ public:
return dtypep() ? dtypep() : childDTypep();
}
// METHODS
uint32_t declTokenNum() const override { return m_declTokenNum; }
void declTokenNumSetMin(uint32_t tokenNum) override {
m_declTokenNum = std::min(m_declTokenNum, tokenNum);
}
string name() const override VL_MT_STABLE { return m_name; }
bool maybePointedTo() const override VL_MT_SAFE { return true; }
bool hasDType() const override VL_MT_SAFE { return true; }
@ -1868,15 +1874,20 @@ public:
class AstTypedefFwd final : public AstNode {
// Forward declaration of a type; stripped after netlist parsing is complete
string m_name;
const VFwdType m_fwdType; // Forward type for lint check
public:
AstTypedefFwd(FileLine* fl, const string& name)
AstTypedefFwd(FileLine* fl, const string& name, const VFwdType& fwdType)
: ASTGEN_SUPER_TypedefFwd(fl)
, m_name{name} {}
, m_name{name}
, m_fwdType{fwdType} {}
ASTGEN_MEMBERS_AstTypedefFwd;
// METHODS
string name() const override VL_MT_STABLE { return m_name; }
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
bool maybePointedTo() const override VL_MT_SAFE { return true; }
VFwdType fwdType() const { return m_fwdType; }
};
class AstUdpTable final : public AstNode {
// @astgen op1 := linesp : List[AstUdpTableLine]
@ -2505,6 +2516,7 @@ class AstClass final : public AstNodeModule {
// @astgen op4 := extendsp : List[AstClassExtends]
// MEMBERS
// @astgen ptr := m_classOrPackagep : Optional[AstClassPackage] // Package to be emitted with
uint32_t m_declTokenNum; // Declaration token number
VBaseOverride m_baseOverride; // BaseOverride (inital/final/extends)
bool m_extended = false; // Is extension or extended by other classes
bool m_interfaceClass = false; // Interface class
@ -2514,7 +2526,8 @@ class AstClass final : public AstNodeModule {
public:
AstClass(FileLine* fl, const string& name, const string& libname)
: ASTGEN_SUPER_Class(fl, name, libname) {}
: ASTGEN_SUPER_Class(fl, name, libname)
, m_declTokenNum{fl->tokenNum()} {}
ASTGEN_MEMBERS_AstClass;
string verilogKwd() const override { return "class"; }
bool maybePointedTo() const override VL_MT_SAFE { return true; }
@ -2538,6 +2551,10 @@ public:
// Return true if this class is an extension of base class (SLOW)
// Accepts nullptrs
static bool isClassExtendedFrom(const AstClass* refClassp, const AstClass* baseClassp);
uint32_t declTokenNum() const override { return m_declTokenNum; }
void declTokenNumSetMin(uint32_t tokenNum) override {
m_declTokenNum = std::min(m_declTokenNum, tokenNum);
}
void baseOverride(const VBaseOverride& flag) { m_baseOverride = flag; }
VBaseOverride baseOverride() const { return m_baseOverride; }
// Return the lowest class extended from, or this class

View File

@ -1771,6 +1771,7 @@ void AstClass::dump(std::ostream& str) const {
if (useVirtualPublic()) str << " [VIRPUB]";
}
void AstClass::dumpJson(std::ostream& str) const {
// dumpJsonNumFunc(str, declTokenNum); // Not dumped as adding token changes whole file
dumpJsonBoolFunc(str, isExtended);
dumpJsonBoolFunc(str, isInterfaceClass);
dumpJsonBoolFunc(str, isVirtual);
@ -2161,9 +2162,18 @@ void AstTypedef::dump(std::ostream& str) const {
}
}
void AstTypedef::dumpJson(std::ostream& str) const {
// dumpJsonNumFunc(str, declTokenNum); // Not dumped as adding token changes whole file
dumpJsonBoolFunc(str, attrPublic);
dumpJsonGen(str);
}
void AstTypedefFwd::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [" << fwdType().ascii() << "]";
}
void AstTypedefFwd::dumpJson(std::ostream& str) const {
dumpJsonStr(str, "fwdType", fwdType().ascii());
dumpJsonGen(str);
}
void AstNodeRange::dump(std::ostream& str) const { this->AstNode::dump(str); }
void AstNodeRange::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
void AstRange::dump(std::ostream& str) const {

View File

@ -293,6 +293,8 @@ void FileLine::lineDirectiveParse(const char* textp, string& filenameRef, int& l
}
void FileLine::forwardToken(const char* textp, size_t size, bool trackLines) {
static int s_tokenNum = 1;
m_tokenNum = s_tokenNum++;
for (const char* sp = textp; size && *sp; ++sp, --size) {
if (*sp == '\n') {
if (trackLines) linenoInc();

View File

@ -146,7 +146,7 @@ class FileLine final {
bool m_waive : 1; // Waive warning - pack next to the line number to save 8 bytes of storage
unsigned m_contentLineno : 31; // Line number within source stream
// 64-bit word # 1
uint32_t m_pad = 0; // space for project coming soon
uint32_t m_tokenNum = 0; // Token number in processing order
int m_firstLineno = 0; // `line corrected token's first line number
// 64-bit word # 2
uint32_t m_firstColumn : 24; // `line corrected token's first column number
@ -197,6 +197,7 @@ public:
, m_filenameno{from.m_filenameno}
, m_waive{from.m_waive}
, m_contentLineno{from.m_contentLineno}
, m_tokenNum{from.m_tokenNum}
, m_firstLineno{from.m_firstLineno}
, m_firstColumn{from.m_firstColumn}
, m_lastColumn{from.m_lastColumn}
@ -210,6 +211,7 @@ public:
, m_filenameno{fromp->m_filenameno}
, m_waive{fromp->m_waive}
, m_contentLineno{fromp->m_contentLineno}
, m_tokenNum{fromp->m_tokenNum}
, m_firstLineno{fromp->m_firstLineno}
, m_firstColumn{fromp->m_firstColumn}
, m_lastColumn{fromp->m_lastColumn}
@ -272,6 +274,7 @@ public:
}
// Advance last line/column based on given text
void forwardToken(const char* textp, size_t size, bool trackLines = true);
uint32_t tokenNum() const VL_MT_SAFE { return m_tokenNum; }
int firstLineno() const VL_MT_SAFE { return m_firstLineno; }
int firstColumn() const VL_MT_SAFE { return static_cast<int>(m_firstColumn); }
int lastLineno() const VL_MT_SAFE { return m_firstLineno + m_lastLinenoAdder; }
@ -386,9 +389,10 @@ public:
/// Simplified information vs warnContextPrimary() to make dump clearer
string warnContextSecondary() const { return warnContext(); }
bool operator==(const FileLine& rhs) const {
return (m_firstLineno == rhs.m_firstLineno && m_firstColumn == rhs.m_firstColumn
&& m_lastLinenoAdder == rhs.m_lastLinenoAdder && m_lastColumn == rhs.m_lastColumn
&& m_filenameno == rhs.m_filenameno && m_msgEnIdx == rhs.m_msgEnIdx);
return (m_tokenNum == rhs.m_tokenNum && m_firstLineno == rhs.m_firstLineno
&& m_firstColumn == rhs.m_firstColumn && m_lastLinenoAdder == rhs.m_lastLinenoAdder
&& m_lastColumn == rhs.m_lastColumn && m_filenameno == rhs.m_filenameno
&& m_msgEnIdx == rhs.m_msgEnIdx);
}
bool equalFirstLineCol(const FileLine& rhs) const {
return (m_filenameno == rhs.m_filenameno && m_firstLineno == rhs.m_firstLineno
@ -408,6 +412,8 @@ public:
for (size_t i = 0; i < msgEn().size(); ++i) {
if (msgEn().test(i) != rhs.msgEn().test(i)) return rhs.msgEn().test(i) ? -1 : 1;
}
// TokenNum is compared last as makes more logical sort order by file/line first
if (m_tokenNum != rhs.m_tokenNum) return (m_tokenNum < rhs.m_tokenNum) ? -1 : 1;
return 0; // (*this) and rhs are equivalent
}

View File

@ -310,6 +310,8 @@ public:
<< nodep->warnContextPrimary() << '\n'
<< fnodep->warnOther() << "... Location of original declaration\n"
<< fnodep->warnContextSecondary());
nodep->declTokenNumSetMin(0); // Disable checking forward typedefs
fnodep->declTokenNumSetMin(0); // Disable checking forward typedefs
} else {
nodep->v3error("Unsupported in C: "
<< ucfirst(nodeTextType(nodep)) << " has the same name as "
@ -2063,8 +2065,13 @@ class LinkDotParamVisitor final : public VNVisitor {
}
void visit(AstTypedefFwd* nodep) override { // ParamVisitor::
VSymEnt* const foundp = m_statep->getNodeSym(nodep)->findIdFallback(nodep->name());
if (!foundp && v3Global.opt.pedantic()
&& nodep->name() != "process") { // Process is dangling as isn't implemented yet
if (foundp) {
// If the typedef was earlier in source order (tokenNum), then remember that earlier
// point to avoid error something wasn't declared
// Might be forward declaring something odd (with declTokenNumSetMin not implemented,
// but should be harmless to ignore as this is just for error detection
foundp->nodep()->declTokenNumSetMin(nodep->fileline()->tokenNum());
} else if (v3Global.opt.pedantic()) {
// We only check it was ever really defined in pedantic mode, as it
// might have been in a header file referring to a module we never
// needed so there are false positives
@ -2645,6 +2652,23 @@ class LinkDotResolveVisitor final : public VNVisitor {
symIterateNull(nodep, m_statep->getNodeSym(nodep));
}
void checkDeclOrder(AstNode* nodep, AstNode* declp) {
uint32_t declTokenNum = declp->declTokenNum();
if (!declTokenNum) return; // Not implemented/valid on this object
if (nodep->fileline()->tokenNum() < declTokenNum) {
UINFO(1, "Related node " << nodep->fileline()->tokenNum() << " " << nodep);
UINFO(1, "Related decl " << declTokenNum << " " << declp);
nodep->v3error("Reference to "
<< nodep->prettyNameQ() << " before declaration (IEEE 1800-2023 6.18)\n"
<< nodep->warnMore()
<< "... Suggest move the declaration before the reference, or use a "
"forward typedef\n"
<< nodep->warnContextPrimary() << '\n'
<< declp->warnOther() << "... Location of original declaration\n"
<< declp->warnContextSecondary());
}
}
void replaceWithCheckBreak(AstNode* oldp, AstNodeDType* newp) {
// Flag now to avoid V3Broken throwing an internal error
if (oldp->wouldBreak(newp)) {
@ -3505,6 +3529,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
ok = m_ds.m_dotPos == DP_NONE || m_ds.m_dotPos == DP_SCOPE;
if (ok) {
AstRefDType* const refp = new AstRefDType{nodep->fileline(), nodep->name()};
// Don't check if typedef is to a <type T>::<reference> as might not be
// resolved yet
if (m_ds.m_dotPos == DP_NONE) checkDeclOrder(nodep, defp);
refp->typedefp(defp);
if (VN_IS(nodep->backp(), SelExtract)) {
m_packedArrayDtp = refp;
@ -4551,6 +4578,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
foundp = m_curSymp->findIdFlat(nodep->name());
}
if (AstTypedef* const defp = foundp ? VN_CAST(foundp->nodep(), Typedef) : nullptr) {
// Don't check if typedef is to a <type T>::<reference> as might not be resolved
// yet
if (!nodep->classOrPackagep()) checkDeclOrder(nodep, defp);
nodep->typedefp(defp);
nodep->classOrPackagep(foundp->classOrPackagep());
} else if (AstParamTypeDType* const defp
@ -4566,6 +4596,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
nodep->classOrPackagep(foundp->classOrPackagep());
}
} else if (AstClass* const defp = foundp ? VN_CAST(foundp->nodep(), Class) : nullptr) {
// Don't check if typedef is to a <type T>::<reference> as might not be resolved
// yet
if (!nodep->classOrPackagep()) checkDeclOrder(nodep, defp);
AstPin* const paramsp = nodep->paramsp();
if (paramsp) paramsp->unlinkFrBackWithNext();
AstClassRefDType* const newp

View File

@ -782,19 +782,7 @@ class ParamProcessor final {
// Constify may have caused pinp->exprp to change
rawTypep = VN_AS(pinp->exprp(), NodeDType);
exprp = rawTypep->skipRefToNonRefp();
bool ok = true;
switch (modvarp->fwdType()) {
case VFwdType::NONE: ok = true; break;
case VFwdType::ENUM: ok = VN_IS(exprp, EnumDType); break;
case VFwdType::STRUCT: ok = VN_IS(exprp, StructDType); break;
case VFwdType::UNION: ok = VN_IS(exprp, UnionDType); break;
case VFwdType::CLASS: ok = VN_IS(exprp, ClassRefDType); break;
case VFwdType::INTERFACE_CLASS: // TODO: Over permissive for now:
ok = VN_IS(exprp, ClassRefDType);
break;
default: modvarp->v3fatalSrc("Bad case");
}
if (!ok) {
if (!modvarp->fwdType().isNodeCompatible(exprp)) {
pinp->v3error("Parameter type expression type "
<< exprp->prettyDTypeNameQ()
<< " violates parameter's forwarding type '"

View File

@ -260,7 +260,7 @@ AstVar* V3ParseGrammar::createVariable(FileLine* fileline, const string& name,
}
if (VN_IS(dtypep, ParseTypeDType)) {
// Parser needs to know what is a type
AstNode* const newp = new AstTypedefFwd{fileline, name};
AstNode* const newp = new AstTypedefFwd{fileline, name, VFwdType::NONE};
AstNode::addNext<AstNode, AstNode>(nodep, newp);
}
// Don't set dtypep in the ranging;

View File

@ -183,8 +183,8 @@ public:
PARSEP->tagNodep(nodep);
return nodep;
}
AstNode* createTypedefFwd(FileLine* fl, const string& name) {
AstTypedefFwd* const nodep = new AstTypedefFwd{fl, name};
AstNode* createTypedefFwd(FileLine* fl, const string& name, const VFwdType& fwdType) {
AstTypedefFwd* const nodep = new AstTypedefFwd{fl, name, fwdType};
PARSEP->tagNodep(nodep);
return nodep;
}
@ -2695,15 +2695,15 @@ type_declaration<nodep>: // ==IEEE: type_declaration
| yTYPEDEF idAny/*interface*/ '.' idAny/*type*/ idAny/*type*/ dtypeAttrListE ';'
{ $$ = nullptr; BBUNSUP($1, "Unsupported: SystemVerilog 2005 typedef in this context"); }
// // idAny as also allows redeclaring same typedef again
| yTYPEDEF idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>2, *$2); }
| yTYPEDEF idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>2, *$2, VFwdType::NONE); }
// // IEEE: expanded forward_type to prevent conflict
| yTYPEDEF yENUM idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>3, *$3); }
| yTYPEDEF ySTRUCT idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>3, *$3); }
| yTYPEDEF yUNION idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>3, *$3); }
| yTYPEDEF yCLASS idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>3, *$3); }
| yTYPEDEF yINTERFACE yCLASS idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>4, *$4); }
| yTYPEDEF yENUM idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>3, *$3, VFwdType::ENUM); }
| yTYPEDEF ySTRUCT idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>3, *$3, VFwdType::STRUCT); }
| yTYPEDEF yUNION idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>3, *$3, VFwdType::UNION); }
| yTYPEDEF yCLASS idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>3, *$3, VFwdType::CLASS); }
| yTYPEDEF yINTERFACE yCLASS idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>4, *$4, VFwdType::INTERFACE_CLASS); }
//
| yTYPEDEF error idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>3, *$3); }
| yTYPEDEF error idAny ';' { $$ = GRAMMARP->createTypedefFwd($<fl>3, *$3, VFwdType::NONE); }
;
dtypeAttrListE<nodep>:

View File

@ -534,7 +534,7 @@
"childDTypep": [
{"type":"PARSETYPEDTYPE","name":"","addr":"(PI)","loc":"d,32:20,32:24","dtypep":"UNLINKED","generic":false}
],"delayp": [],"valuep": [],"attrsp": []},
{"type":"TYPEDEFFWD","name":"T","addr":"(QI)","loc":"d,32:25,32:26"},
{"type":"TYPEDEFFWD","name":"T","addr":"(QI)","loc":"d,32:25,32:26","fwdType":"none"},
{"type":"VAR","name":"m_bound","addr":"(RI)","loc":"d,33:21,33:28","dtypep":"UNLINKED","origName":"m_bound","isSc":false,"isPrimaryIO":false,"direction":"NONE","isConst":false,"isPullup":false,"isPulldown":false,"isUsedClock":false,"isSigPublic":false,"isLatched":false,"isUsedLoopIdx":false,"noReset":false,"attrIsolateAssign":false,"attrFileDescr":false,"isDpiOpenArray":false,"isFuncReturn":false,"isFuncLocal":false,"attrClocker":"UNKNOWN","lifetime":"NONE","varType":"VAR","isSigUserRdPublic":false,"isSigUserRWPublic":false,"isGParam":false,"isParam":false,"attrScBv":false,"attrSFormat":false,"ignorePostWrite":false,"ignoreSchedWrite":false,"sensIfacep":"UNLINKED",
"childDTypep": [
{"type":"BASICDTYPE","name":"int","addr":"(SI)","loc":"d,33:17,33:20","dtypep":"(SI)","keyword":"int","range":"31:0","generic":false,"rangep": []}

16
test_regress/t/t_typedef_fwd.py Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.lint()
test.passes()

View File

@ -0,0 +1,42 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
package P;
`ifndef TEST_NO_TYPEDEFS
typedef enum enum_t;
typedef struct struct_t;
typedef union union_t;
typedef class ClsB;
typedef interface class IfC;
typedef generic_t;
`endif
class ClsA;
enum_t m_e; // <--- Error need forward decl (if TEST_NO_TYPEDEFS)
struct_t m_s; // <--- Error need forward decl (if TEST_NO_TYPEDEFS)
union_t m_u; // <--- Error need forward decl (if TEST_NO_TYPEDEFS)
ClsB m_b; // <--- Error need forward decl (if TEST_NO_TYPEDEFS)
IfC m_i; // <--- Error need forward decl (if TEST_NO_TYPEDEFS)
generic_t m_g; // <--- Error need forward decl (if TEST_NO_TYPEDEFS)
endclass
typedef enum {N = 0} enum_t;
typedef struct packed {int s;} struct_t;
typedef union packed {int s;} union_t;
class ClsB;
endclass
interface class IfC;
endclass
typedef int generic_t;
endpackage
module t;
endmodule

View File

@ -0,0 +1,44 @@
%Error: t/t_typedef_fwd.v:18:5: Reference to 'enum_t' before declaration (IEEE 1800-2023 6.18)
: ... Suggest move the declaration before the reference, or use a forward typedef
18 | enum_t m_e;
| ^~~~~~
t/t_typedef_fwd.v:26:24: ... Location of original declaration
26 | typedef enum {N = 0} enum_t;
| ^~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_typedef_fwd.v:19:5: Reference to 'struct_t' before declaration (IEEE 1800-2023 6.18)
: ... Suggest move the declaration before the reference, or use a forward typedef
19 | struct_t m_s;
| ^~~~~~~~
t/t_typedef_fwd.v:28:34: ... Location of original declaration
28 | typedef struct packed {int s;} struct_t;
| ^~~~~~~~
%Error: t/t_typedef_fwd.v:20:5: Reference to 'union_t' before declaration (IEEE 1800-2023 6.18)
: ... Suggest move the declaration before the reference, or use a forward typedef
20 | union_t m_u;
| ^~~~~~~
t/t_typedef_fwd.v:29:33: ... Location of original declaration
29 | typedef union packed {int s;} union_t;
| ^~~~~~~
%Error: t/t_typedef_fwd.v:21:5: Reference to 'ClsB' before declaration (IEEE 1800-2023 6.18)
: ... Suggest move the declaration before the reference, or use a forward typedef
21 | ClsB m_b;
| ^~~~
t/t_typedef_fwd.v:31:3: ... Location of original declaration
31 | class ClsB;
| ^~~~~
%Error: t/t_typedef_fwd.v:22:5: Reference to 'IfC' before declaration (IEEE 1800-2023 6.18)
: ... Suggest move the declaration before the reference, or use a forward typedef
22 | IfC m_i;
| ^~~
t/t_typedef_fwd.v:34:13: ... Location of original declaration
34 | interface class IfC;
| ^~~~~
%Error: t/t_typedef_fwd.v:23:5: Reference to 'generic_t' before declaration (IEEE 1800-2023 6.18)
: ... Suggest move the declaration before the reference, or use a forward typedef
23 | generic_t m_g;
| ^~~~~~~~~
t/t_typedef_fwd.v:37:15: ... Location of original declaration
37 | typedef int generic_t;
| ^~~~~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,17 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.top_filename = 't/t_typedef_fwd.v'
test.lint(v_flags2=['+define+TEST_NO_TYPEDEFS'], fails=True, expect_filename=test.golden_filename)
test.passes()