Typhon-Language 0.1.2__py3-none-any.whl → 0.1.3__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.
Files changed (29) hide show
  1. Typhon/Driver/translate.py +2 -1
  2. Typhon/Grammar/_typhon_parser.py +1638 -1649
  3. Typhon/Grammar/syntax_errors.py +11 -0
  4. Typhon/Grammar/typhon_ast.py +405 -55
  5. Typhon/Grammar/unparse_custom.py +25 -0
  6. Typhon/SourceMap/datatype.py +264 -264
  7. Typhon/Transform/const_member_to_final.py +1 -1
  8. Typhon/Transform/extended_patterns.py +139 -0
  9. Typhon/Transform/forbidden_statements.py +24 -0
  10. Typhon/Transform/if_while_let.py +122 -11
  11. Typhon/Transform/inline_statement_block_capture.py +22 -15
  12. Typhon/Transform/placeholder_to_function.py +0 -1
  13. Typhon/Transform/record_to_dataclass.py +22 -238
  14. Typhon/Transform/scope_check_rename.py +65 -11
  15. Typhon/Transform/transform.py +16 -12
  16. Typhon/Transform/type_abbrev_desugar.py +1 -1
  17. Typhon/Transform/utils/__init__.py +0 -0
  18. Typhon/Transform/utils/imports.py +48 -0
  19. Typhon/Transform/{utils.py → utils/jump_away.py} +2 -38
  20. Typhon/Transform/utils/make_class.py +140 -0
  21. Typhon/Typing/pyright.py +143 -144
  22. Typhon/Typing/result_diagnostic.py +1 -1
  23. {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/METADATA +7 -2
  24. typhon_language-0.1.3.dist-info/RECORD +53 -0
  25. typhon_language-0.1.2.dist-info/RECORD +0 -48
  26. {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/WHEEL +0 -0
  27. {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/entry_points.txt +0 -0
  28. {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/licenses/LICENSE +0 -0
  29. {typhon_language-0.1.2.dist-info → typhon_language-0.1.3.dist-info}/top_level.txt +0 -0
@@ -116,6 +116,17 @@ def raise_type_annotation_error(
116
116
  raise TypeAnnotationError(message, **pos)
117
117
 
118
118
 
119
+ class LetMissingElseError(TyphonSyntaxError):
120
+ pass
121
+
122
+
123
+ def raise_let_missing_else_error(
124
+ message: str,
125
+ **pos: Unpack[PosAttributes],
126
+ ):
127
+ raise LetMissingElseError(message, **pos)
128
+
129
+
119
130
  def _get_range_of_error(
120
131
  syntax_error: SyntaxError | TyphonSyntaxError,
121
132
  ) -> Range:
@@ -2,6 +2,7 @@
2
2
 
3
3
  import ast
4
4
  from typing import Union, Unpack, TypedDict, Tuple, cast
5
+ from dataclasses import dataclass
5
6
  from copy import copy
6
7
  from ..Driver.debugging import debug_print, debug_verbose_print
7
8
 
@@ -77,6 +78,75 @@ def get_empty_pos_attributes() -> PosAttributes:
77
78
  )
78
79
 
79
80
 
81
+ _ANONYMOUS_NAME = "_typh_anonymous"
82
+ _anonymous_global_id = 0
83
+
84
+
85
+ def set_anonymous_name_id(node: ast.Name, id: int) -> ast.Name:
86
+ setattr(node, _ANONYMOUS_NAME, id)
87
+ return node
88
+
89
+
90
+ def get_anonymous_name_id(node: ast.Name) -> int | None:
91
+ return getattr(node, _ANONYMOUS_NAME, None)
92
+
93
+
94
+ def clear_anonymous_name(node: ast.Name) -> None:
95
+ if hasattr(node, _ANONYMOUS_NAME):
96
+ delattr(node, _ANONYMOUS_NAME)
97
+
98
+
99
+ def is_anonymous_name(node: ast.Name) -> bool:
100
+ return hasattr(node, _ANONYMOUS_NAME)
101
+
102
+
103
+ def make_anonymous_name(
104
+ ctx: ast.expr_context, **kwargs: Unpack[PosAttributes]
105
+ ) -> tuple[ast.Name, int]:
106
+ global _anonymous_global_id
107
+ anon_id = _anonymous_global_id
108
+ name = ast.Name(f"{_ANONYMOUS_NAME}_{anon_id}", ctx, **kwargs)
109
+ set_anonymous_name_id(name, anon_id)
110
+ _anonymous_global_id += 1
111
+ return name, anon_id
112
+
113
+
114
+ def copy_anonymous_name(src: ast.Name, ctx: ast.expr_context) -> ast.Name:
115
+ result = ast.Name(src.id, ctx, **get_pos_attributes(src))
116
+ anon_id = get_anonymous_name_id(src)
117
+ if anon_id is not None:
118
+ set_anonymous_name_id(result, anon_id)
119
+ return result
120
+
121
+
122
+ _TYPE_IGNORE_NODES = "_typh_type_ignore"
123
+
124
+
125
+ def get_type_ignore_tag(node: ast.AST) -> str | None:
126
+ return getattr(node, _TYPE_IGNORE_NODES, None)
127
+
128
+
129
+ def get_type_ignore_comment(node: ast.AST) -> str | None:
130
+ tag = get_type_ignore_tag(node)
131
+ if tag is not None:
132
+ return f"# type: ignore[{tag}]"
133
+ return None
134
+
135
+
136
+ def set_type_ignore_node(node: ast.AST, tag: str) -> ast.AST:
137
+ setattr(node, _TYPE_IGNORE_NODES, tag)
138
+ return node
139
+
140
+
141
+ def is_type_ignore_node(node: ast.AST) -> bool:
142
+ return hasattr(node, _TYPE_IGNORE_NODES)
143
+
144
+
145
+ def clear_type_ignore_node(node: ast.AST) -> None:
146
+ if hasattr(node, _TYPE_IGNORE_NODES):
147
+ delattr(node, _TYPE_IGNORE_NODES)
148
+
149
+
80
150
  # Normal assignments, let assignments for variable declarations,
81
151
  # and constant assignments for constant definitions.
82
152
  # They all are Assign/AnnAssign in Python, we distinguish them by
@@ -93,7 +163,13 @@ def is_decl_stmt(node: ast.AST) -> bool:
93
163
 
94
164
 
95
165
  type DeclarableStmt = Union[
96
- ast.Assign, ast.AnnAssign, ast.withitem, ast.For, ast.AsyncFor, ast.comprehension
166
+ ast.Assign,
167
+ ast.AnnAssign,
168
+ ast.withitem,
169
+ ast.For,
170
+ ast.AsyncFor,
171
+ ast.comprehension,
172
+ ast.pattern,
97
173
  ]
98
174
 
99
175
  _IS_VAR = "_typh_is_var"
@@ -399,6 +475,99 @@ def declaration_as_withitem(assign: Union[ast.Assign, ast.AnnAssign]) -> ast.wit
399
475
  return item
400
476
 
401
477
 
478
+ def _make_with_let_pattern(
479
+ is_async: bool,
480
+ decl_type: str,
481
+ pattern_subjects: list[tuple[ast.pattern, ast.expr]],
482
+ body: list[ast.stmt],
483
+ **kwargs: Unpack[PosAttributes],
484
+ ) -> tuple[ast.stmt, list[ast.withitem]]:
485
+ items: list[ast.withitem] = []
486
+ pattern_vars: list[tuple[ast.pattern, ast.expr]] = []
487
+ for pattern, subject in pattern_subjects:
488
+ var, var_id = make_anonymous_name(ast.Store(), **get_pos_attributes(pattern))
489
+ item = ast.withitem(context_expr=subject, optional_vars=var)
490
+ _set_is_let_var(item, decl_type)
491
+ items.append(item)
492
+ pattern_vars.append((pattern, copy_anonymous_name(var, ast.Load())))
493
+ let_pattern_stmt = make_if_let(
494
+ decl_type,
495
+ pattern_subjects=pattern_vars,
496
+ cond=None,
497
+ body=body,
498
+ orelse=None,
499
+ is_let_else=True,
500
+ **kwargs,
501
+ )
502
+ return let_pattern_stmt, items
503
+
504
+
505
+ def make_with_let_pattern(
506
+ is_async: bool,
507
+ decl_type: str,
508
+ pattern_subjects: list[tuple[ast.pattern, ast.expr]],
509
+ body: list[ast.stmt],
510
+ **kwargs: Unpack[PosAttributes],
511
+ ) -> ast.With | ast.AsyncWith:
512
+ let_pattern_stmt, items = _make_with_let_pattern(
513
+ is_async,
514
+ decl_type,
515
+ pattern_subjects,
516
+ body,
517
+ **kwargs,
518
+ )
519
+ return make_with_stmt(
520
+ is_async=is_async,
521
+ items=items,
522
+ body=[let_pattern_stmt],
523
+ is_inline=False,
524
+ **kwargs,
525
+ )
526
+
527
+
528
+ def make_inline_with_let_pattern(
529
+ is_async: bool,
530
+ decl_type: str,
531
+ pattern_subjects: list[tuple[ast.pattern, ast.expr]],
532
+ **kwargs: Unpack[PosAttributes],
533
+ ) -> list[ast.stmt]:
534
+ """
535
+ with <expr1> as <temp_name1>, ...: # inline with_let_pattern
536
+ if True: # Sequentially expanded. Captured later.
537
+ match <temp_name1>:
538
+ case <pattern1>:
539
+ match <temp_name2>:
540
+ case <pattern2>:
541
+ ...
542
+ match <temp_nameN>:
543
+ case <patternN> if <cond>:
544
+ <body>
545
+ case _:
546
+ pass
547
+ ...
548
+ case _:
549
+ pass
550
+ case _:
551
+ pass
552
+ """
553
+ let_pattern_stmt, items = _make_with_let_pattern(
554
+ is_async,
555
+ decl_type,
556
+ pattern_subjects,
557
+ body=[],
558
+ **kwargs,
559
+ )
560
+ inline_with = make_with_stmt(
561
+ is_async=is_async,
562
+ items=items,
563
+ body=[],
564
+ is_inline=True,
565
+ **kwargs,
566
+ )
567
+ # inline_with will capture the let pattern stmt (and the following stmts) in its body later.
568
+ return [inline_with, let_pattern_stmt]
569
+
570
+
402
571
  # Use Name as a function literal. Replaced to name of FunctionDef.
403
572
  type FunctionLiteral = ast.Name
404
573
 
@@ -628,6 +797,40 @@ def make_for_stmt(
628
797
  return result
629
798
 
630
799
 
800
+ def make_for_let_pattern(
801
+ decl_type: str,
802
+ pattern: ast.pattern,
803
+ iter: ast.expr,
804
+ body: list[ast.stmt],
805
+ orelse: list[ast.stmt] | None,
806
+ type_comment: str | None,
807
+ is_async: bool,
808
+ **kwargs: Unpack[PosAttributes],
809
+ ):
810
+ temp_name, anon_id = make_anonymous_name(ast.Load(), **get_pos_attributes(pattern))
811
+ let_stmt = make_if_let(
812
+ decl_type,
813
+ pattern_subjects=[(pattern, temp_name)],
814
+ cond=None,
815
+ body=body,
816
+ orelse=None,
817
+ is_let_else=True,
818
+ **kwargs,
819
+ )
820
+ temp_name_store = copy_anonymous_name(temp_name, ast.Store())
821
+ return make_for_stmt(
822
+ decl_type=decl_type,
823
+ target=temp_name_store,
824
+ type_annotation=None,
825
+ iter=iter,
826
+ body=[let_stmt],
827
+ orelse=orelse,
828
+ type_comment=type_comment,
829
+ is_async=is_async,
830
+ **kwargs,
831
+ )
832
+
833
+
631
834
  def _make_none_check(name: str, pos: PosAttributes) -> ast.Compare:
632
835
  return ast.Compare(
633
836
  left=ast.Name(id=name, ctx=ast.Load(), **pos),
@@ -641,12 +844,18 @@ _LET_PATTERN_BODY = "_typh_multiple_let_pattern_body"
641
844
  _IS_LET_ELSE = "_typh_is_let_else"
642
845
 
643
846
 
644
- def get_let_pattern_body(node: ast.While | ast.If) -> list[ast.stmt] | None:
847
+ @dataclass
848
+ class LetPatternInfo:
849
+ body: list[ast.stmt]
850
+ is_all_pattern_irrefutable: bool
851
+
852
+
853
+ def get_let_pattern_body(node: ast.While | ast.If) -> LetPatternInfo | None:
645
854
  return getattr(node, _LET_PATTERN_BODY, None)
646
855
 
647
856
 
648
857
  def set_let_pattern_body(
649
- node: ast.While | ast.If, body: list[ast.stmt]
858
+ node: ast.While | ast.If, body: LetPatternInfo
650
859
  ) -> ast.While | ast.If:
651
860
  setattr(node, _LET_PATTERN_BODY, body)
652
861
  return node
@@ -657,21 +866,25 @@ def clear_let_pattern_body(node: ast.While | ast.If) -> None:
657
866
  delattr(node, _LET_PATTERN_BODY)
658
867
 
659
868
 
660
- def is_let_else(node: ast.If | ast.Match) -> bool:
869
+ type LetElseAnnotatedNode = ast.If | ast.Match | ast.match_case
870
+
871
+
872
+ def is_let_else(node: LetElseAnnotatedNode) -> bool:
661
873
  return getattr(node, _IS_LET_ELSE, False)
662
874
 
663
875
 
664
- def set_is_let_else(node: ast.If | ast.Match, is_let_else: bool) -> ast.stmt:
876
+ def set_is_let_else[T: LetElseAnnotatedNode](node: T, is_let_else: bool) -> T:
665
877
  setattr(node, _IS_LET_ELSE, is_let_else)
666
878
  return node
667
879
 
668
880
 
669
- def clear_is_let_else(node: ast.If | ast.Match) -> None:
881
+ def clear_is_let_else(node: LetElseAnnotatedNode) -> None:
670
882
  if hasattr(node, _IS_LET_ELSE):
671
883
  delattr(node, _IS_LET_ELSE)
672
884
 
673
885
 
674
886
  def make_if_let(
887
+ decl_type: str,
675
888
  pattern_subjects: list[tuple[ast.pattern, ast.expr]],
676
889
  cond: ast.expr | None,
677
890
  body: list[ast.stmt],
@@ -681,25 +894,24 @@ def make_if_let(
681
894
  ) -> ast.stmt:
682
895
  if len(pattern_subjects) == 0:
683
896
  raise SyntaxError("if let must have at least one pattern")
684
- # elif len(pattern_subjects) == 1:
685
- # (pattern, subject) = pattern_subjects[0]
686
- # return set_is_let_else(
687
- # _make_if_let_single(subject, pattern, cond, body, orelse, **kwargs),
688
- # is_let_else,
689
- # )
690
897
  else:
691
898
  return set_is_let_else(
692
- _make_if_let_multiple(pattern_subjects, cond, body, orelse, **kwargs),
899
+ _make_if_let_multiple(
900
+ decl_type, pattern_subjects, cond, body, orelse, is_let_else, **kwargs
901
+ ),
693
902
  is_let_else,
694
903
  )
695
904
 
696
905
 
697
906
  def _make_if_let_single_case(
907
+ decl_type: str,
698
908
  pattern: ast.pattern,
699
909
  cond: ast.expr | None,
700
910
  body: list[ast.stmt],
701
911
  make_none_check: bool,
912
+ is_let_else: bool,
702
913
  ) -> ast.match_case:
914
+ _set_is_let_var(pattern, decl_type)
703
915
  if (
704
916
  isinstance(pattern, ast.MatchAs)
705
917
  and pattern.pattern is None
@@ -709,37 +921,66 @@ def _make_if_let_single_case(
709
921
  ):
710
922
  # Variable capture pattern, e.g. `let x = ...` without condition.
711
923
  # In this case, the condition is None check.
712
- return ast.match_case(
713
- pattern=pattern,
714
- guard=_make_none_check(pattern.name, get_pos_attributes(pattern)),
715
- body=body,
924
+ return set_is_let_else(
925
+ ast.match_case(
926
+ pattern=pattern,
927
+ guard=_make_none_check(pattern.name, get_pos_attributes(pattern)),
928
+ body=body,
929
+ ),
930
+ is_let_else,
716
931
  )
717
932
  else:
718
- return ast.match_case(
719
- pattern=pattern,
720
- guard=cond,
721
- body=body,
933
+ return set_is_let_else(
934
+ ast.match_case(
935
+ pattern=pattern,
936
+ guard=cond,
937
+ body=body,
938
+ ),
939
+ is_let_else,
722
940
  )
723
941
 
724
942
 
725
943
  def _make_nested_match_for_multiple_let(
944
+ decl_type: str,
726
945
  pattern_subjects: list[tuple[ast.pattern, ast.expr]],
727
946
  cond: ast.expr | None,
728
947
  body: list[ast.stmt],
948
+ type_error_on_failure: bool,
949
+ is_let_else: bool,
729
950
  **kwargs: Unpack[PosAttributes],
730
951
  ) -> list[ast.stmt]:
731
952
  # Build nested match statements from inside out.
732
953
  for pattern, subject in reversed(pattern_subjects):
954
+ # Add a wildcard case to handle non-matching case to avoid linter error.
955
+ default_case = ast.match_case( # case _: pass
956
+ pattern=ast.MatchAs(
957
+ name=None, pattern=None, **pos_attribute_to_range(kwargs)
958
+ ),
959
+ guard=None,
960
+ body=[
961
+ ast.Raise(
962
+ ast.Name(id="TypeError", ctx=ast.Load(), **kwargs),
963
+ None,
964
+ **kwargs,
965
+ )
966
+ if type_error_on_failure
967
+ else ast.Pass(**kwargs)
968
+ ],
969
+ )
970
+ if type_error_on_failure:
971
+ # Ignore unreachable clause error for this default case, because this is recovery for the
972
+ # case type check can not detect a pattern mismatch (e.g. due to cast).
973
+ set_type_ignore_node(default_case, "all")
733
974
  cases = [
734
- _make_if_let_single_case(pattern, cond, body, make_none_check=cond is None),
735
- # Add a wildcard case to handle non-matching case to avoid linter error.
736
- ast.match_case( # case _: pass
737
- pattern=ast.MatchAs(
738
- name=None, pattern=None, **pos_attribute_to_range(kwargs)
739
- ),
740
- guard=None,
741
- body=[ast.Pass(**kwargs)],
975
+ _make_if_let_single_case(
976
+ decl_type,
977
+ pattern,
978
+ cond,
979
+ body,
980
+ make_none_check=cond is None,
981
+ is_let_else=is_let_else,
742
982
  ),
983
+ default_case,
743
984
  ]
744
985
  nested_match: ast.stmt = ast.Match(
745
986
  subject=subject,
@@ -753,10 +994,12 @@ def _make_nested_match_for_multiple_let(
753
994
 
754
995
  # Multiple patterns are combined into nested match statements.
755
996
  def _make_if_let_multiple(
997
+ decl_type: str,
756
998
  pattern_subjects: list[tuple[ast.pattern, ast.expr]],
757
999
  cond: ast.expr | None,
758
1000
  body: list[ast.stmt],
759
1001
  orelse: list[ast.stmt] | None,
1002
+ is_let_else: bool,
760
1003
  **kwargs: Unpack[PosAttributes],
761
1004
  ) -> ast.If:
762
1005
  """
@@ -777,19 +1020,49 @@ def _make_if_let_multiple(
777
1020
  match <subjectN>:
778
1021
  case <patternN> if <cond>:
779
1022
  <body>
1023
+ case _:
1024
+ pass
1025
+ ...
1026
+ case _:
1027
+ pass
1028
+ case _:
1029
+ pass
780
1030
  else:
781
1031
  <orelse>
782
1032
  """
783
1033
  # Build nested match statements from inside out.
1034
+ is_all_pattern_irrefutable = all(
1035
+ is_pattern_irrefutable(pat) for pat, _ in pattern_subjects
1036
+ )
1037
+ is_all_pattern_truly_irrefutable = all(
1038
+ is_pattern_irrefutable(pat, assume_type_checked=False)
1039
+ for pat, _ in pattern_subjects
1040
+ )
784
1041
  result = ast.If(
785
1042
  test=ast.Constant(value=True, **kwargs),
786
1043
  body=_make_nested_match_for_multiple_let(
787
- pattern_subjects, cond, body, **kwargs
1044
+ decl_type,
1045
+ pattern_subjects,
1046
+ cond,
1047
+ body,
1048
+ type_error_on_failure=(
1049
+ is_let_else
1050
+ and (orelse is None)
1051
+ and (not is_all_pattern_truly_irrefutable)
1052
+ ),
1053
+ is_let_else=is_let_else,
1054
+ **kwargs,
788
1055
  ),
789
1056
  orelse=orelse or [],
790
1057
  **kwargs,
791
1058
  )
792
- set_let_pattern_body(result, body)
1059
+ set_let_pattern_body(
1060
+ result,
1061
+ LetPatternInfo(
1062
+ body=body,
1063
+ is_all_pattern_irrefutable=is_all_pattern_irrefutable,
1064
+ ),
1065
+ )
793
1066
  return result
794
1067
 
795
1068
 
@@ -842,12 +1115,20 @@ def _make_while_let(
842
1115
  result = ast.While(
843
1116
  test=ast.Constant(value=True, **kwargs),
844
1117
  body=_make_nested_match_for_multiple_let(
845
- pattern_subjects, cond, body, **kwargs
1118
+ "let", pattern_subjects, cond, body, False, False, **kwargs
846
1119
  ),
847
1120
  orelse=orelse or [],
848
1121
  **kwargs,
849
1122
  )
850
- set_let_pattern_body(result, body)
1123
+ set_let_pattern_body(
1124
+ result,
1125
+ LetPatternInfo(
1126
+ body=body,
1127
+ is_all_pattern_irrefutable=all(
1128
+ is_pattern_irrefutable(pat) for pat, _ in pattern_subjects
1129
+ ),
1130
+ ),
1131
+ )
851
1132
  return result
852
1133
 
853
1134
 
@@ -1358,6 +1639,7 @@ def make_if_let_comp(
1358
1639
  args=_empty_args(),
1359
1640
  body=[
1360
1641
  make_if_let(
1642
+ "let",
1361
1643
  pattern_subjects,
1362
1644
  cond,
1363
1645
  [ast.Return(value=body, **get_pos_attributes(body))],
@@ -1563,10 +1845,8 @@ def clear_is_placeholder(node: ast.Name) -> None:
1563
1845
 
1564
1846
  _RECORD_LITERAL_FIELDS = "_typh_is_record_literal_fields"
1565
1847
  _RECORD_TYPE = "_typh_is_record_literal_type"
1566
- _RECORD_PATTERN = "_typh_is_record_pattern"
1567
1848
  type RecordLiteral = ast.Name
1568
1849
  type RecordType = ast.Name
1569
- type RecordPatternClass = ast.Name
1570
1850
 
1571
1851
 
1572
1852
  def set_record_literal_fields(
@@ -1637,24 +1917,29 @@ def make_record_type(
1637
1917
  return result
1638
1918
 
1639
1919
 
1640
- def set_is_record_pattern(node: ast.Name, is_record_pattern: bool) -> ast.expr:
1641
- setattr(node, _RECORD_PATTERN, is_record_pattern)
1920
+ _ATTRIBUTES_PATTERN = "_typh_is_attributes_pattern"
1921
+ type AttributesPatternClass = ast.Name
1922
+
1923
+
1924
+ def set_is_attributes_pattern(node: ast.Name, is_record_pattern: bool) -> ast.expr:
1925
+ setattr(node, _ATTRIBUTES_PATTERN, is_record_pattern)
1642
1926
  return node
1643
1927
 
1644
1928
 
1645
- def is_record_pattern(node: ast.Name) -> bool:
1646
- return getattr(node, _RECORD_PATTERN, None) is not None
1929
+ def is_attributes_pattern(node: ast.Name) -> bool:
1930
+ return getattr(node, _ATTRIBUTES_PATTERN, None) is not None
1647
1931
 
1648
1932
 
1649
- def clear_is_record_pattern(node: ast.Name) -> None:
1650
- if hasattr(node, _RECORD_PATTERN):
1651
- delattr(node, _RECORD_PATTERN)
1933
+ def clear_is_attributes_pattern(node: ast.Name) -> None:
1934
+ if hasattr(node, _ATTRIBUTES_PATTERN):
1935
+ delattr(node, _ATTRIBUTES_PATTERN)
1652
1936
 
1653
1937
 
1654
- def make_record_pattern(
1938
+ def make_attributes_pattern(
1655
1939
  keywords: list[tuple[str, ast.pattern]],
1656
1940
  **kwargs: Unpack[PosAttributes],
1657
1941
  ) -> ast.MatchClass:
1942
+ debug_verbose_print(f"Creating attributes pattern with keywords: {keywords}")
1658
1943
  kwd_attrs = [k for k, _ in keywords]
1659
1944
  kwd_patterns = [
1660
1945
  (
@@ -1666,17 +1951,19 @@ def make_record_pattern(
1666
1951
  for k, p in keywords
1667
1952
  ]
1668
1953
  cls_name = ast.Name(
1669
- id="__record_pattern",
1954
+ id="__attribute_pattern",
1670
1955
  **kwargs,
1671
1956
  )
1672
- set_is_record_pattern(cls_name, True)
1673
- return ast.MatchClass(
1957
+ set_is_attributes_pattern(cls_name, True)
1958
+ result = ast.MatchClass(
1674
1959
  cls=cls_name,
1675
1960
  patterns=[],
1676
1961
  kwd_attrs=kwd_attrs,
1677
1962
  kwd_patterns=kwd_patterns,
1678
1963
  **pos_attribute_to_range(kwargs),
1679
1964
  )
1965
+ debug_verbose_print(f"Created attributes pattern: {ast.dump(result)}")
1966
+ return result
1680
1967
 
1681
1968
 
1682
1969
  def if_comp_exp(
@@ -1699,7 +1986,25 @@ def get_postfix_operator_temp_name(symbol: str) -> str:
1699
1986
  raise ValueError(f"Unknown postfix operator symbol: {symbol}")
1700
1987
 
1701
1988
 
1989
+ _IMPORTS = "_typh_imports"
1990
+
1991
+
1992
+ def get_imports(mod: ast.Module) -> dict[tuple[str, str], ast.alias]:
1993
+ imports: dict[tuple[str, str], ast.alias] | None = getattr(mod, _IMPORTS, None)
1994
+ if imports is None:
1995
+ imports = {}
1996
+ setattr(mod, _IMPORTS, imports)
1997
+ return imports
1998
+
1999
+
1702
2000
  def add_import_alias_top(mod: ast.Module, from_module: str, name: str, as_name: str):
2001
+ debug_verbose_print(f"Adding import: from {from_module} import {name} as {as_name}")
2002
+ # Check if already imported.
2003
+ imports = get_imports(mod)
2004
+ if (from_module, name) in imports and imports[
2005
+ (from_module, name)
2006
+ ].asname == as_name:
2007
+ return
1703
2008
  # Duplicate import is NOT a problem, but better to avoid it for speed.
1704
2009
  for stmt in mod.body:
1705
2010
  if isinstance(stmt, ast.ImportFrom):
@@ -1710,28 +2015,73 @@ def add_import_alias_top(mod: ast.Module, from_module: str, name: str, as_name:
1710
2015
  else:
1711
2016
  break # Only check the top sequence of import statements.
1712
2017
  # Add import at the top.
2018
+ alias = ast.alias(name=name, asname=as_name, **get_empty_pos_attributes())
2019
+ imports[(from_module, name)] = alias
1713
2020
  import_stmt = ast.ImportFrom(
1714
2021
  module=from_module,
1715
- names=[
1716
- ast.alias(
1717
- name=name,
1718
- asname=as_name,
1719
- **get_empty_pos_attributes(),
1720
- )
1721
- ],
2022
+ names=[alias],
1722
2023
  level=0,
1723
2024
  **get_empty_pos_attributes(),
1724
2025
  )
1725
2026
  mod.body.insert(0, import_stmt)
1726
2027
 
1727
2028
 
1728
- def is_pattern_irrefutable(pattern: ast.pattern) -> bool:
2029
+ _PATTERN_IS_TUPLE = "_typh_pattern_is_tuple"
2030
+
2031
+
2032
+ def set_pattern_is_tuple(pattern: ast.pattern, is_tuple: bool = True) -> ast.pattern:
2033
+ setattr(pattern, _PATTERN_IS_TUPLE, is_tuple)
2034
+ return pattern
2035
+
2036
+
2037
+ def is_pattern_tuple(pattern: ast.pattern) -> bool:
2038
+ return getattr(pattern, _PATTERN_IS_TUPLE, False)
2039
+
2040
+
2041
+ def clear_pattern_is_tuple(pattern: ast.pattern) -> None:
2042
+ if hasattr(pattern, _PATTERN_IS_TUPLE):
2043
+ delattr(pattern, _PATTERN_IS_TUPLE)
2044
+
2045
+
2046
+ def make_tuple_pattern(
2047
+ patterns: list[ast.pattern],
2048
+ **kwargs: Unpack[PosAttributes],
2049
+ ) -> ast.MatchSequence:
2050
+ result = ast.MatchSequence(
2051
+ patterns=patterns,
2052
+ **pos_attribute_to_range(kwargs),
2053
+ )
2054
+ set_pattern_is_tuple(result, True)
2055
+ return result
2056
+
2057
+
2058
+ def is_pattern_irrefutable(
2059
+ pattern: ast.pattern, assume_type_checked: bool = True
2060
+ ) -> bool:
1729
2061
  if isinstance(pattern, ast.MatchAs):
1730
- if pattern.pattern is None:
2062
+ if pattern.pattern is None: # Wildcard pattern or variable capture.
1731
2063
  return True
1732
2064
  return is_pattern_irrefutable(pattern.pattern)
1733
2065
  if isinstance(pattern, ast.MatchOr):
1734
2066
  return any(is_pattern_irrefutable(p) for p in pattern.patterns)
2067
+ if assume_type_checked: # Irrefutable if type is correct.
2068
+ if isinstance(pattern, ast.MatchClass):
2069
+ for p in pattern.patterns:
2070
+ if not is_pattern_irrefutable(p, assume_type_checked):
2071
+ return False
2072
+ for p in pattern.kwd_patterns:
2073
+ if not is_pattern_irrefutable(p, assume_type_checked):
2074
+ return False
2075
+ return True
2076
+ if isinstance(pattern, ast.MatchSequence):
2077
+ # Sequence is in general refutable due to length mismatch,
2078
+ # but if we assume tuple is type-checked, we can consider it irrefutable.
2079
+ if is_pattern_tuple(pattern):
2080
+ for p in pattern.patterns:
2081
+ if not is_pattern_irrefutable(p, assume_type_checked):
2082
+ return False
2083
+ return True
2084
+ # Other patterns are considered refutable.
1735
2085
  return False
1736
2086
 
1737
2087