microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.28.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/pip/qsharp/qre/_enumeration.py

242lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4import types
5from typing import (
6 Generator,
7 Type,
8 TypeVar,
9 Literal,
10 Union,
11 cast,
12 get_args,
13 get_origin,
14 get_type_hints,
15)
16from dataclasses import MISSING
17from itertools import product
18from enum import Enum
19
20
21T = TypeVar("T")
22
23
24def _is_union_type(tp) -> bool:
25 """Check if a type is a Union or Python 3.10+ union (X | Y)."""
26 return get_origin(tp) is Union or isinstance(tp, types.UnionType)
27
28
29def _is_type_filter(val, union_members: tuple) -> bool:
30 """
31 Check if *val* is a union member type or a list of union member types,
32 i.e. a type filter for a union field (as opposed to a fixed value or
33 instance domain).
34 """
35 member_set = set(union_members)
36 if isinstance(val, type) and val in member_set:
37 return True
38 if isinstance(val, list) and all(
39 isinstance(v, type) and v in member_set for v in val
40 ):
41 return True
42 return False
43
44
45def _is_union_constraint_dict(val) -> bool:
46 """
47 Check if *val* is a dict whose keys are all types, i.e. a per-member
48 constraint mapping for a union field.
49
50 Example: ``{OptionA: {"number": [2, 3]}, OptionB: {}}``
51 """
52 return isinstance(val, dict) and all(isinstance(k, type) for k in val)
53
54
55def _enumerate_union_members(
56 union_members: tuple,
57 val=None,
58) -> list:
59 """
60 Enumerate instances for a union-typed field.
61
62 *val* controls which members are enumerated and how:
63
64 - ``None`` - enumerate all members with their default domains.
65 - A single type (e.g. ``OptionB``) - enumerate only that member.
66 - A list of types (e.g. ``[OptionA, OptionB]``) - enumerate those members.
67 - A dict mapping types to constraint dicts
68 (e.g. ``{OptionA: {"number": [2, 3]}, OptionB: {}}``) -
69 enumerate only the listed members, forwarding the constraint dicts.
70 """
71 # No override - enumerate all members with defaults
72 if val is None:
73 domain: list = []
74 for member_type in union_members:
75 domain.extend(_enumerate_instances(member_type))
76 return domain
77
78 # Single type
79 if isinstance(val, type):
80 return list(_enumerate_instances(val))
81
82 # List of types
83 if isinstance(val, list) and all(isinstance(v, type) for v in val):
84 domain = []
85 for member_type in val:
86 domain.extend(_enumerate_instances(member_type))
87 return domain
88
89 # Dict of type → constraint dict
90 if _is_union_constraint_dict(val):
91 domain = []
92 for member_type, member_kwargs in cast(dict, val).items():
93 domain.extend(_enumerate_instances(member_type, **member_kwargs))
94 return domain
95
96 raise ValueError(
97 f"Invalid value for union field: {val!r}. "
98 "Expected a union member type, a list of types, or a dict mapping "
99 "types to constraint dicts."
100 )
101
102
103def _enumerate_instances(cls: Type[T], **kwargs) -> Generator[T, None, None]:
104 """
105 Yield all instances of a dataclass given its class.
106
107 The enumeration logic supports defining domains for fields using the
108 ``domain`` metadata key. Additionally, boolean fields are automatically
109 enumerated with ``[True, False]``, Enum fields with all their members,
110 and Literal types with their defined values.
111
112 **Nested dataclass fields** can be constrained by passing a dict::
113
114 _enumerate_instances(Outer, inner={"option": True})
115
116 **Union-typed fields** support several override forms:
117
118 - A single type to select one member::
119
120 _enumerate_instances(Config, option=OptionB)
121
122 - A list of types to select a subset::
123
124 _enumerate_instances(Config, option=[OptionA, OptionB])
125
126 - A dict mapping types to constraint dicts::
127
128 _enumerate_instances(Config, option={OptionA: {"number": [2, 3]}, OptionB: {}})
129
130 Args:
131 cls (Type[T]): The dataclass type to enumerate.
132 **kwargs: Fixed values or domains for fields. If a value is a list
133 and the corresponding field is kw_only, it is treated as a domain
134 to enumerate over. For nested dataclass fields a ``dict`` value
135 is forwarded as keyword arguments. For union-typed fields a type,
136 list of types, or ``dict[type, dict]`` controls member selection
137 and constraints.
138
139 Returns:
140 Generator[T, None, None]: A generator yielding instances of the
141 dataclass.
142
143 Raises:
144 ValueError: If a field cannot be enumerated (no domain found).
145 """
146
147 names = []
148 values = []
149 fixed_kwargs = {}
150
151 if (fields := getattr(cls, "__dataclass_fields__", None)) is None:
152 # There are no fields defined for this class, so just yield a single
153 # instance
154 yield cls(**kwargs)
155 return
156
157 # Resolve type hints to handle stringified types from __future__.annotations
158 type_hints = get_type_hints(cls)
159
160 for field in fields.values(): # type: ignore
161 name = field.name
162 # Get resolved type or fallback to field.type
163 current_type = type_hints.get(name, field.type)
164
165 if name in kwargs:
166 val = kwargs[name]
167
168 is_union = _is_union_type(current_type)
169 union_members = get_args(current_type) if is_union else ()
170
171 # Union field with a type filter or constraint dict
172 if is_union and (
173 _is_type_filter(val, union_members) or _is_union_constraint_dict(val)
174 ):
175 names.append(name)
176 values.append(_enumerate_union_members(union_members, val))
177 continue
178
179 # Nested dataclass field with a dict of constraints
180 if (
181 isinstance(val, dict)
182 and not is_union
183 and isinstance(current_type, type)
184 and hasattr(current_type, "__dataclass_fields__")
185 ):
186 names.append(name)
187 values.append(list(_enumerate_instances(current_type, **val)))
188 continue
189
190 # If kw_only and list, it's a domain to enumerate
191 if field.kw_only and isinstance(val, list):
192 names.append(name)
193 values.append(val)
194 else:
195 # Otherwise, it's a fixed value
196 fixed_kwargs[name] = val
197 continue
198
199 if not field.kw_only:
200 # We don't enumerate non-kw-only fields that aren't in kwargs
201 continue
202
203 # Derived domain logic
204 names.append(name)
205
206 domain = field.metadata.get("domain", None)
207 if domain is not None:
208 values.append(domain)
209 continue
210
211 if current_type is bool:
212 values.append([True, False])
213 continue
214
215 if isinstance(current_type, type) and issubclass(current_type, Enum):
216 values.append(list(current_type))
217 continue
218
219 if get_origin(current_type) is Literal:
220 values.append(list(get_args(current_type)))
221 continue
222
223 # Union types (e.g., OptionA | OptionB or Union[OptionA, OptionB])
224 if _is_union_type(current_type):
225 values.append(_enumerate_union_members(get_args(current_type), None))
226 continue
227
228 # Nested dataclass types
229 if isinstance(current_type, type) and hasattr(
230 current_type, "__dataclass_fields__"
231 ):
232 values.append(list(_enumerate_instances(current_type)))
233 continue
234
235 if field.default is not MISSING:
236 values.append([field.default])
237 continue
238
239 raise ValueError(f"Cannot enumerate field {name}.")
240
241 for instance_values in product(*values):
242 yield cls(**fixed_kwargs, **dict(zip(names, instance_values)))
243