Fix class-qualified self reference to a typedef in a parameterized class (#8348) (#8349)

This commit is contained in:
Dragon-Git
2026-09-14 10:54:15 -04:00
committed by GitHub
parent 6a57cf10f4
commit 377865e2a0
3 changed files with 135 additions and 0 deletions
+52
View File
@@ -2367,6 +2367,22 @@ class ParamClassRefDTypeRelinkVisitor final : public VNVisitor {
}
}
// A class's name before specialization (e.g. "holder" for "holder__Tz1").
static string classOrigName(const AstClass* classp) {
return classp->origName().empty() ? classp->name() : classp->origName();
}
// Find 'name' in classp or any base class (findTypedefInModule() searches
// only the class itself).
static AstTypedef* findTypedefWithBases(AstClass* classp, const string& name) {
for (AstClass* cp = classp; cp; cp = cp->extendsp() ? cp->extendsp()->classp() : nullptr) {
if (AstTypedef* const tdp = V3LinkDotIfaceCapture::findTypedefInModule(cp, name)) {
if (tdp->subDTypep()) return tdp;
}
}
return nullptr;
}
// Re-resolve REFDTYPE.typedefp/refDTypep using the containing module's
// own resolved typedef chain. The eager retargeting in deepCloneModule
// blindly retargets every captured entry to whichever sibling clone is
@@ -2377,10 +2393,46 @@ class ParamClassRefDTypeRelinkVisitor final : public VNVisitor {
// those local typedefs as ground truth.
void retargetRefDType(AstRefDType* refp) {
if (!m_ownerModp) return;
// IEEE 1800-2023 8.25.1: a bare class-qualified reference to the class
// being compiled means the current specialization, but V3LinkDot binds
// it to the default instance, so a typedef reached through it widens
// with the template's defaults (#8348). Rebind it to the
// specialization's own typedef.
if (AstClass* const ownerClassp = VN_CAST(m_ownerModp, Class)) {
if (!ownerClassp->hasGParam() && !refp->paramsp()) {
// What the reference resolves to: its typedef's owner if
// linked, else the class it is qualified by.
const AstClass* refClassp = nullptr;
if (AstTypedef* const tdp = refp->typedefp()) {
refClassp = VN_CAST(V3LinkDotIfaceCapture::findOwnerModule(tdp), Class);
} else if (const AstClassOrPackageRef* const classRefp
= VN_CAST(refp->classOrPackageOpp(), ClassOrPackageRef)) {
refClassp = VN_CAST(classRefp->classOrPackageSkipp(), Class);
}
// Same originating class => bare self reference (another
// specialization would carry #(), rejected above).
if (refClassp && refClassp != ownerClassp
&& classOrigName(refClassp) == classOrigName(ownerClassp)) {
AstTypedef* const selfTdp = findTypedefWithBases(ownerClassp, refp->name());
if (selfTdp) {
UINFO(9, "post-param REFDTYPE self-reference retarget: "
<< refp << " from " << refClassp->name() << " to "
<< ownerClassp->name());
refp->typedefp(selfTdp);
refp->classOrPackagep(V3LinkDotIfaceCapture::findOwnerModule(selfTdp));
refp->refDTypep(selfTdp->subDTypep());
return;
}
}
}
}
AstTypedef* const oldTdp = refp->typedefp();
if (!oldTdp) return;
AstClass* const oldOwnerp = VN_CAST(V3LinkDotIfaceCapture::findOwnerModule(oldTdp), Class);
if (!oldOwnerp) return;
ensureOwnerMap();
if (m_origNameToClone.empty()) return;
const std::string origName
+18
View File
@@ -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: 2026 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()
@@ -0,0 +1,65 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
// IEEE 1800-2023 8.25.1: inside a parameterized class, a bare class name used
// without #() denotes the current specialization. A typedef reached through
// such a self reference must resolve against the specialization's type
// parameters, not the template's defaults. Covers both a typedef declared in
// the class itself and one inherited from a base class.
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package pkg;
class item_base #(type DATA = int);
DATA value;
endclass
class holder #(type FIN = int);
typedef item_base#(FIN) my_item_t;
virtual function holder::my_item_t get_bare();
my_item_t item;
return item;
endfunction
endclass
// Same, but the typedef is inherited from a base class instead.
class base_holder #(type FIN = int);
typedef item_base#(FIN) my_item_t;
endclass
class derived #(type FIN = int) extends base_holder#(FIN);
virtual function derived::my_item_t get_bare();
my_item_t item;
return item;
endfunction
endclass
endpackage
module t;
import pkg::*;
// Non-default parameters, so the specialization differs from the template
// default and the bug is observable.
holder#(byte) h;
derived#(shortint) d;
initial begin
automatic holder#(byte)::my_item_t got;
automatic derived#(shortint)::my_item_t got_inherited;
h = new;
got = h.get_bare();
`checkd($bits(got.value), 8);
d = new;
got_inherited = d.get_bare();
`checkd($bits(got_inherited.value), 16);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule