From 1ea10ba71c2e598687c43d571909b0f81418ceba Mon Sep 17 00:00:00 2001 From: Sergey Chusov <44680536+serge0699@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:58:35 +0300 Subject: [PATCH] Fix class scope '::' reference through an inherited type parameter (#7844) --- docs/CONTRIBUTORS | 1 + src/V3LinkDot.cpp | 17 +++++-- test_regress/t/t_class_extends_param_scope.py | 18 +++++++ test_regress/t/t_class_extends_param_scope.v | 47 +++++++++++++++++++ 4 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 test_regress/t/t_class_extends_param_scope.py create mode 100644 test_regress/t/t_class_extends_param_scope.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index f0e8b1c98..763ac8ac0 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -258,6 +258,7 @@ Samuel Riedel Sean Cross Sebastien Van Cauwenberghe Secturion +Sergey Chusov Sergey Fedorov Sergi Granell Seth Pellegrino diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 7dd5fe3ea..33e2d38b5 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1013,18 +1013,23 @@ public: if (checkUnresolvedRef(VN_CAST(dtypep, RefDType))) return true; } else if (const AstParamTypeDType* const paramTypep = VN_CAST(symp->nodep(), ParamTypeDType)) { - // ParamTypeDType child may be wrapped in RequireDType or unwrapped + // Before V3Param the declared default is in childDTypep (possibly + // wrapped in a RequireDType); after V3Param it is consumed and the + // bound type is the resolved data type, e.g. a type parameter + // inherited from a specialized base class (REQ #(Item) -> class Item). AstNode* childp = paramTypep->childDTypep(); if (const AstRequireDType* const reqp = VN_CAST(childp, RequireDType)) { childp = reqp->lhsp(); } - if (isValidTypeNode(childp)) return true; - if (checkUnresolvedRef(VN_CAST(childp, RefDType))) return true; + const AstNode* const checkp = childp ? childp : paramTypep->skipRefp(); + if (isValidTypeNode(checkp)) return true; + if (checkUnresolvedRef(VN_CAST(checkp, RefDType))) return true; } return false; } VSymEnt* resolveClassOrPackage(VSymEnt* lookSymp, AstClassOrPackageRef* nodep, bool fallback, - bool classOnly, const string& forWhat) { + bool classOnly, const string& forWhat, + bool deferIfUnresolved = false) { if (nodep->classOrPackageSkipp()) return getNodeSym(nodep->classOrPackageSkipp()); VSymEnt* foundp; VSymEnt* searchSymp = lookSymp; @@ -1050,6 +1055,7 @@ public: nodep->classOrPackageNodep(foundp->nodep()); return foundp; } + if (deferIfUnresolved) return nullptr; const string suggest = suggestSymFallback(lookSymp, nodep->name(), LinkNodeMatcherClassOrPackage{}); nodep->v3error((classOnly ? "Class" : "Package/class") @@ -4758,8 +4764,9 @@ class LinkDotResolveVisitor final : public VNVisitor { VL_RESTORER(m_pinSymp); if (!nodep->classOrPackageSkipp() && nodep->name() != "local::") { + const bool deferIfUnresolved = m_statep->forPrimary() && m_insideClassExtParam; m_statep->resolveClassOrPackage(m_ds.m_dotSymp, nodep, m_ds.m_dotPos != DP_PACKAGE, - false, ":: reference"); + false, ":: reference", deferIfUnresolved); } // ClassRef's have pins, so track diff --git a/test_regress/t/t_class_extends_param_scope.py b/test_regress/t/t_class_extends_param_scope.py new file mode 100644 index 000000000..3cc73805c --- /dev/null +++ b/test_regress/t/t_class_extends_param_scope.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_extends_param_scope.v b/test_regress/t/t_class_extends_param_scope.v new file mode 100644 index 000000000..d60ac9381 --- /dev/null +++ b/test_regress/t/t_class_extends_param_scope.v @@ -0,0 +1,47 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Sergey Chusov +// SPDX-License-Identifier: CC0-1.0 + +// A factory-like static method, as used by UVM's type_id::create(). +class registry #(type T = int); + static function T create(); + T r = new; + return r; + endfunction +endclass + +class item; + int val; + typedef registry#(item) type_id; + function new(); + val = 42; + endfunction +endclass + +class seq_base #(type REQ = int, type RSP = REQ); + REQ req; +endclass + +// Non-parameterized class extending a parameterized base. +// REQ is a type parameter inherited from the (specialized) base class and is +// here used as a '::' class scope: REQ::type_id::create(). Resolving REQ in +// this position requires deferring until after V3Param has bound REQ to item. +class seq extends seq_base #(item); + function int make(); + REQ r; + r = REQ::type_id::create(); + return r.val; + endfunction +endclass + +module t; + seq s; + initial begin + s = new; + if (s.make() != 42) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule