1use crate::impl_::introspection::{escape_json_string, escaped_json_string_len};
6use core::fmt::{self, Display, Write};
7
8#[macro_export]
21macro_rules! type_hint_identifier {
22 ("builtins", $name:expr) => {
23 $crate::inspect::PyStaticExpr::Name { id: $name }
24 };
25 ($module:expr, $name:expr) => {
26 $crate::inspect::PyStaticExpr::Attribute {
27 value: &$crate::inspect::PyStaticExpr::Name { id: $module },
28 attr: $name,
29 }
30 };
31}
32pub(crate) use type_hint_identifier;
33
34#[macro_export]
44macro_rules! type_hint_union {
45 ($e:expr) => { $e };
46 ($l:expr , $($r:expr),+) => { $crate::inspect::PyStaticExpr::BinOp {
47 left: &$l,
48 op: $crate::inspect::PyStaticOperator::BitOr,
49 right: &type_hint_union!($($r),+),
50 } };
51}
52pub(crate) use type_hint_union;
53
54#[macro_export]
67macro_rules! type_hint_subscript {
68 ($l:expr, $r:expr) => {
69 $crate::inspect::PyStaticExpr::Subscript {
70 value: &$l,
71 slice: &$r
72 }
73 };
74 ($l:expr, $($r:expr),*) => {
75 $crate::inspect::PyStaticExpr::Subscript {
76 value: &$l,
77 slice: &$crate::inspect::PyStaticExpr::Tuple { elts: &[$($r),*] }
78 }
79 };
80}
81pub(crate) use type_hint_subscript;
82
83#[derive(Clone, Copy)]
91#[non_exhaustive]
92#[allow(missing_docs)]
93pub enum PyStaticExpr {
94 Constant { value: PyStaticConstant },
96 Name { id: &'static str },
98 Attribute {
100 value: &'static Self,
101 attr: &'static str,
102 },
103 BinOp {
105 left: &'static Self,
106 op: PyStaticOperator,
107 right: &'static Self,
108 },
109 Tuple { elts: &'static [Self] },
111 List { elts: &'static [Self] },
113 Subscript {
115 value: &'static Self,
116 slice: &'static Self,
117 },
118 PyClass(PyClassNameStaticExpr),
120}
121
122#[doc(hidden)]
124pub const fn serialize_for_introspection(expr: &PyStaticExpr, mut output: &mut [u8]) -> usize {
125 let original_len = output.len();
126 match expr {
127 PyStaticExpr::Constant { value } => match value {
128 PyStaticConstant::None => {
129 output = write_slice_and_move_forward(
130 b"{\"type\":\"constant\",\"kind\":\"none\"}",
131 output,
132 )
133 }
134 PyStaticConstant::Bool(value) => {
135 output = write_slice_and_move_forward(
136 if *value {
137 b"{\"type\":\"constant\",\"kind\":\"bool\",\"value\":true}"
138 } else {
139 b"{\"type\":\"constant\",\"kind\":\"bool\",\"value\":false}"
140 },
141 output,
142 )
143 }
144 PyStaticConstant::Int(value) => {
145 output = write_slice_and_move_forward(
146 b"{\"type\":\"constant\",\"kind\":\"int\",\"value\":\"",
147 output,
148 );
149 output = write_slice_and_move_forward(value.as_bytes(), output);
150 output = write_slice_and_move_forward(b"\"}", output);
151 }
152 PyStaticConstant::Float(value) => {
153 output = write_slice_and_move_forward(
154 b"{\"type\":\"constant\",\"kind\":\"float\",\"value\":\"",
155 output,
156 );
157 output = write_slice_and_move_forward(value.as_bytes(), output);
158 output = write_slice_and_move_forward(b"\"}", output);
159 }
160 PyStaticConstant::Str(value) => {
161 output = write_slice_and_move_forward(
162 b"{\"type\":\"constant\",\"kind\":\"str\",\"value\":",
163 output,
164 );
165 output = write_json_string_and_move_forward(value, output);
166 output = write_slice_and_move_forward(b"}", output);
167 }
168 PyStaticConstant::Ellipsis => {
169 output = write_slice_and_move_forward(
170 b"{\"type\":\"constant\",\"kind\":\"ellipsis\"}",
171 output,
172 )
173 }
174 },
175 PyStaticExpr::Name { id } => {
176 output = write_slice_and_move_forward(b"{\"type\":\"name\",\"id\":\"", output);
177 output = write_slice_and_move_forward(id.as_bytes(), output);
178 output = write_slice_and_move_forward(b"\"}", output);
179 }
180 PyStaticExpr::Attribute { value, attr } => {
181 output = write_slice_and_move_forward(b"{\"type\":\"attribute\",\"value\":", output);
182 output = write_expr_and_move_forward(value, output);
183 output = write_slice_and_move_forward(b",\"attr\":\"", output);
184 output = write_slice_and_move_forward(attr.as_bytes(), output);
185 output = write_slice_and_move_forward(b"\"}", output);
186 }
187 PyStaticExpr::BinOp { left, op, right } => {
188 output = write_slice_and_move_forward(b"{\"type\":\"binop\",\"left\":", output);
189 output = write_expr_and_move_forward(left, output);
190 output = write_slice_and_move_forward(b",\"op\":\"", output);
191 output = write_slice_and_move_forward(
192 match op {
193 PyStaticOperator::BitOr => b"bitor",
194 },
195 output,
196 );
197 output = write_slice_and_move_forward(b"\",\"right\":", output);
198 output = write_expr_and_move_forward(right, output);
199 output = write_slice_and_move_forward(b"}", output);
200 }
201 PyStaticExpr::Tuple { elts } => {
202 output = write_container_and_move_forward(b"tuple", elts, output);
203 }
204 PyStaticExpr::List { elts } => {
205 output = write_container_and_move_forward(b"list", elts, output);
206 }
207 PyStaticExpr::Subscript { value, slice } => {
208 output = write_slice_and_move_forward(b"{\"type\":\"subscript\",\"value\":", output);
209 output = write_expr_and_move_forward(value, output);
210 output = write_slice_and_move_forward(b",\"slice\":", output);
211 output = write_expr_and_move_forward(slice, output);
212 output = write_slice_and_move_forward(b"}", output);
213 }
214 PyStaticExpr::PyClass(expr) => {
215 output = write_slice_and_move_forward(b"{\"type\":\"id\",\"id\":\"", output);
216 output = write_slice_and_move_forward(expr.introspection_id.as_bytes(), output);
217 output = write_slice_and_move_forward(b"\"}", output);
218 }
219 }
220 original_len - output.len()
221}
222
223#[doc(hidden)]
225pub const fn serialized_len_for_introspection(expr: &PyStaticExpr) -> usize {
226 match expr {
227 PyStaticExpr::Constant { value } => match value {
228 PyStaticConstant::None => 33,
229 PyStaticConstant::Bool(value) => 42 + if *value { 4 } else { 5 },
230 PyStaticConstant::Int(value) => 43 + value.len(),
231 PyStaticConstant::Float(value) => 45 + value.len(),
232 PyStaticConstant::Str(value) => 43 + escaped_json_string_len(value),
233 PyStaticConstant::Ellipsis => 37,
234 },
235 PyStaticExpr::Name { id } => 23 + id.len(),
236 PyStaticExpr::Attribute { value, attr } => {
237 39 + serialized_len_for_introspection(value) + attr.len()
238 }
239 PyStaticExpr::BinOp { left, op, right } => {
240 41 + serialized_len_for_introspection(left)
241 + match op {
242 PyStaticOperator::BitOr => 5,
243 }
244 + serialized_len_for_introspection(right)
245 }
246 PyStaticExpr::Tuple { elts } => 5 + serialized_container_len_for_introspection(elts),
247 PyStaticExpr::List { elts } => 4 + serialized_container_len_for_introspection(elts),
248 PyStaticExpr::Subscript { value, slice } => {
249 38 + serialized_len_for_introspection(value) + serialized_len_for_introspection(slice)
250 }
251 PyStaticExpr::PyClass(expr) => 21 + expr.introspection_id.len(),
252 }
253}
254
255impl fmt::Display for PyStaticExpr {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 match self {
258 Self::Constant { value } => match value {
259 PyStaticConstant::None => f.write_str("None"),
260 PyStaticConstant::Bool(value) => f.write_str(if *value { "True" } else { "False" }),
261 PyStaticConstant::Int(value) => f.write_str(value),
262 PyStaticConstant::Float(value) => {
263 f.write_str(value)?;
264 if !value.contains(['.', 'e', 'E']) {
265 f.write_char('.')?;
267 }
268 Ok(())
269 }
270 PyStaticConstant::Str(value) => write!(f, "{value:?}"),
271 PyStaticConstant::Ellipsis => f.write_str("..."),
272 },
273 Self::Name { id, .. } => f.write_str(id),
274 Self::Attribute { value, attr } => {
275 value.fmt(f)?;
276 f.write_str(".")?;
277 f.write_str(attr)
278 }
279 Self::BinOp { left, op, right } => {
280 left.fmt(f)?;
281 f.write_char(' ')?;
282 f.write_char(match op {
283 PyStaticOperator::BitOr => '|',
284 })?;
285 f.write_char(' ')?;
286 right.fmt(f)
287 }
288 Self::Tuple { elts } => {
289 f.write_char('(')?;
290 fmt_elements(elts, f)?;
291 if elts.len() == 1 {
292 f.write_char(',')?;
293 }
294 f.write_char(')')
295 }
296 Self::List { elts } => {
297 f.write_char('[')?;
298 fmt_elements(elts, f)?;
299 f.write_char(']')
300 }
301 Self::Subscript { value, slice } => {
302 value.fmt(f)?;
303 f.write_char('[')?;
304 if let PyStaticExpr::Tuple { elts } = slice {
305 fmt_elements(elts, f)?;
307 } else {
308 slice.fmt(f)?;
309 }
310 f.write_char(']')
311 }
312 Self::PyClass(expr) => expr.expr.fmt(f),
313 }
314 }
315}
316
317#[derive(Clone, Copy)]
321#[non_exhaustive]
322pub enum PyStaticConstant {
323 None,
325 Bool(bool),
327 Int(&'static str),
329 Float(&'static str),
331 Str(&'static str),
333 Ellipsis,
335}
336
337#[derive(Clone, Copy)]
339#[non_exhaustive]
340pub enum PyStaticOperator {
341 BitOr,
343}
344
345const fn write_slice_and_move_forward<'a>(value: &[u8], output: &'a mut [u8]) -> &'a mut [u8] {
346 let mut i = 0;
348 while i < value.len() {
349 output[i] = value[i];
350 i += 1;
351 }
352 output.split_at_mut(value.len()).1
353}
354
355const fn write_json_string_and_move_forward<'a>(value: &str, output: &'a mut [u8]) -> &'a mut [u8] {
356 output[0] = b'"';
357 let output = output.split_at_mut(1).1;
358 let written = escape_json_string(value, output);
359 output[written] = b'"';
360 output.split_at_mut(written + 1).1
361}
362
363const fn write_expr_and_move_forward<'a>(
364 value: &PyStaticExpr,
365 output: &'a mut [u8],
366) -> &'a mut [u8] {
367 let written = serialize_for_introspection(value, output);
368 output.split_at_mut(written).1
369}
370
371const fn write_container_and_move_forward<'a>(
372 name: &'static [u8],
373 elts: &[PyStaticExpr],
374 mut output: &'a mut [u8],
375) -> &'a mut [u8] {
376 output = write_slice_and_move_forward(b"{\"type\":\"", output);
377 output = write_slice_and_move_forward(name, output);
378 output = write_slice_and_move_forward(b"\",\"elts\":[", output);
379 let mut i = 0;
380 while i < elts.len() {
381 if i > 0 {
382 output = write_slice_and_move_forward(b",", output);
383 }
384 output = write_expr_and_move_forward(&elts[i], output);
385 i += 1;
386 }
387 write_slice_and_move_forward(b"]}", output)
388}
389
390const fn serialized_container_len_for_introspection(elts: &[PyStaticExpr]) -> usize {
391 let mut len = 21;
392 let mut i = 0;
393 while i < elts.len() {
394 if i > 0 {
395 len += 1;
396 }
397 len += serialized_len_for_introspection(&elts[i]);
398 i += 1;
399 }
400 len
401}
402
403fn fmt_elements(elts: &[PyStaticExpr], f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 for (i, elt) in elts.iter().enumerate() {
405 if i > 0 {
406 f.write_str(", ")?;
407 }
408 elt.fmt(f)?;
409 }
410 Ok(())
411}
412
413#[derive(Clone, Copy)]
417pub struct PyClassNameStaticExpr {
418 expr: &'static PyStaticExpr,
419 introspection_id: &'static str,
420}
421
422impl PyClassNameStaticExpr {
423 #[doc(hidden)]
424 #[inline]
425 pub const fn new(expr: &'static PyStaticExpr, introspection_id: &'static str) -> Self {
426 Self {
427 expr,
428 introspection_id,
429 }
430 }
431
432 #[inline]
437 pub const fn expr(&self) -> &'static PyStaticExpr {
438 self.expr
439 }
440}
441
442impl AsRef<PyStaticExpr> for PyClassNameStaticExpr {
443 #[inline]
444 fn as_ref(&self) -> &PyStaticExpr {
445 self.expr
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452 use crate::platform::prelude::*;
453
454 #[test]
455 fn test_to_string() {
456 const T: PyStaticExpr = type_hint_subscript!(
457 type_hint_identifier!("builtins", "dict"),
458 type_hint_union!(
459 type_hint_identifier!("builtins", "int"),
460 type_hint_subscript!(
461 type_hint_identifier!("typing", "Literal"),
462 PyStaticExpr::Constant {
463 value: PyStaticConstant::Str("\0\t\\\"")
464 }
465 )
466 ),
467 type_hint_identifier!("datetime", "time")
468 );
469 assert_eq!(
470 T.to_string(),
471 "dict[int | typing.Literal[\"\\0\\t\\\\\\\"\"], datetime.time]"
472 )
473 }
474
475 #[test]
476 fn test_serialize_for_introspection() {
477 fn check_serialization(expr: PyStaticExpr, expected: &str) {
478 let mut out = vec![0; serialized_len_for_introspection(&expr)];
479 serialize_for_introspection(&expr, &mut out);
480 assert_eq!(core::str::from_utf8(&out).unwrap(), expected)
481 }
482
483 check_serialization(
484 PyStaticExpr::Constant {
485 value: PyStaticConstant::None,
486 },
487 r#"{"type":"constant","kind":"none"}"#,
488 );
489 check_serialization(
490 type_hint_identifier!("builtins", "int"),
491 r#"{"type":"name","id":"int"}"#,
492 );
493 check_serialization(
494 type_hint_identifier!("datetime", "date"),
495 r#"{"type":"attribute","value":{"type":"name","id":"datetime"},"attr":"date"}"#,
496 );
497 check_serialization(
498 type_hint_union!(
499 type_hint_identifier!("builtins", "int"),
500 type_hint_identifier!("builtins", "float")
501 ),
502 r#"{"type":"binop","left":{"type":"name","id":"int"},"op":"bitor","right":{"type":"name","id":"float"}}"#,
503 );
504 check_serialization(
505 PyStaticExpr::Tuple {
506 elts: &[type_hint_identifier!("builtins", "list")],
507 },
508 r#"{"type":"tuple","elts":[{"type":"name","id":"list"}]}"#,
509 );
510 check_serialization(
511 PyStaticExpr::List {
512 elts: &[type_hint_identifier!("builtins", "list")],
513 },
514 r#"{"type":"list","elts":[{"type":"name","id":"list"}]}"#,
515 );
516 check_serialization(
517 type_hint_subscript!(
518 type_hint_identifier!("builtins", "list"),
519 type_hint_identifier!("builtins", "int")
520 ),
521 r#"{"type":"subscript","value":{"type":"name","id":"list"},"slice":{"type":"name","id":"int"}}"#,
522 );
523 check_serialization(
524 PyStaticExpr::PyClass(PyClassNameStaticExpr::new(
525 &type_hint_identifier!("builtins", "foo"),
526 "foo",
527 )),
528 r#"{"type":"id","id":"foo"}"#,
529 );
530 check_serialization(
531 PyStaticExpr::Constant {
532 value: PyStaticConstant::Bool(true),
533 },
534 r#"{"type":"constant","kind":"bool","value":true}"#,
535 );
536 check_serialization(
537 PyStaticExpr::Constant {
538 value: PyStaticConstant::Bool(false),
539 },
540 r#"{"type":"constant","kind":"bool","value":false}"#,
541 );
542 check_serialization(
543 PyStaticExpr::Constant {
544 value: PyStaticConstant::Int("-123"),
545 },
546 r#"{"type":"constant","kind":"int","value":"-123"}"#,
547 );
548 check_serialization(
549 PyStaticExpr::Constant {
550 value: PyStaticConstant::Float("-2.1"),
551 },
552 r#"{"type":"constant","kind":"float","value":"-2.1"}"#,
553 );
554 check_serialization(
555 PyStaticExpr::Constant {
556 value: PyStaticConstant::Float("+2.1e10"),
557 },
558 r#"{"type":"constant","kind":"float","value":"+2.1e10"}"#,
559 );
560 check_serialization(
561 PyStaticExpr::Constant {
562 value: PyStaticConstant::Str("abc(1)"),
563 },
564 r#"{"type":"constant","kind":"str","value":"abc(1)"}"#,
565 );
566 check_serialization(
567 PyStaticExpr::Constant {
568 value: PyStaticConstant::Str("\"\\/\x08\x0C\n\r\t\0\x19a"),
569 },
570 r#"{"type":"constant","kind":"str","value":"\"\\/\b\f\n\r\t\u0000\u0019a"}"#,
571 );
572 check_serialization(
573 PyStaticExpr::Constant {
574 value: PyStaticConstant::Ellipsis,
575 },
576 r#"{"type":"constant","kind":"ellipsis"}"#,
577 );
578 }
579}