pytecode 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,791 @@
1
+ """JVM type descriptor and generic signature parsing utilities.
2
+
3
+ Provides structured representations for JVM field descriptors (§4.3.2),
4
+ method descriptors (§4.3.3), and generic signatures (§4.7.9.1), along
5
+ with parsing, construction, validation, and slot-counting helpers.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from enum import Enum
12
+ from typing import Literal, Never
13
+
14
+ __all__ = [
15
+ "ArrayType",
16
+ "ArrayTypeSignature",
17
+ "BaseType",
18
+ "ClassSignature",
19
+ "ClassTypeSignature",
20
+ "FieldDescriptor",
21
+ "FieldSignature",
22
+ "InnerClassType",
23
+ "JavaTypeSignature",
24
+ "MethodDescriptor",
25
+ "MethodSignature",
26
+ "ObjectType",
27
+ "ReferenceTypeSignature",
28
+ "ReturnType",
29
+ "TypeArgument",
30
+ "TypeParameter",
31
+ "TypeVariable",
32
+ "VOID",
33
+ "VoidType",
34
+ "is_valid_field_descriptor",
35
+ "is_valid_method_descriptor",
36
+ "parameter_slot_count",
37
+ "parse_class_signature",
38
+ "parse_field_descriptor",
39
+ "parse_field_signature",
40
+ "parse_method_descriptor",
41
+ "parse_method_signature",
42
+ "slot_size",
43
+ "to_descriptor",
44
+ ]
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # Descriptor data model (JVM spec §4.3.2, §4.3.3)
48
+ # ---------------------------------------------------------------------------
49
+
50
+
51
+ class BaseType(Enum):
52
+ """JVM primitive types as defined in §4.3.2.
53
+
54
+ Each member carries its single-character descriptor code as its value.
55
+ """
56
+
57
+ BOOLEAN = "Z"
58
+ BYTE = "B"
59
+ CHAR = "C"
60
+ SHORT = "S"
61
+ INT = "I"
62
+ LONG = "J"
63
+ FLOAT = "F"
64
+ DOUBLE = "D"
65
+
66
+
67
+ _BASE_TYPE_BY_CHAR: dict[str, BaseType] = {t.value: t for t in BaseType}
68
+
69
+ _TWO_SLOT_TYPES = frozenset({BaseType.LONG, BaseType.DOUBLE})
70
+
71
+
72
+ class VoidType(Enum):
73
+ """Sentinel for the ``V`` return descriptor (§4.3.3).
74
+
75
+ Only valid in the return-type position of a method descriptor.
76
+ """
77
+
78
+ VOID = "V"
79
+
80
+
81
+ VOID = VoidType.VOID
82
+
83
+
84
+ @dataclass(frozen=True, slots=True)
85
+ class ObjectType:
86
+ """Reference to a class or interface type in internal form (§4.3.2).
87
+
88
+ Attributes:
89
+ class_name: Fully-qualified name using ``/`` as the package separator
90
+ (e.g. ``java/lang/String``).
91
+ """
92
+
93
+ class_name: str
94
+
95
+
96
+ @dataclass(frozen=True, slots=True)
97
+ class ArrayType:
98
+ """Array type whose component may be any field descriptor (§4.3.2).
99
+
100
+ The component may itself be an ``ArrayType``, representing multi-dimensional
101
+ arrays.
102
+
103
+ Attributes:
104
+ component_type: The element type of the array.
105
+ """
106
+
107
+ component_type: FieldDescriptor
108
+
109
+
110
+ @dataclass(frozen=True, slots=True)
111
+ class MethodDescriptor:
112
+ """Parsed method descriptor representing parameter and return types (§4.3.3).
113
+
114
+ Attributes:
115
+ parameter_types: Ordered tuple of parameter type descriptors.
116
+ return_type: The method's return type, which may be ``VoidType.VOID``.
117
+ """
118
+
119
+ parameter_types: tuple[FieldDescriptor, ...]
120
+ return_type: ReturnType
121
+
122
+
123
+ type FieldDescriptor = BaseType | ObjectType | ArrayType
124
+ type ReturnType = FieldDescriptor | VoidType
125
+ _INVALID_UNQUALIFIED_NAME_CHARS = frozenset({".", ";", "[", "/", "<", ">", ":"})
126
+
127
+
128
+ # ---------------------------------------------------------------------------
129
+ # Generic signature data model (JVM spec §4.7.9.1)
130
+ # ---------------------------------------------------------------------------
131
+
132
+
133
+ @dataclass(frozen=True, slots=True)
134
+ class TypeVariable:
135
+ """Type variable reference (§4.7.9.1).
136
+
137
+ For example, ``TT;`` in a generic signature parses to
138
+ ``TypeVariable("T")``.
139
+
140
+ Attributes:
141
+ name: The identifier of the type variable.
142
+ """
143
+
144
+ name: str
145
+
146
+
147
+ @dataclass(frozen=True, slots=True)
148
+ class TypeArgument:
149
+ """A single type argument inside angle brackets (§4.7.9.1).
150
+
151
+ Attributes:
152
+ wildcard: ``"+"`` (extends), ``"-"`` (super), or ``None`` (exact match).
153
+ signature: The bounding type, or ``None`` for the unbounded wildcard
154
+ ``*``.
155
+ """
156
+
157
+ wildcard: Literal["+", "-"] | None
158
+ signature: ReferenceTypeSignature | None
159
+
160
+
161
+ @dataclass(frozen=True, slots=True)
162
+ class InnerClassType:
163
+ """An inner-class suffix inside a ``ClassTypeSignature`` (§4.7.9.1).
164
+
165
+ Represents a ``.SimpleClassName<TypeArgs>`` segment that follows the
166
+ outer class type.
167
+
168
+ Attributes:
169
+ name: Simple name of the inner class.
170
+ type_arguments: Generic type arguments applied to this inner class.
171
+ """
172
+
173
+ name: str
174
+ type_arguments: tuple[TypeArgument, ...]
175
+
176
+
177
+ @dataclass(frozen=True, slots=True)
178
+ class ClassTypeSignature:
179
+ """Fully-qualified generic class type (§4.7.9.1).
180
+
181
+ Example: ``Ljava/util/Map<TK;TV;>.Entry<TK;TV;>;``.
182
+
183
+ Attributes:
184
+ package: Package prefix in internal form (e.g. ``java/util/``), or
185
+ empty string for classes in the default package.
186
+ name: Simple class name.
187
+ type_arguments: Generic type arguments on the outer class.
188
+ inner: Inner-class suffixes with their own type arguments.
189
+ """
190
+
191
+ package: str
192
+ name: str
193
+ type_arguments: tuple[TypeArgument, ...]
194
+ inner: tuple[InnerClassType, ...]
195
+
196
+
197
+ @dataclass(frozen=True, slots=True)
198
+ class ArrayTypeSignature:
199
+ """Generic array type signature (§4.7.9.1).
200
+
201
+ For example, ``[TT;`` represents an array of a type variable.
202
+
203
+ Attributes:
204
+ component: The element type signature of the array.
205
+ """
206
+
207
+ component: JavaTypeSignature
208
+
209
+
210
+ type ReferenceTypeSignature = ClassTypeSignature | TypeVariable | ArrayTypeSignature
211
+ type JavaTypeSignature = BaseType | ReferenceTypeSignature
212
+
213
+
214
+ @dataclass(frozen=True, slots=True)
215
+ class TypeParameter:
216
+ """A formal type parameter declaration (§4.7.9.1).
217
+
218
+ For example, ``T:Ljava/lang/Object;`` declares a type parameter ``T``
219
+ bounded by ``Object``.
220
+
221
+ Attributes:
222
+ name: The type parameter identifier.
223
+ class_bound: Optional upper class bound, or ``None`` if absent.
224
+ interface_bounds: Additional interface bounds the type parameter must
225
+ satisfy.
226
+ """
227
+
228
+ name: str
229
+ class_bound: ReferenceTypeSignature | None
230
+ interface_bounds: tuple[ReferenceTypeSignature, ...]
231
+
232
+
233
+ @dataclass(frozen=True, slots=True)
234
+ class ClassSignature:
235
+ """Parsed generic class signature from a ``Signature`` attribute (§4.7.9.1).
236
+
237
+ Attributes:
238
+ type_parameters: Formal type parameters declared by the class.
239
+ super_class: The generic superclass type.
240
+ super_interfaces: Generic superinterface types.
241
+ """
242
+
243
+ type_parameters: tuple[TypeParameter, ...]
244
+ super_class: ClassTypeSignature
245
+ super_interfaces: tuple[ClassTypeSignature, ...]
246
+
247
+
248
+ @dataclass(frozen=True, slots=True)
249
+ class MethodSignature:
250
+ """Parsed generic method signature from a ``Signature`` attribute (§4.7.9.1).
251
+
252
+ Attributes:
253
+ type_parameters: Formal type parameters declared by the method.
254
+ parameter_types: Generic parameter type signatures.
255
+ return_type: The return type signature, or ``VoidType.VOID``.
256
+ throws: Exception types declared in the throws clause.
257
+ """
258
+
259
+ type_parameters: tuple[TypeParameter, ...]
260
+ parameter_types: tuple[JavaTypeSignature, ...]
261
+ return_type: JavaTypeSignature | VoidType
262
+ throws: tuple[ClassTypeSignature | TypeVariable, ...]
263
+
264
+
265
+ type FieldSignature = ReferenceTypeSignature
266
+
267
+
268
+ # ---------------------------------------------------------------------------
269
+ # Internal parser helpers
270
+ # ---------------------------------------------------------------------------
271
+
272
+
273
+ class _Reader:
274
+ """Minimal cursor over a string for recursive-descent parsing."""
275
+
276
+ __slots__ = ("_s", "_pos")
277
+
278
+ def __init__(self, s: str) -> None:
279
+ self._s = s
280
+ self._pos = 0
281
+
282
+ @property
283
+ def pos(self) -> int:
284
+ return self._pos
285
+
286
+ def at_end(self) -> bool:
287
+ return self._pos >= len(self._s)
288
+
289
+ def peek(self) -> str:
290
+ if self._pos >= len(self._s):
291
+ self._fail("unexpected end of string")
292
+ return self._s[self._pos]
293
+
294
+ def advance(self) -> str:
295
+ ch = self.peek()
296
+ self._pos += 1
297
+ return ch
298
+
299
+ def expect(self, ch: str) -> None:
300
+ actual = self.advance()
301
+ if actual != ch:
302
+ self._fail(f"expected '{ch}', got '{actual}'", self._pos - 1)
303
+
304
+ def remaining(self) -> str:
305
+ return self._s[self._pos :]
306
+
307
+ def _fail(self, msg: str, pos: int | None = None) -> Never:
308
+ p = pos if pos is not None else self._pos
309
+ raise ValueError(f"{msg} at position {p} in {self._s!r}")
310
+
311
+
312
+ def _validate_unqualified_name(name: str, r: _Reader, start: int, context: str) -> None:
313
+ if not name:
314
+ r._fail(f"empty {context}", start)
315
+ for ch in name:
316
+ if ch in _INVALID_UNQUALIFIED_NAME_CHARS:
317
+ r._fail(f"invalid character '{ch}' in {context}", start)
318
+
319
+
320
+ def _validate_internal_class_name(class_name: str, r: _Reader, start: int) -> None:
321
+ if not class_name:
322
+ r._fail("empty class name in object type", start)
323
+
324
+ segments = class_name.split("/")
325
+ if any(segment == "" for segment in segments):
326
+ r._fail("empty class name segment in object type", start)
327
+
328
+ for segment in segments:
329
+ _validate_unqualified_name(segment, r, start, "class name")
330
+
331
+
332
+ def _split_class_type_identifier(r: _Reader, full_ident: str, start: int) -> tuple[str, str]:
333
+ if not full_ident:
334
+ r._fail("empty class name in class type signature", start)
335
+
336
+ segments = full_ident.split("/")
337
+ if any(segment == "" for segment in segments):
338
+ r._fail("empty class name segment in class type signature", start)
339
+
340
+ for segment in segments:
341
+ _validate_unqualified_name(segment, r, start, "class name")
342
+
343
+ if len(segments) == 1:
344
+ return "", segments[0]
345
+ return "/".join(segments[:-1]) + "/", segments[-1]
346
+
347
+
348
+ def _read_field_descriptor(r: _Reader) -> FieldDescriptor:
349
+ ch = r.peek()
350
+ bt = _BASE_TYPE_BY_CHAR.get(ch)
351
+ if bt is not None:
352
+ r.advance()
353
+ return bt
354
+ if ch == "L":
355
+ return _read_object_type(r)
356
+ if ch == "[":
357
+ r.advance()
358
+ return ArrayType(_read_field_descriptor(r))
359
+ r._fail(f"invalid descriptor character '{ch}'")
360
+
361
+
362
+ def _read_object_type(r: _Reader) -> ObjectType:
363
+ r.expect("L")
364
+ start = r.pos
365
+ while r.peek() != ";":
366
+ r.advance()
367
+ class_name = r._s[start : r.pos]
368
+ r.expect(";")
369
+ _validate_internal_class_name(class_name, r, start)
370
+ return ObjectType(class_name)
371
+
372
+
373
+ def _read_return_type(r: _Reader) -> ReturnType:
374
+ if r.peek() == "V":
375
+ r.advance()
376
+ return VOID
377
+ return _read_field_descriptor(r)
378
+
379
+
380
+ # ---------------------------------------------------------------------------
381
+ # Generic signature parser helpers
382
+ # ---------------------------------------------------------------------------
383
+
384
+
385
+ def _read_reference_type_signature(r: _Reader) -> ReferenceTypeSignature:
386
+ ch = r.peek()
387
+ if ch == "L":
388
+ return _read_class_type_signature(r)
389
+ if ch == "T":
390
+ return _read_type_variable(r)
391
+ if ch == "[":
392
+ return _read_array_type_signature(r)
393
+ r._fail(f"expected reference type signature, got '{ch}'")
394
+
395
+
396
+ def _read_java_type_signature(r: _Reader) -> JavaTypeSignature:
397
+ ch = r.peek()
398
+ bt = _BASE_TYPE_BY_CHAR.get(ch)
399
+ if bt is not None:
400
+ r.advance()
401
+ return bt
402
+ return _read_reference_type_signature(r)
403
+
404
+
405
+ def _read_type_variable(r: _Reader) -> TypeVariable:
406
+ r.expect("T")
407
+ start = r.pos
408
+ while r.peek() != ";":
409
+ r.advance()
410
+ name = r._s[start : r.pos]
411
+ r.expect(";")
412
+ _validate_unqualified_name(name, r, start, "type variable name")
413
+ return TypeVariable(name)
414
+
415
+
416
+ def _read_type_arguments(r: _Reader) -> tuple[TypeArgument, ...]:
417
+ r.expect("<")
418
+ args: list[TypeArgument] = []
419
+ while r.peek() != ">":
420
+ args.append(_read_type_argument(r))
421
+ r.expect(">")
422
+ return tuple(args)
423
+
424
+
425
+ def _read_type_argument(r: _Reader) -> TypeArgument:
426
+ ch = r.peek()
427
+ if ch == "*":
428
+ r.advance()
429
+ return TypeArgument(wildcard=None, signature=None)
430
+ wildcard: Literal["+", "-"] | None = None
431
+ if ch in ("+", "-"):
432
+ wildcard = ch # pyright: ignore[reportAssignmentType]
433
+ r.advance()
434
+ sig = _read_reference_type_signature(r)
435
+ return TypeArgument(wildcard=wildcard, signature=sig)
436
+
437
+
438
+ def _read_class_type_signature(r: _Reader) -> ClassTypeSignature:
439
+ r.expect("L")
440
+ start = r.pos
441
+
442
+ # Collect the full identifier path (package + simple name) first.
443
+ ident_chars: list[str] = []
444
+ while r.peek() not in ("<", ".", ";"):
445
+ ident_chars.append(r.advance())
446
+
447
+ full_ident = "".join(ident_chars)
448
+ package, name = _split_class_type_identifier(r, full_ident, start)
449
+
450
+ # Optional type arguments.
451
+ type_arguments: tuple[TypeArgument, ...] = ()
452
+ if not r.at_end() and r.peek() == "<":
453
+ type_arguments = _read_type_arguments(r)
454
+
455
+ # Inner class suffixes.
456
+ inner: list[InnerClassType] = []
457
+ while not r.at_end() and r.peek() == ".":
458
+ r.advance() # consume '.'
459
+ inner_start = r.pos
460
+ inner_name_chars: list[str] = []
461
+ while r.peek() not in ("<", ".", ";"):
462
+ inner_name_chars.append(r.advance())
463
+ inner_name = "".join(inner_name_chars)
464
+ _validate_unqualified_name(inner_name, r, inner_start, "inner class name")
465
+ inner_type_args: tuple[TypeArgument, ...] = ()
466
+ if not r.at_end() and r.peek() == "<":
467
+ inner_type_args = _read_type_arguments(r)
468
+ inner.append(InnerClassType(inner_name, inner_type_args))
469
+
470
+ r.expect(";")
471
+ return ClassTypeSignature(package, name, type_arguments, tuple(inner))
472
+
473
+
474
+ def _read_array_type_signature(r: _Reader) -> ArrayTypeSignature:
475
+ r.expect("[")
476
+ component = _read_java_type_signature(r)
477
+ return ArrayTypeSignature(component)
478
+
479
+
480
+ def _read_type_parameters(r: _Reader) -> tuple[TypeParameter, ...]:
481
+ r.expect("<")
482
+ params: list[TypeParameter] = []
483
+ while r.peek() != ">":
484
+ params.append(_read_type_parameter(r))
485
+ r.expect(">")
486
+ return tuple(params)
487
+
488
+
489
+ def _read_type_parameter(r: _Reader) -> TypeParameter:
490
+ # Identifier ':' [ClassBound] {':' InterfaceBound}
491
+ start = r.pos
492
+ name_chars: list[str] = []
493
+ while r.peek() != ":":
494
+ name_chars.append(r.advance())
495
+ name = "".join(name_chars)
496
+ if not name:
497
+ r._fail("empty type parameter name", start)
498
+
499
+ r.expect(":")
500
+
501
+ # Class bound — may be empty (just ':' followed by another ':' or next param or '>').
502
+ class_bound: ReferenceTypeSignature | None = None
503
+ if not r.at_end() and r.peek() not in (":", ">"):
504
+ class_bound = _read_reference_type_signature(r)
505
+
506
+ # Interface bounds (each prefixed by ':').
507
+ interface_bounds: list[ReferenceTypeSignature] = []
508
+ while not r.at_end() and r.peek() == ":":
509
+ r.advance() # consume ':'
510
+ interface_bounds.append(_read_reference_type_signature(r))
511
+
512
+ return TypeParameter(name, class_bound, tuple(interface_bounds))
513
+
514
+
515
+ def _read_return_type_signature(r: _Reader) -> JavaTypeSignature | VoidType:
516
+ if r.peek() == "V":
517
+ r.advance()
518
+ return VOID
519
+ return _read_java_type_signature(r)
520
+
521
+
522
+ def _read_throws_signature(r: _Reader) -> ClassTypeSignature | TypeVariable:
523
+ r.expect("^")
524
+ ch = r.peek()
525
+ if ch == "L":
526
+ return _read_class_type_signature(r)
527
+ if ch == "T":
528
+ return _read_type_variable(r)
529
+ r._fail(f"expected class type or type variable after '^', got '{ch}'")
530
+
531
+
532
+ # ---------------------------------------------------------------------------
533
+ # Public parsing API
534
+ # ---------------------------------------------------------------------------
535
+
536
+
537
+ def parse_field_descriptor(s: str) -> FieldDescriptor:
538
+ """Parse a JVM field descriptor string into a structured type (§4.3.2).
539
+
540
+ Args:
541
+ s: A field descriptor string such as ``"I"`` or
542
+ ``"Ljava/lang/String;"``.
543
+
544
+ Returns:
545
+ The parsed descriptor as a ``BaseType``, ``ObjectType``, or
546
+ ``ArrayType``.
547
+
548
+ Raises:
549
+ ValueError: If *s* is not a valid field descriptor.
550
+
551
+ Examples:
552
+ >>> parse_field_descriptor("Ljava/lang/String;")
553
+ ObjectType(class_name='java/lang/String')
554
+ >>> parse_field_descriptor("[[I")
555
+ ArrayType(component_type=ArrayType(component_type=<BaseType.INT: 'I'>))
556
+ """
557
+ r = _Reader(s)
558
+ result = _read_field_descriptor(r)
559
+ if not r.at_end():
560
+ r._fail("trailing characters after field descriptor")
561
+ return result
562
+
563
+
564
+ def parse_method_descriptor(s: str) -> MethodDescriptor:
565
+ """Parse a JVM method descriptor string into parameter and return types (§4.3.3).
566
+
567
+ Args:
568
+ s: A method descriptor string such as
569
+ ``"(IDLjava/lang/Thread;)Ljava/lang/Object;"``.
570
+
571
+ Returns:
572
+ A ``MethodDescriptor`` with the parsed parameter and return types.
573
+
574
+ Raises:
575
+ ValueError: If *s* is not a valid method descriptor.
576
+
577
+ Examples:
578
+ >>> parse_method_descriptor("(IDLjava/lang/Thread;)Ljava/lang/Object;")
579
+ MethodDescriptor(parameter_types=(...), return_type=ObjectType(...))
580
+ """
581
+ r = _Reader(s)
582
+ r.expect("(")
583
+ params: list[FieldDescriptor] = []
584
+ while r.peek() != ")":
585
+ params.append(_read_field_descriptor(r))
586
+ r.expect(")")
587
+ ret = _read_return_type(r)
588
+ if not r.at_end():
589
+ r._fail("trailing characters after method descriptor")
590
+ return MethodDescriptor(tuple(params), ret)
591
+
592
+
593
+ def parse_class_signature(s: str) -> ClassSignature:
594
+ """Parse a generic class signature from a ``Signature`` attribute (§4.7.9.1).
595
+
596
+ Args:
597
+ s: A class signature string such as
598
+ ``"<T:Ljava/lang/Object;>Ljava/lang/Object;"``.
599
+
600
+ Returns:
601
+ A ``ClassSignature`` with type parameters, superclass, and
602
+ superinterfaces.
603
+
604
+ Raises:
605
+ ValueError: If *s* is not a valid class signature.
606
+
607
+ Examples:
608
+ >>> parse_class_signature("<T:Ljava/lang/Object;>Ljava/lang/Object;")
609
+ ClassSignature(...)
610
+ """
611
+ r = _Reader(s)
612
+ type_params: tuple[TypeParameter, ...] = ()
613
+ if r.peek() == "<":
614
+ type_params = _read_type_parameters(r)
615
+ super_class = _read_class_type_signature(r)
616
+ super_interfaces: list[ClassTypeSignature] = []
617
+ while not r.at_end():
618
+ super_interfaces.append(_read_class_type_signature(r))
619
+ return ClassSignature(type_params, super_class, tuple(super_interfaces))
620
+
621
+
622
+ def parse_method_signature(s: str) -> MethodSignature:
623
+ """Parse a generic method signature from a ``Signature`` attribute (§4.7.9.1).
624
+
625
+ Args:
626
+ s: A method signature string such as
627
+ ``"<T:Ljava/lang/Object;>(TT;)TT;"``.
628
+
629
+ Returns:
630
+ A ``MethodSignature`` with type parameters, parameter types, return
631
+ type, and throws clause.
632
+
633
+ Raises:
634
+ ValueError: If *s* is not a valid method signature.
635
+
636
+ Examples:
637
+ >>> parse_method_signature("<T:Ljava/lang/Object;>(TT;)TT;")
638
+ MethodSignature(...)
639
+ """
640
+ r = _Reader(s)
641
+ type_params: tuple[TypeParameter, ...] = ()
642
+ if r.peek() == "<":
643
+ type_params = _read_type_parameters(r)
644
+ r.expect("(")
645
+ param_types: list[JavaTypeSignature] = []
646
+ while r.peek() != ")":
647
+ param_types.append(_read_java_type_signature(r))
648
+ r.expect(")")
649
+ ret = _read_return_type_signature(r)
650
+ throws: list[ClassTypeSignature | TypeVariable] = []
651
+ while not r.at_end():
652
+ throws.append(_read_throws_signature(r))
653
+ return MethodSignature(type_params, tuple(param_types), ret, tuple(throws))
654
+
655
+
656
+ def parse_field_signature(s: str) -> FieldSignature:
657
+ """Parse a generic field type signature from a ``Signature`` attribute (§4.7.9.1).
658
+
659
+ Args:
660
+ s: A field signature string such as
661
+ ``"Ljava/util/List<Ljava/lang/String;>;"``.
662
+
663
+ Returns:
664
+ The parsed reference type signature.
665
+
666
+ Raises:
667
+ ValueError: If *s* is not a valid field signature.
668
+
669
+ Examples:
670
+ >>> parse_field_signature("Ljava/util/List<Ljava/lang/String;>;")
671
+ ClassTypeSignature(...)
672
+ """
673
+ r = _Reader(s)
674
+ result = _read_reference_type_signature(r)
675
+ if not r.at_end():
676
+ r._fail("trailing characters after field signature")
677
+ return result
678
+
679
+
680
+ # ---------------------------------------------------------------------------
681
+ # Descriptor string construction (round-trip)
682
+ # ---------------------------------------------------------------------------
683
+
684
+
685
+ def _field_descriptor_to_str(t: FieldDescriptor) -> str:
686
+ if isinstance(t, BaseType):
687
+ return t.value
688
+ if isinstance(t, ObjectType):
689
+ return f"L{t.class_name};"
690
+ if isinstance(t, ArrayType): # pyright: ignore[reportUnnecessaryIsInstance]
691
+ return f"[{_field_descriptor_to_str(t.component_type)}"
692
+ raise TypeError(f"unexpected descriptor type: {type(t)}") # pyright: ignore[reportUnreachable]
693
+
694
+
695
+ def to_descriptor(t: FieldDescriptor | MethodDescriptor) -> str:
696
+ """Convert a structured descriptor back into its JVM string form (§4.3).
697
+
698
+ Args:
699
+ t: A field or method descriptor to serialize.
700
+
701
+ Returns:
702
+ The canonical JVM descriptor string.
703
+
704
+ Raises:
705
+ TypeError: If *t* is not a recognized descriptor type.
706
+
707
+ Examples:
708
+ >>> to_descriptor(BaseType.INT)
709
+ 'I'
710
+ >>> to_descriptor(ObjectType("java/lang/String"))
711
+ 'Ljava/lang/String;'
712
+ >>> to_descriptor(MethodDescriptor((BaseType.INT,), VOID))
713
+ '(I)V'
714
+ """
715
+ if isinstance(t, MethodDescriptor):
716
+ params = "".join(_field_descriptor_to_str(p) for p in t.parameter_types)
717
+ ret = "V" if isinstance(t.return_type, VoidType) else _field_descriptor_to_str(t.return_type)
718
+ return f"({params}){ret}"
719
+ return _field_descriptor_to_str(t)
720
+
721
+
722
+ # ---------------------------------------------------------------------------
723
+ # Slot helpers
724
+ # ---------------------------------------------------------------------------
725
+
726
+
727
+ def slot_size(t: FieldDescriptor) -> int:
728
+ """Return the number of JVM local/stack slots a type occupies.
729
+
730
+ ``long`` and ``double`` use two slots (§2.6.1); all other types use one.
731
+
732
+ Args:
733
+ t: A field descriptor for the value type.
734
+
735
+ Returns:
736
+ ``2`` for ``long``/``double``, ``1`` otherwise.
737
+ """
738
+ if isinstance(t, BaseType) and t in _TWO_SLOT_TYPES:
739
+ return 2
740
+ return 1
741
+
742
+
743
+ def parameter_slot_count(d: MethodDescriptor) -> int:
744
+ """Return the total number of JVM parameter slots for a method descriptor.
745
+
746
+ Does **not** include the implicit ``this`` slot for instance methods.
747
+
748
+ Args:
749
+ d: A parsed method descriptor.
750
+
751
+ Returns:
752
+ Sum of slot sizes across all parameter types.
753
+ """
754
+ return sum(slot_size(p) for p in d.parameter_types)
755
+
756
+
757
+ # ---------------------------------------------------------------------------
758
+ # Validation helpers
759
+ # ---------------------------------------------------------------------------
760
+
761
+
762
+ def is_valid_field_descriptor(s: str) -> bool:
763
+ """Return ``True`` if *s* is a well-formed JVM field descriptor (§4.3.2).
764
+
765
+ Args:
766
+ s: The string to validate.
767
+
768
+ Returns:
769
+ Whether *s* can be parsed as a valid field descriptor.
770
+ """
771
+ try:
772
+ parse_field_descriptor(s)
773
+ return True
774
+ except ValueError:
775
+ return False
776
+
777
+
778
+ def is_valid_method_descriptor(s: str) -> bool:
779
+ """Return ``True`` if *s* is a well-formed JVM method descriptor (§4.3.3).
780
+
781
+ Args:
782
+ s: The string to validate.
783
+
784
+ Returns:
785
+ Whether *s* can be parsed as a valid method descriptor.
786
+ """
787
+ try:
788
+ parse_method_descriptor(s)
789
+ return True
790
+ except ValueError:
791
+ return False