Fix `random.sample` crash when cloning `ClassDef` and `FunctionDef` nodes (#2937)
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
diff --git a/ChangeLog b/ChangeLog
index a661b96..d92379a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -56,6 +56,10 @@
Closes #2518
+* Fix ``random.sample`` crash when cloning ``ClassDef`` or ``FunctionDef`` nodes.
+
+ Closes #2923
+
What's New in astroid 4.0.3?
============================
Release date: 2026-01-03
diff --git a/astroid/brain/brain_random.py b/astroid/brain/brain_random.py
index d83fda1..849a9f4 100644
--- a/astroid/brain/brain_random.py
+++ b/astroid/brain/brain_random.py
@@ -4,6 +4,7 @@
from __future__ import annotations
+import inspect
import random
from astroid import nodes
@@ -30,11 +31,19 @@
"end_col_offset": node.end_col_offset,
}
postinit_params = {param: getattr(node, param) for param in _astroid_fields}
- if other_fields:
- init_params.update({param: getattr(node, param) for param in other_fields})
+
+ valid_init_params = set(inspect.signature(cls.__init__).parameters)
+ for param in other_fields:
+ if param in valid_init_params:
+ init_params[param] = getattr(node, param)
+
new_node = cls(**init_params)
if hasattr(node, "postinit") and _astroid_fields:
new_node.postinit(**postinit_params)
+
+ for param in other_fields:
+ if param not in valid_init_params:
+ setattr(new_node, param, getattr(node, param))
return new_node
diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py
index 83c8772..b66cf0e 100644
--- a/tests/brain/test_brain.py
+++ b/tests/brain/test_brain.py
@@ -1091,6 +1091,40 @@
inferred = next(node.infer())
assert inferred is astroid.Uninferable
+ def test_no_crash_on_classdef_clone(self) -> None:
+ """Test that random.sample does not crash when cloning ClassDef nodes.
+
+ Regression test for https://github.com/pylint-dev/astroid/issues/2923
+ """
+ node = astroid.extract_node(
+ """
+ import random
+ random.sample([dict] * 2, 1) #@
+ """
+ )
+ inferred = next(node.infer())
+ assert isinstance(inferred, nodes.List)
+ assert len(inferred.elts) == 1
+ assert isinstance(inferred.elts[0], nodes.ClassDef)
+ assert inferred.elts[0].name == "dict"
+
+ def test_no_crash_on_functiondef_clone(self) -> None:
+ """Test that random.sample does not crash when cloning FunctionDef nodes.
+
+ Regression test for https://github.com/pylint-dev/astroid/issues/2923
+ """
+ node = astroid.extract_node(
+ """
+ import random
+ random.sample([len] * 2, 1) #@
+ """
+ )
+ inferred = next(node.infer())
+ assert isinstance(inferred, nodes.List)
+ assert len(inferred.elts) == 1
+ assert isinstance(inferred.elts[0], nodes.FunctionDef)
+ assert inferred.elts[0].name == "len"
+
class SubprocessTest(unittest.TestCase):
"""Test subprocess brain"""