Skip to content

Commit 8021e00

Browse files
committed
Move const-ness from Type to Expr.
1 parent 9d6cf39 commit 8021e00

File tree

11 files changed

+41
-480
lines changed

11 files changed

+41
-480
lines changed

qiskit/circuit/classical/expr/constructors.py

+20-46
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def lift_legacy_condition(
7777
return Binary(Binary.Op.EQUAL, left, right, types.Bool())
7878

7979

80-
def lift(value: typing.Any, /, type: types.Type | None = None, *, try_const: bool = False) -> Expr:
80+
def lift(value: typing.Any, /, type: types.Type | None = None) -> Expr:
8181
"""Lift the given Python ``value`` to a :class:`~.expr.Value` or :class:`~.expr.Var`.
8282
8383
By default, lifted scalars are not const. To lift supported scalars to const-typed
@@ -106,31 +106,16 @@ def lift(value: typing.Any, /, type: types.Type | None = None, *, try_const: boo
106106
Var(ClassicalRegister(3, "c"), Uint(5, const=False))
107107
>>> expr.lift(5, types.Uint(4))
108108
Value(5, Uint(4))
109-
110-
Lifting non-classical resource scalars to const values::
111-
112-
>>> from qiskit.circuit.classical import expr, types
113-
>>> expr.lift(7)
114-
Value(7, Uint(3, const=False))
115-
>>> expr.lift(7, try_const=True)
116-
Value(7, Uint(3, const=True))
117-
>>> expr.lift(7, types.Uint(8, const=True))
118-
Value(7, Uint(8, const=True))
119-
120109
"""
121110
if isinstance(value, Expr):
122111
if type is not None:
123112
raise ValueError("use 'cast' to cast existing expressions, not 'lift'")
124113
return value
125114
from qiskit.circuit import Clbit, ClassicalRegister # pylint: disable=cyclic-import
126115

127-
if type is not None:
128-
# If a type was specified, the inferred type must be the same
129-
# const-ness.
130-
try_const = type.const
131116
inferred: types.Type
132117
if value is True or value is False:
133-
inferred = types.Bool(const=try_const)
118+
inferred = types.Bool()
134119
constructor = Value
135120
elif isinstance(value, Clbit):
136121
inferred = types.Bool()
@@ -141,7 +126,7 @@ def lift(value: typing.Any, /, type: types.Type | None = None, *, try_const: boo
141126
elif isinstance(value, int):
142127
if value < 0:
143128
raise ValueError("cannot represent a negative value")
144-
inferred = types.Uint(width=value.bit_length() or 1, const=try_const)
129+
inferred = types.Uint(width=value.bit_length() or 1)
145130
constructor = Value
146131
else:
147132
raise TypeError(f"failed to infer a type for '{value}'")
@@ -218,7 +203,7 @@ def logic_not(operand: typing.Any, /) -> Expr:
218203
Bool(const=False))
219204
"""
220205
operand = lift(operand)
221-
operand = _coerce_lossless(operand, types.Bool(const=operand.type.const))
206+
operand = _coerce_lossless(operand, types.Bool())
222207
return Unary(Unary.Op.LOGIC_NOT, operand, operand.type)
223208

224209

@@ -233,24 +218,13 @@ def _lift_binary_operands(left: typing.Any, right: typing.Any) -> tuple[Expr, Ex
233218
to be interoperable.
234219
* If both operands are expressions, they are returned as-is, and may require a cast node.
235220
"""
236-
left_bool = isinstance(left, bool)
237221
left_int = isinstance(left, int) and not isinstance(left, bool)
238-
right_bool = isinstance(right, bool)
239-
right_int = isinstance(right, int) and not right_bool
222+
right_int = isinstance(right, int) and not isinstance(right, bool)
240223
if not (left_int or right_int):
241-
if left_bool == right_bool:
242-
# They're either both bool, or neither are, so we lift them
243-
# independently.
244-
left = lift(left)
245-
right = lift(right)
246-
elif not right_bool:
247-
# Left is a bool, which should only be const if right is const.
248-
right = lift(right)
249-
left = lift(left, try_const=right.type.const)
250-
elif not left_bool:
251-
# Right is a bool, which should only be const if left is const.
252-
left = lift(left)
253-
right = lift(right, try_const=left.type.const)
224+
# They're either both bool, or neither are, so we lift them
225+
# independently.
226+
left = lift(left)
227+
right = lift(right)
254228
elif not right_int:
255229
# Left is an int.
256230
right = lift(right)
@@ -262,7 +236,7 @@ def _lift_binary_operands(left: typing.Any, right: typing.Any) -> tuple[Expr, Ex
262236
# Left will share const-ness of right.
263237
left = Value(left, right.type)
264238
else:
265-
left = lift(left, try_const=right.type.const)
239+
left = lift(left)
266240
elif not left_int:
267241
# Right is an int.
268242
left = lift(left)
@@ -274,7 +248,7 @@ def _lift_binary_operands(left: typing.Any, right: typing.Any) -> tuple[Expr, Ex
274248
# Right will share const-ness of left.
275249
right = Value(right, left.type)
276250
else:
277-
right = lift(right, try_const=left.type.const)
251+
right = lift(right)
278252
else:
279253
# Both are `int`, so we take our best case to make things work.
280254
# If the caller needs a const type, they should lift one side to
@@ -289,14 +263,14 @@ def _binary_bitwise(op: Binary.Op, left: typing.Any, right: typing.Any) -> Expr:
289263
left, right = _lift_binary_operands(left, right)
290264
type: types.Type
291265
if left.type.kind is right.type.kind is types.Bool:
292-
type = types.Bool(const=(left.type.const and right.type.const))
266+
type = types.Bool()
293267
elif left.type.kind is types.Uint and right.type.kind is types.Uint:
294268
if left.type.width != right.type.width:
295269
raise TypeError(
296270
"binary bitwise operations are defined between unsigned integers of the same width,"
297271
f" but got {left.type.width} and {right.type.width}."
298272
)
299-
type = types.Uint(width=left.type.width, const=(left.type.const and right.type.const))
273+
type = types.Uint(width=left.type.width)
300274
else:
301275
raise TypeError(f"invalid types for '{op}': '{left.type}' and '{right.type}'")
302276
return Binary(op, _coerce_lossless(left, type), _coerce_lossless(right, type), type)
@@ -362,7 +336,7 @@ def bit_xor(left: typing.Any, right: typing.Any, /) -> Expr:
362336
def _binary_logical(op: Binary.Op, left: typing.Any, right: typing.Any) -> Expr:
363337
left = lift(left)
364338
right = lift(right)
365-
type = types.Bool(const=(left.type.const and right.type.const))
339+
type = types.Bool()
366340
left = _coerce_lossless(left, type)
367341
right = _coerce_lossless(right, type)
368342
return Binary(op, left, right, type)
@@ -416,7 +390,7 @@ def _equal_like(op: Binary.Op, left: typing.Any, right: typing.Any) -> Expr:
416390
op,
417391
_coerce_lossless(left, type),
418392
_coerce_lossless(right, type),
419-
types.Bool(const=type.const),
393+
types.Bool(),
420394
)
421395

422396

@@ -469,7 +443,7 @@ def _binary_relation(op: Binary.Op, left: typing.Any, right: typing.Any) -> Expr
469443
op,
470444
_coerce_lossless(left, type),
471445
_coerce_lossless(right, type),
472-
types.Bool(const=type.const),
446+
types.Bool(),
473447
)
474448

475449

@@ -554,14 +528,14 @@ def _shift_like(
554528
left = _coerce_lossless(left, type) if type is not None else left
555529
else:
556530
left = lift(left, type)
557-
right = lift(right, try_const=left.type.const)
531+
right = lift(right)
558532
if left.type.kind != types.Uint or right.type.kind != types.Uint:
559533
raise TypeError(f"invalid types for '{op}': '{left.type}' and '{right.type}'")
560534
return Binary(
561535
op,
562536
left,
563537
right,
564-
types.Uint(width=left.type.width, const=(left.type.const and right.type.const)),
538+
types.Uint(width=left.type.width),
565539
)
566540

567541

@@ -631,7 +605,7 @@ def index(target: typing.Any, index: typing.Any, /) -> Expr:
631605
Bool(const=False))
632606
"""
633607
target = lift(target)
634-
index = lift(index, try_const=target.type.const)
608+
index = lift(index)
635609
if target.type.kind is not types.Uint or index.type.kind is not types.Uint:
636610
raise TypeError(f"invalid types for indexing: '{target.type}' and '{index.type}'")
637-
return Index(target, index, types.Bool(const=target.type.const and index.type.const))
611+
return Index(target, index, types.Bool())

qiskit/circuit/classical/expr/expr.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ class Expr(abc.ABC):
5555
All subclasses are responsible for setting their ``type`` attribute in their ``__init__``, and
5656
should not call the parent initializer."""
5757

58-
__slots__ = ("type",)
58+
__slots__ = ("type", "const")
5959

6060
type: types.Type
61+
const: bool
6162

6263
# Sentinel to prevent instantiation of the base class.
6364
@abc.abstractmethod
@@ -89,6 +90,7 @@ class Cast(Expr):
8990

9091
def __init__(self, operand: Expr, type: types.Type, implicit: bool = False):
9192
self.type = type
93+
self.const = operand.const
9294
self.operand = operand
9395
self.implicit = implicit
9496

@@ -99,6 +101,7 @@ def __eq__(self, other):
99101
return (
100102
isinstance(other, Cast)
101103
and self.type == other.type
104+
and self.const == other.const
102105
and self.operand == other.operand
103106
and self.implicit == other.implicit
104107
)
@@ -141,6 +144,7 @@ def __init__(
141144
name: str | None = None,
142145
):
143146
super().__setattr__("type", type)
147+
super().__setattr__("const", False)
144148
super().__setattr__("var", var)
145149
super().__setattr__("name", name)
146150

@@ -207,6 +211,7 @@ class Value(Expr):
207211
def __init__(self, value: typing.Any, type: types.Type):
208212
self.type = type
209213
self.value = value
214+
self.const = True
210215

211216
def accept(self, visitor, /):
212217
return visitor.visit_value(self)
@@ -258,6 +263,7 @@ def __init__(self, op: Unary.Op, operand: Expr, type: types.Type):
258263
self.op = op
259264
self.operand = operand
260265
self.type = type
266+
self.const = operand.const
261267

262268
def accept(self, visitor, /):
263269
return visitor.visit_unary(self)
@@ -266,6 +272,7 @@ def __eq__(self, other):
266272
return (
267273
isinstance(other, Unary)
268274
and self.type == other.type
275+
and self.const == other.const
269276
and self.op is other.op
270277
and self.operand == other.operand
271278
)
@@ -349,6 +356,7 @@ def __init__(self, op: Binary.Op, left: Expr, right: Expr, type: types.Type):
349356
self.left = left
350357
self.right = right
351358
self.type = type
359+
self.const = left.const and right.const
352360

353361
def accept(self, visitor, /):
354362
return visitor.visit_binary(self)
@@ -382,6 +390,7 @@ def __init__(self, target: Expr, index: Expr, type: types.Type):
382390
self.target = target
383391
self.index = index
384392
self.type = type
393+
self.const = target.const and index.const
385394

386395
def accept(self, visitor, /):
387396
return visitor.visit_index(self)

qiskit/circuit/classical/types/ordering.py

+1-24
Original file line numberDiff line numberDiff line change
@@ -98,24 +98,7 @@ def order(left: Type, right: Type, /) -> Ordering:
9898
"""
9999
if (orderer := _ORDERERS.get((left.kind, right.kind))) is None:
100100
return Ordering.NONE
101-
order_ = orderer(left, right)
102-
103-
# If the natural type ordering is equal (either one can represent both)
104-
# but the types differ in const-ness, the non-const variant is greater.
105-
# If one type is greater (and thus is the only type that can represent
106-
# both) an ordering is only defined if that type is non-const or both
107-
# types are const.
108-
if left.const and not right.const:
109-
if order_ is Ordering.EQUAL:
110-
return Ordering.LESS
111-
if order_ is Ordering.GREATER:
112-
return Ordering.NONE
113-
if right.const and not left.const:
114-
if order_ is Ordering.EQUAL:
115-
return Ordering.GREATER
116-
if order_ is Ordering.LESS:
117-
return Ordering.NONE
118-
return order_
101+
return orderer(left, right)
119102

120103

121104
def is_subtype(left: Type, right: Type, /, strict: bool = False) -> bool:
@@ -251,13 +234,7 @@ def cast_kind(from_: Type, to_: Type, /) -> CastKind:
251234
>>> types.cast_kind(types.Uint(16), types.Uint(8))
252235
<CastKind.DANGEROUS: 4>
253236
"""
254-
if to_.const and not from_.const:
255-
# We can't cast to a const type.
256-
return CastKind.NONE
257237
if (coercer := _ALLOWED_CASTS.get((from_.kind, to_.kind))) is None:
258238
return CastKind.NONE
259239
cast_kind_ = coercer(from_, to_)
260-
if cast_kind_ is CastKind.EQUAL and to_.const != from_.const:
261-
# We need an implicit cast to drop const.
262-
return CastKind.IMPLICIT
263240
return cast_kind_

qiskit/circuit/classical/types/types.py

+8-15
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,10 @@ class Type:
3030
3131
This must not be subclassed by users; subclasses form the internal data of the representation of
3232
expressions, and it does not make sense to add more outside of Qiskit library code.
33-
34-
All subclasses are responsible for setting the ``const`` attribute in their ``__init__``.
3533
"""
3634

37-
__slots__ = ("const",)
35+
__slots__ = ()
3836

39-
const: bool
4037

4138
@property
4239
def kind(self):
@@ -69,17 +66,14 @@ class Bool(Type):
6966

7067
__slots__ = ()
7168

72-
def __init__(self, *, const: bool = False):
73-
super(Type, self).__setattr__("const", const)
74-
7569
def __repr__(self):
76-
return f"Bool(const={self.const})"
70+
return f"Bool()"
7771

7872
def __hash__(self):
79-
return hash((self.__class__, self.const))
73+
return hash(self.__class__)
8074

8175
def __eq__(self, other):
82-
return isinstance(other, Bool) and self.const == other.const
76+
return isinstance(other, Bool)
8377

8478

8579
@typing.final
@@ -88,17 +82,16 @@ class Uint(Type):
8882

8983
__slots__ = ("width",)
9084

91-
def __init__(self, width: int, *, const: bool = False):
85+
def __init__(self, width: int):
9286
if isinstance(width, int) and width <= 0:
9387
raise ValueError("uint width must be greater than zero")
94-
super(Type, self).__setattr__("const", const)
9588
super(Type, self).__setattr__("width", width)
9689

9790
def __repr__(self):
98-
return f"Uint({self.width}, const={self.const})"
91+
return f"Uint({self.width})"
9992

10093
def __hash__(self):
101-
return hash((self.__class__, self.const, self.width))
94+
return hash(self.__class__)
10295

10396
def __eq__(self, other):
104-
return isinstance(other, Uint) and self.const == other.const and self.width == other.width
97+
return isinstance(other, Uint) and self.width == other.width

qiskit/circuit/quantumcircuit.py

-8
Original file line numberDiff line numberDiff line change
@@ -2812,8 +2812,6 @@ def add_var(self, name_or_var: str | expr.Var, /, initial: typing.Any) -> expr.V
28122812
circuit_scope = self._current_scope()
28132813
coerce_type = None
28142814
if isinstance(name_or_var, expr.Var):
2815-
if name_or_var.type.const:
2816-
raise CircuitError("const variables are not supported.")
28172815
if (
28182816
name_or_var.type.kind is types.Uint
28192817
and isinstance(initial, int)
@@ -2872,8 +2870,6 @@ def add_uninitialized_var(self, var: expr.Var, /):
28722870
raise CircuitError("cannot add an uninitialized variable in a control-flow scope")
28732871
if not var.standalone:
28742872
raise CircuitError("cannot add a variable wrapping a bit or register to a circuit")
2875-
if var.type.const:
2876-
raise CircuitError("const variables are not supported.")
28772873
self._builder_api.add_uninitialized_var(var)
28782874

28792875
def add_capture(self, var: expr.Var):
@@ -2939,10 +2935,6 @@ def add_input( # pylint: disable=missing-raises-doc
29392935
if isinstance(name_or_var, expr.Var):
29402936
if type_ is not None:
29412937
raise ValueError("cannot give an explicit type with an existing Var")
2942-
if name_or_var.type.const:
2943-
raise CircuitError("const variables cannot be input variables")
2944-
elif type_ is not None and type_.const:
2945-
raise CircuitError("const variables cannot be input variables")
29462938

29472939
var = self._prepare_new_var(name_or_var, type_)
29482940
self._vars_input[var.name] = var

0 commit comments

Comments
 (0)