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