Skip to content

Accurate annotation for ast.Constant.value #14156

Closed
@hunterhogan

Description

@hunterhogan

Current annotation is Any

typeshed/stdlib/ast.pyi

Lines 1098 to 1111 in 27383aa

class Constant(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("value", "kind")
value: Any # None, str, bytes, bool, int, float, complex, Ellipsis
kind: str | None
if sys.version_info < (3, 14):
# Aliases for value, for backwards compatibility
s: Any
n: int | float | complex
def __init__(self, value: Any, kind: str | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, value: Any = ..., kind: str | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ...

Valid input

I feel the annotation should accurately represent which inputs are valid. Over-inclusion is misleading. Annotating the input for ast.Constant based on the output of ast.parse would be under-inclusive and misleading.

Various explanations of ast.Constant, constants in Python, and immutable Python types

Comment implies Any is too broad

The stub file seems to concede that the annotation is overly broad. # None, str, bytes, bool, int, float, complex, Ellipsis.

The documentation has a third definition

The value attribute of the Constant literal contains the Python object it represents. The values represented can be simple types such as a number, string or None, but also immutable container types (tuples and frozensets) if all of their elements are constant.

Immortal constant types

https://docs.python.org/3/c-api/object.html#c.Py_GetConstant

  • str
  • bytes
  • EllipsisType
  • bool
  • NoneType
  • NotImplementedType
  • tuple

Built-in Constant types

https://docs.python.org/3/library/constants.html#built-in-consts

  • bool
  • NoneType
  • NotImplementedType
  • EllipsisType

range

https://docs.python.org/3.13/library/stdtypes.html#ranges

The range type represents an immutable sequence of numbers ...

(.venv) C:\apps\astToolFactory>py
Python 3.13.3 (tags/v3.13.3:6280bb5, Apr  8 2025, 14:47:33) [MSC v.1943 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import ast
>>> ast.dump ( ast.Constant( value = range( 13, 17 ) ) )
'Constant(value=range(13, 17))'

None vs NoneType

I have observed that type annotations almost always read None instead of NoneType. I haven't tried to figure this out, so I'll just mimic others.

Ellipsis vs EllipsisType

Please, for the love of puppies, kittens, and cookies, do not pollute the ast namespace further by using builtins.Ellipsis in type annotations. Therefore, types.EllipsisType.

NotImplemented vs NotImplementedType

NotImplemented is not an accurate annotation, and it would be confusing, especially for people unfamiliar with esoteric Python. Therefore, NotImplementedType.

Nesting

Frankly, I am not confident I understand how to represent infinite nesting in annotations, but a precise annotation would include infinite nesting of very cold tuplesets.

A (potentially) precise annotation for ast.Constant.value

from types import EllipsisType, NotImplementedType
from typing import TypeAlias

_Constant: TypeAlias = bool | bytes | complex | EllipsisType | float | int | None | NotImplementedType | range | str

ConstantType: TypeAlias = _Constant | frozenset['ConstantType'] | tuple['ConstantType', ...]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions