diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py index a2ca95514..9eec700da 100644 --- a/astroid/brain/brain_builtin_inference.py +++ b/astroid/brain/brain_builtin_inference.py @@ -842,15 +842,35 @@ def infer_str(node, context: InferenceContext | None = None) -> nodes.Const: :param nodes.Call node: str() call to infer :param context.InferenceContext: node context - :rtype nodes.Const: a Const containing an empty string + :rtype nodes.Const: + a Const containing a stringified value of str() call if possible, else an empty string """ call = arguments.CallSite.from_call(node, context=context) if call.keyword_arguments: raise UseInferenceDefault("TypeError: str() must take no keyword arguments") + + fallback = nodes.Const("") + + if not call.positional_arguments: + return fallback + + # Accept only if all inferred values resolve to the same string + candidates: set[str] = set() try: - return nodes.Const("") - except (AstroidTypeError, InferenceError) as exc: - raise UseInferenceDefault(str(exc)) from exc + for inferred in call.positional_arguments[0].infer(context=context): + if not isinstance(inferred, nodes.Const): + return fallback + + try: + candidates.add(str(inferred.value)) + except ValueError: + return fallback + except InferenceError: + return fallback + + if len(candidates) == 1: + return nodes.Const(candidates.pop()) + return fallback def infer_int(node, context: InferenceContext | None = None): diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py index 0b60ac264..b661f4495 100644 --- a/tests/brain/test_brain.py +++ b/tests/brain/test_brain.py @@ -1407,6 +1407,7 @@ def test_infer_str() -> None: str(s) #@ str('a') #@ str(some_object()) #@ + str(7**10000) #@ """) for node in ast_nodes: inferred = next(node.infer()) @@ -1420,6 +1421,37 @@ def test_infer_str() -> None: assert inferred.qname() == "builtins.str" +def test_infer_str_const() -> None: + ast_nodes = astroid.extract_node(""" + str('') #@ + str('a') #@ + str(1) #@ + str(True) #@ + str(False) #@ + str(None) #@ + str(4.33) #@ + str(...) #@ + str(2 + 2) #@ + str() #@ + str(int) #@ + str(2 if unknown() else 3) #@ + """) + + inferred = list(node.inferred()[0].value for node in ast_nodes) + assert inferred[0] == "" + assert inferred[1] == "a" + assert inferred[2] == "1" + assert inferred[3] == "True" + assert inferred[4] == "False" + assert inferred[5] == "None" + assert inferred[6] == "4.33" + assert inferred[7] == "Ellipsis" + assert inferred[8] == "4" + assert inferred[9] == "" + assert inferred[10] == "" + assert inferred[11] == "" + + def test_infer_int() -> None: ast_nodes = astroid.extract_node(""" int(0) #@