1use crate::impl_::introspection::{escape_json_string, escaped_json_string_len};
6use std::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
453 #[test]
454 fn test_to_string() {
455 const T: PyStaticExpr = type_hint_subscript!(
456 type_hint_identifier!("builtins", "dict"),
457 type_hint_union!(
458 type_hint_identifier!("builtins", "int"),
459 type_hint_subscript!(
460 type_hint_identifier!("typing", "Literal"),
461 PyStaticExpr::Constant {
462 value: PyStaticConstant::Str("\0\t\\\"")
463 }
464 )
465 ),
466 type_hint_identifier!("datetime", "time")
467 );
468 assert_eq!(
469 T.to_string(),
470 "dict[int | typing.Literal[\"\\0\\t\\\\\\\"\"], datetime.time]"
471 )
472 }
473
474 #[test]
475 fn test_serialize_for_introspection() {
476 fn check_serialization(expr: PyStaticExpr, expected: &str) {
477 let mut out = vec![0; serialized_len_for_introspection(&expr)];
478 serialize_for_introspection(&expr, &mut out);
479 assert_eq!(std::str::from_utf8(&out).unwrap(), expected)
480 }
481
482 check_serialization(
483 PyStaticExpr::Constant {
484 value: PyStaticConstant::None,
485 },
486 r#"{"type":"constant","kind":"none"}"#,
487 );
488 check_serialization(
489 type_hint_identifier!("builtins", "int"),
490 r#"{"type":"name","id":"int"}"#,
491 );
492 check_serialization(
493 type_hint_identifier!("datetime", "date"),
494 r#"{"type":"attribute","value":{"type":"name","id":"datetime"},"attr":"date"}"#,
495 );
496 check_serialization(
497 type_hint_union!(
498 type_hint_identifier!("builtins", "int"),
499 type_hint_identifier!("builtins", "float")
500 ),
501 r#"{"type":"binop","left":{"type":"name","id":"int"},"op":"bitor","right":{"type":"name","id":"float"}}"#,
502 );
503 check_serialization(
504 PyStaticExpr::Tuple {
505 elts: &[type_hint_identifier!("builtins", "list")],
506 },
507 r#"{"type":"tuple","elts":[{"type":"name","id":"list"}]}"#,
508 );
509 check_serialization(
510 PyStaticExpr::List {
511 elts: &[type_hint_identifier!("builtins", "list")],
512 },
513 r#"{"type":"list","elts":[{"type":"name","id":"list"}]}"#,
514 );
515 check_serialization(
516 type_hint_subscript!(
517 type_hint_identifier!("builtins", "list"),
518 type_hint_identifier!("builtins", "int")
519 ),
520 r#"{"type":"subscript","value":{"type":"name","id":"list"},"slice":{"type":"name","id":"int"}}"#,
521 );
522 check_serialization(
523 PyStaticExpr::PyClass(PyClassNameStaticExpr::new(
524 &type_hint_identifier!("builtins", "foo"),
525 "foo",
526 )),
527 r#"{"type":"id","id":"foo"}"#,
528 );
529 check_serialization(
530 PyStaticExpr::Constant {
531 value: PyStaticConstant::Bool(true),
532 },
533 r#"{"type":"constant","kind":"bool","value":true}"#,
534 );
535 check_serialization(
536 PyStaticExpr::Constant {
537 value: PyStaticConstant::Bool(false),
538 },
539 r#"{"type":"constant","kind":"bool","value":false}"#,
540 );
541 check_serialization(
542 PyStaticExpr::Constant {
543 value: PyStaticConstant::Int("-123"),
544 },
545 r#"{"type":"constant","kind":"int","value":"-123"}"#,
546 );
547 check_serialization(
548 PyStaticExpr::Constant {
549 value: PyStaticConstant::Float("-2.1"),
550 },
551 r#"{"type":"constant","kind":"float","value":"-2.1"}"#,
552 );
553 check_serialization(
554 PyStaticExpr::Constant {
555 value: PyStaticConstant::Float("+2.1e10"),
556 },
557 r#"{"type":"constant","kind":"float","value":"+2.1e10"}"#,
558 );
559 check_serialization(
560 PyStaticExpr::Constant {
561 value: PyStaticConstant::Str("abc(1)"),
562 },
563 r#"{"type":"constant","kind":"str","value":"abc(1)"}"#,
564 );
565 check_serialization(
566 PyStaticExpr::Constant {
567 value: PyStaticConstant::Str("\"\\/\x08\x0C\n\r\t\0\x19a"),
568 },
569 r#"{"type":"constant","kind":"str","value":"\"\\/\b\f\n\r\t\u0000\u0019a"}"#,
570 );
571 check_serialization(
572 PyStaticExpr::Constant {
573 value: PyStaticConstant::Ellipsis,
574 },
575 r#"{"type":"constant","kind":"ellipsis"}"#,
576 );
577 }
578}