diff --git a/doc/whatsnew/fragments/8603.bugfix b/doc/whatsnew/fragments/8603.bugfix
new file mode 100644
index 0000000000..1a1025c4b3
--- /dev/null
+++ b/doc/whatsnew/fragments/8603.bugfix
@@ -0,0 +1,3 @@
+``pyreverse``: added escaping of vertical bar character in annotation labels produced by DOT printer to ensure it is not treated as field separator of record-based nodes.
+
+Closes #8603
diff --git a/pylint/pyreverse/dot_printer.py b/pylint/pyreverse/dot_printer.py
index 077e0552da..99cb17e97e 100644
--- a/pylint/pyreverse/dot_printer.py
+++ b/pylint/pyreverse/dot_printer.py
@@ -121,11 +121,19 @@ def _build_label_for_node(self, properties: NodeProperties) -> str:
)
label += rf"{method_name}({', '.join(args)})"
if func.returns:
- label += ": " + get_annotation_label(func.returns)
+ annotation_label = get_annotation_label(func.returns)
+ label += ": " + self._escape_annotation_label(annotation_label)
label += rf"{HTMLLabels.LINEBREAK_LEFT.value}"
label += "}"
return label
+ def _escape_annotation_label(self, annotation_label: str) -> str:
+ # Escape vertical bar characters to make them appear as a literal characters
+ # otherwise it gets treated as field separator of record-based nodes
+ annotation_label = annotation_label.replace("|", r"\|")
+
+ return annotation_label
+
def emit_edge(
self,
from_node: str,
diff --git a/tests/data/nullable_pattern.py b/tests/data/nullable_pattern.py
new file mode 100644
index 0000000000..bd730bbd61
--- /dev/null
+++ b/tests/data/nullable_pattern.py
@@ -0,0 +1,10 @@
+""" docstring for file nullable_pattern.py """
+from typing import Optional
+
+class NullablePatterns:
+ def return_nullable_1(self) -> int | None:
+ """ Nullable return type using the | operator as mentioned in PEP 604, see https://peps.python.org/pep-0604 """
+ pass
+
+ def return_nullable_2(self) -> Optional[int]:
+ pass
diff --git a/tests/pyreverse/data/classes_No_Name.dot b/tests/pyreverse/data/classes_No_Name.dot
index a598ab6d9a..2e8830fa0b 100644
--- a/tests/pyreverse/data/classes_No_Name.dot
+++ b/tests/pyreverse/data/classes_No_Name.dot
@@ -7,6 +7,7 @@ charset="utf-8"
"data.suppliermodule_test.DoNothing2" [color="black", fontcolor="black", label=<{DoNothing2|
|}>, shape="record", style="solid"];
"data.suppliermodule_test.DoSomething" [color="black", fontcolor="black", label=<{DoSomething|my_int : Optional[int]
my_int_2 : Optional[int]
my_string : str
|do_it(new_int: int): int
}>, shape="record", style="solid"];
"data.suppliermodule_test.Interface" [color="black", fontcolor="black", label=<{Interface|
|get_value()
set_value(value)
}>, shape="record", style="solid"];
+"data.nullable_pattern.NullablePatterns" [color="black", fontcolor="black", label=<{NullablePatterns|
|return_nullable_1(): int \| None
return_nullable_2(): Optional[int]
}>, shape="record", style="solid"];
"data.property_pattern.PropertyPatterns" [color="black", fontcolor="black", label=<{PropertyPatterns|prop1
prop2
|}>, shape="record", style="solid"];
"data.clientmodule_test.Specialization" [color="black", fontcolor="black", label=<{Specialization|TYPE : str
relation
relation2
top : str
|from_value(value: int)
increment_value(): None
transform_value(value: int): int
}>, shape="record", style="solid"];
"data.clientmodule_test.Specialization" -> "data.clientmodule_test.Ancestor" [arrowhead="empty", arrowtail="none"];
diff --git a/tests/pyreverse/data/classes_No_Name.html b/tests/pyreverse/data/classes_No_Name.html
index 602f2e3b7a..bed9a8d14d 100644
--- a/tests/pyreverse/data/classes_No_Name.html
+++ b/tests/pyreverse/data/classes_No_Name.html
@@ -26,6 +26,10 @@
get_value()*
set_value(value)*
}
+ class NullablePatterns {
+ return_nullable_1()* int | None
+ return_nullable_2()* Optional[int]
+ }
class PropertyPatterns {
prop1
prop2
@@ -44,7 +48,7 @@
DoNothing --* Ancestor : cls_member
DoNothing --* Specialization : relation
DoNothing2 --o Specialization : relation2
-
+