Files
nuitka-mirror/nuitka/Constants.py
2025-04-28 11:04:36 +02:00

426 lines
11 KiB
Python

# Copyright 2025, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file
""" Module for constants in Nuitka.
This contains tools to compare, classify and test constants.
"""
import math
import sys
from types import BuiltinFunctionType
from nuitka.Builtins import builtin_type_names
from nuitka.PythonVersions import python_version
from .__past__ import GenericAlias, UnionType, iterItems, long, unicode, xrange
from .Builtins import (
builtin_anon_names,
builtin_anon_value_list,
builtin_exception_values_list,
builtin_named_values_list,
)
NoneType = type(None)
EllipsisType = type(Ellipsis)
def compareConstants(a, b):
# Many many cases to deal with, pylint: disable=too-many-branches,too-many-return-statements
# Supposed fast path for comparison.
if type(a) is not type(b):
return False
# Now it's either not the same, or it is a container that contains NaN or it
# is a complex or float that is NaN, the other cases can use == at the end.
if type(a) is complex:
return compareConstants(a.imag, b.imag) and compareConstants(a.real, b.real)
if type(a) is float:
# Check sign first, -0.0 is not 0.0, or -nan is not nan, it has a
# different sign for a start.
if math.copysign(1.0, a) != math.copysign(1.0, b):
return False
if math.isnan(a) and math.isnan(b):
return True
return a == b
if type(a) in (tuple, list):
if len(a) != len(b):
return False
for ea, eb in zip(a, b):
if not compareConstants(ea, eb):
return False
return True
if type(a) is dict:
if len(a) != len(b):
return False
for ea1, ea2 in iterItems(a):
for eb1, eb2 in iterItems(b):
if compareConstants(ea1, eb1) and compareConstants(ea2, eb2):
break
else:
return False
return True
if type(a) in (frozenset, set):
if len(a) != len(b):
return False
for ea in a:
if ea not in b:
# Due to NaN values, we need to compare each set element with
# all the other set to be really sure.
for eb in b:
if compareConstants(ea, eb):
break
else:
return False
return True
if type(a) is xrange:
return str(a) == str(b)
# The NaN values of float and complex may let this fail, even if the
# constants are built in the same way, therefore above checks.
return a == b
# These built-in type references are kind of constant too. The list should be
# complete.
constant_builtin_types = (
int,
str,
float,
list,
tuple,
set,
dict,
slice,
complex,
xrange,
NoneType,
)
if python_version >= 0x300:
constant_builtin_types += (bytes,)
else:
constant_builtin_types += (
unicode,
long,
# This has no name in Python, but the natural one in C-API.
builtin_anon_names["instance"],
)
def isConstant(constant):
# Too many cases and all return, that is how we do it here,
# pylint: disable=too-many-branches,too-many-return-statements
constant_type = type(constant)
if constant_type is dict:
for key, value in iterItems(constant):
if not isConstant(key):
return False
if not isConstant(value):
return False
return True
elif constant_type in (tuple, list):
for element_value in constant:
if not isConstant(element_value):
return False
return True
elif constant_type is slice:
if (
not isConstant(constant.start)
or not isConstant(constant.stop)
or not isConstant(constant.step)
):
return False
return True
elif constant_type in (
str,
unicode,
complex,
int,
long,
bool,
float,
NoneType,
range,
bytes,
set,
frozenset,
xrange,
bytearray,
EllipsisType,
):
return True
elif constant in (NoneType, NotImplemented):
return True
elif constant in builtin_anon_value_list:
return True
elif constant_type is type:
# Maybe pre-build this as a set for quicker testing.
return (
constant.__name__ in builtin_type_names
or constant in builtin_exception_values_list
)
elif constant_type is BuiltinFunctionType and constant in builtin_named_values_list:
# TODO: Some others could also be usable and even interesting, but
# then probably should go into other node types, e.g. str.join is
# a candidate.
return True
elif constant_type is GenericAlias:
return True
elif constant_type is UnionType:
return True
elif constant is sys.version_info:
return True
else:
return False
def isMutable(constant):
"""Is a constant mutable
That means a user of a reference to it, can modify it. Strings are
a prime example of immutable, dictionaries are mutable.
"""
# Many cases and all return, that is how we do it here,
# pylint: disable=too-many-return-statements
constant_type = type(constant)
if constant_type in (
str,
unicode,
complex,
int,
long,
bool,
float,
NoneType,
range,
bytes,
slice,
xrange,
type,
frozenset,
BuiltinFunctionType,
EllipsisType,
):
return False
elif constant_type in (dict, list, set, bytearray):
return True
elif constant_type is tuple:
for value in constant:
if isMutable(value):
return True
return False
elif constant is NotImplemented:
return False
elif constant_type is GenericAlias:
return isMutable(constant.__origin__) or isMutable(constant.__args__)
elif constant_type is UnionType:
return False
elif constant is sys.version_info:
return False
else:
assert False, repr(constant)
def isHashable(constant):
"""Is a constant hashable
That means a user of a reference to it, can use it for dicts and set
keys. This is distinct from mutable, there is one types that is not
mutable, and still not hashable: slices.
"""
# Many cases and all return, that is how we do it here,
constant_type = type(constant)
if constant_type in (
str,
unicode,
complex,
int,
long,
bool,
float,
NoneType,
xrange,
bytes,
type,
frozenset,
BuiltinFunctionType,
EllipsisType,
):
return True
elif constant_type in (dict, list, set, slice, bytearray):
return False
elif constant_type is tuple:
for value in constant:
if not isHashable(value):
return False
return True
else:
assert False, constant_type
def getUnhashableConstant(constant):
"""Get one unhashable part of a constant."""
constant_type = type(constant)
if constant_type in (
str,
unicode,
complex,
int,
long,
bool,
float,
NoneType,
xrange,
bytes,
type,
slice,
BuiltinFunctionType,
EllipsisType,
):
return None
elif constant_type in (dict, list, set):
return constant
elif constant_type is tuple:
for value in constant:
res = getUnhashableConstant(value)
if res is not None:
return res
return None
elif constant in constant_builtin_types:
return None
else:
assert False, constant_type
def createConstantDict(keys, values):
# Create it proper size immediately.
constant_value = dict.fromkeys(keys, None)
for key, value in zip(keys, values):
constant_value[key] = value
return constant_value
def isCompileTimeConstantValue(value):
"""Determine if a value will be usable at compile time."""
# This needs to match code in makeCompileTimeConstantReplacementNode
if isConstant(value):
return True
elif type(value) is type:
return True
else:
return False
# Shared empty values, it would cost time to create them locally.
the_empty_dict = {}
the_empty_list = []
the_empty_set = set()
the_empty_bytearray = bytearray()
the_empty_tuple = ()
the_empty_frozenset = frozenset()
the_empty_slice = slice(None)
the_empty_unicode = unicode() # black doesn't let us write u"" anymore.
def getConstantValueGuide(constant, elements_only):
# Many cases and all return, that is how we do it here,
# pylint: disable=too-many-return-statements
constant_type = type(constant)
if constant_type in (
str,
unicode,
complex,
int,
long,
bool,
float,
NoneType,
range,
bytes,
slice,
xrange,
type,
frozenset,
BuiltinFunctionType,
EllipsisType,
):
return "i"
elif constant_type is list:
for element in constant:
if isMutable(element):
return ("%s" if elements_only else "L%s") % (
"".join(
getConstantValueGuide(element, elements_only=False)
for element in constant
),
)
return "l"
elif constant_type is tuple:
# Empty tuples need no deep copy, not even a copy.
if not constant:
return "i"
return ("%s" if elements_only else "T%s") % (
"".join(
getConstantValueGuide(element, elements_only=False)
for element in constant
),
)
elif constant_type is set:
return "S"
elif constant_type is bytearray:
return "B"
elif constant_type is dict:
for value in iterItems(constant):
if isMutable(value):
return "D"
return "d"
else:
return "?"
# Part of "Nuitka", an optimizing Python compiler that is compatible and
# integrates with CPython, but also works on its own.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.