Typhon-Language 0.1.2__py3-none-any.whl → 0.1.4__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 (57) hide show
  1. Typhon/Driver/configs.py +14 -0
  2. Typhon/Driver/debugging.py +148 -5
  3. Typhon/Driver/diagnostic.py +4 -3
  4. Typhon/Driver/language_server.py +25 -0
  5. Typhon/Driver/run.py +1 -1
  6. Typhon/Driver/translate.py +16 -11
  7. Typhon/Driver/utils.py +39 -1
  8. Typhon/Grammar/_typhon_parser.py +2920 -2718
  9. Typhon/Grammar/parser.py +80 -53
  10. Typhon/Grammar/parser_helper.py +68 -87
  11. Typhon/Grammar/syntax_errors.py +41 -20
  12. Typhon/Grammar/token_factory_custom.py +541 -485
  13. Typhon/Grammar/tokenizer_custom.py +52 -0
  14. Typhon/Grammar/typhon_ast.py +754 -76
  15. Typhon/Grammar/typhon_ast_error.py +438 -0
  16. Typhon/Grammar/unparse_custom.py +25 -0
  17. Typhon/LanguageServer/__init__.py +3 -0
  18. Typhon/LanguageServer/client/__init__.py +42 -0
  19. Typhon/LanguageServer/client/pyrefly.py +115 -0
  20. Typhon/LanguageServer/client/pyright.py +173 -0
  21. Typhon/LanguageServer/semantic_tokens.py +446 -0
  22. Typhon/LanguageServer/server.py +376 -0
  23. Typhon/LanguageServer/utils.py +65 -0
  24. Typhon/SourceMap/ast_match_based_map.py +199 -152
  25. Typhon/SourceMap/ast_matching.py +102 -87
  26. Typhon/SourceMap/datatype.py +275 -264
  27. Typhon/SourceMap/defined_name_retrieve.py +145 -0
  28. Typhon/Transform/comprehension_to_function.py +2 -5
  29. Typhon/Transform/const_member_to_final.py +12 -7
  30. Typhon/Transform/extended_patterns.py +139 -0
  31. Typhon/Transform/forbidden_statements.py +25 -0
  32. Typhon/Transform/if_while_let.py +122 -11
  33. Typhon/Transform/inline_statement_block_capture.py +22 -15
  34. Typhon/Transform/optional_operators_to_checked.py +14 -6
  35. Typhon/Transform/placeholder_to_function.py +0 -1
  36. Typhon/Transform/record_to_dataclass.py +22 -238
  37. Typhon/Transform/scope_check_rename.py +109 -29
  38. Typhon/Transform/transform.py +16 -12
  39. Typhon/Transform/type_abbrev_desugar.py +11 -15
  40. Typhon/Transform/type_annotation_check_expand.py +2 -2
  41. Typhon/Transform/utils/__init__.py +0 -0
  42. Typhon/Transform/utils/imports.py +83 -0
  43. Typhon/Transform/{utils.py → utils/jump_away.py} +2 -38
  44. Typhon/Transform/utils/make_class.py +135 -0
  45. Typhon/Transform/visitor.py +25 -0
  46. Typhon/Typing/pyrefly.py +145 -0
  47. Typhon/Typing/pyright.py +141 -144
  48. Typhon/Typing/result_diagnostic.py +1 -1
  49. Typhon/__main__.py +15 -1
  50. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/METADATA +13 -6
  51. typhon_language-0.1.4.dist-info/RECORD +65 -0
  52. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/WHEEL +1 -1
  53. typhon_language-0.1.4.dist-info/licenses/LICENSE +201 -0
  54. typhon_language-0.1.2.dist-info/RECORD +0 -48
  55. typhon_language-0.1.2.dist-info/licenses/LICENSE +0 -21
  56. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/entry_points.txt +0 -0
  57. {typhon_language-0.1.2.dist-info → typhon_language-0.1.4.dist-info}/top_level.txt +0 -0
@@ -13,34 +13,38 @@ from .if_while_let import if_while_let_transform
13
13
  from .comprehension_to_function import comprehension_to_function
14
14
  from .placeholder_to_function import placeholder_to_func
15
15
  from .record_to_dataclass import record_to_dataclass
16
+ from .extended_patterns import extended_protocol
16
17
 
17
18
  from ..Grammar.syntax_errors import raise_from_module_syntax_errors
19
+ from ..Grammar.unparse_custom import unparse_custom
18
20
 
19
21
 
20
22
  def transform(mod: ast.Module):
21
23
  check_forbidden_statements(mod)
22
24
  record_to_dataclass(mod)
23
- debug_verbose_print(f"After record_to_dataclass:\n{ast.unparse(mod)}\n")
25
+ debug_verbose_print(f"After record_to_dataclass:\n{unparse_custom(mod)}\n")
26
+ extended_protocol(mod)
27
+ debug_verbose_print(f"After extended_protocol:\n{unparse_custom(mod)}\n")
24
28
  inline_statement_block_capture(mod)
25
- debug_print(f"After inline_statement_block_capture:\n{ast.unparse(mod)}\n")
29
+ debug_print(f"After inline_statement_block_capture:\n{unparse_custom(mod)}\n")
26
30
  if_while_let_transform(mod)
27
- debug_verbose_print(f"After if_while_let_transform:\n{ast.unparse(mod)}\n")
31
+ debug_verbose_print(f"After if_while_let_transform:\n{unparse_custom(mod)}\n")
28
32
  insert_self_to_method(mod)
29
- debug_verbose_print(f"After insert_self_to_method:\n{ast.unparse(mod)}\n")
33
+ debug_verbose_print(f"After insert_self_to_method:\n{unparse_custom(mod)}\n")
30
34
  comprehension_to_function(mod)
31
- debug_verbose_print(f"After comprehension_to_function:\n{ast.unparse(mod)}\n")
35
+ debug_verbose_print(f"After comprehension_to_function:\n{unparse_custom(mod)}\n")
32
36
  func_literal_to_def(mod)
33
- debug_print(f"After func_literal_to_def:\n{ast.unparse(mod)}\n")
37
+ debug_print(f"After func_literal_to_def:\n{unparse_custom(mod)}\n")
34
38
  scope_check_rename(mod)
35
- debug_print(f"After scope_check_rename:\n{ast.unparse(mod)}\n")
39
+ debug_print(f"After scope_check_rename:\n{unparse_custom(mod)}\n")
36
40
  placeholder_to_func(mod)
37
- debug_print(f"After placeholder_to_func:\n{ast.unparse(mod)}\n")
41
+ debug_print(f"After placeholder_to_func:\n{unparse_custom(mod)}\n")
38
42
  optional_to_checked(mod)
39
- debug_verbose_print(f"After optional_to_checked:\n{ast.unparse(mod)}\n")
43
+ debug_verbose_print(f"After optional_to_checked:\n{unparse_custom(mod)}\n")
40
44
  type_annotation_check_expand(mod)
41
- debug_verbose_print(f"After type_annotation_check_expand:\n{ast.unparse(mod)}\n")
45
+ debug_verbose_print(f"After type_annotation_check_expand:\n{unparse_custom(mod)}\n")
42
46
  type_abbrev_desugar(mod)
43
- debug_verbose_print(f"After func_type_to_protocol:\n{ast.unparse(mod)}\n")
47
+ debug_verbose_print(f"After func_type_to_protocol:\n{unparse_custom(mod)}\n")
44
48
  const_member_to_final(mod)
45
- debug_print(f"After transform:\n{ast.unparse(mod)}\n")
49
+ debug_print(f"After transform:\n{unparse_custom(mod)}\n")
46
50
  raise_from_module_syntax_errors(mod)
@@ -12,12 +12,11 @@ from ..Grammar.typhon_ast import (
12
12
  is_typing_expression,
13
13
  is_optional_question,
14
14
  )
15
- from ..Grammar.syntax_errors import (
16
- raise_type_annotation_error,
17
- try_handle_syntax_error_or,
18
- )
19
15
  from .visitor import TyphonASTVisitor, TyphonASTTransformer
20
- from .utils import add_import_for_protocol, get_insert_point_for_class
16
+ from .utils.imports import (
17
+ get_insert_point_for_class,
18
+ get_protocol,
19
+ )
21
20
  from ..Driver.debugging import debug_print, debug_verbose_print
22
21
 
23
22
 
@@ -34,7 +33,7 @@ class _GatherArrowType(TyphonASTVisitor):
34
33
 
35
34
 
36
35
  def _protocol_for_function_type(
37
- func_type: FunctionType, arrow_type_name: str, protocol_name: str
36
+ mod: ast.Module, func_type: FunctionType, arrow_type_name: str
38
37
  ) -> ast.ClassDef:
39
38
  func_type.id = arrow_type_name
40
39
  args = get_args_of_function_type(func_type)
@@ -77,9 +76,7 @@ def _protocol_for_function_type(
77
76
  )
78
77
  protocol_def = ast.ClassDef(
79
78
  name=arrow_type_name,
80
- bases=[
81
- ast.Name(id=protocol_name, ctx=ast.Load()),
82
- ],
79
+ bases=[get_protocol(mod, ctx=ast.Load(), **get_empty_pos_attributes())],
83
80
  keywords=[],
84
81
  body=[func_def],
85
82
  decorator_list=[],
@@ -92,18 +89,18 @@ def _protocol_for_function_type(
92
89
 
93
90
 
94
91
  def _add_protocols(
95
- mod: ast.Module, func_types: list[tuple[FunctionType, str]], protocol_name: str
92
+ mod: ast.Module, func_types: list[tuple[FunctionType, str]]
96
93
  ) -> dict[FunctionType, ast.ClassDef]:
97
94
  result: dict[FunctionType, ast.ClassDef] = {}
95
+ # Ensure protocol is imported before classes.
96
+ get_protocol(mod, ctx=ast.Load(), **get_empty_pos_attributes())
98
97
  # Insert after imports.
99
98
  insert_point = get_insert_point_for_class(mod)
100
99
  for func_type, arrow_type_name in func_types:
101
100
  debug_print(
102
101
  f"Adding protocol for function type: {func_type.__dict__} as {arrow_type_name}"
103
102
  )
104
- protocol_def = _protocol_for_function_type(
105
- func_type, arrow_type_name, protocol_name
106
- )
103
+ protocol_def = _protocol_for_function_type(mod, func_type, arrow_type_name)
107
104
  result[func_type] = protocol_def
108
105
  mod.body.insert(insert_point, protocol_def)
109
106
  insert_point += 1
@@ -193,8 +190,7 @@ def type_abbrev_desugar(mod: ast.Module):
193
190
  gatherer = _GatherArrowType(mod)
194
191
  gatherer.run()
195
192
  if gatherer.func_types:
196
- protocol_name = add_import_for_protocol(mod)
197
- _add_protocols(mod, gatherer.func_types, protocol_name)
193
+ _add_protocols(mod, gatherer.func_types)
198
194
  # Run optional question first, as it is represented as Tuple nodes.
199
195
  _OptionalQuestionTransformer(mod).run()
200
196
  _TupleListTransformer(mod).run()
@@ -13,7 +13,7 @@ from ..Grammar.typhon_ast import (
13
13
  from ..Grammar.syntax_errors import (
14
14
  raise_type_annotation_error,
15
15
  handle_syntax_error,
16
- TyphonSyntaxError,
16
+ TyphonTransformSyntaxError,
17
17
  )
18
18
  from .visitor import TyphonASTTransformer, TyphonASTVisitor, flat_append
19
19
  from ..Driver.debugging import debug_print, debug_verbose_print
@@ -50,7 +50,7 @@ def _expand_target_annotation(
50
50
  result.append(new_assign)
51
51
  copy_is_let_var(orig_node, new_assign)
52
52
  return result
53
- except TyphonSyntaxError as e:
53
+ except TyphonTransformSyntaxError as e:
54
54
  handle_syntax_error(module, e)
55
55
  return []
56
56
 
File without changes
@@ -0,0 +1,83 @@
1
+ from ...Grammar.typhon_ast import (
2
+ PosAttributes,
3
+ add_import_alias_top,
4
+ is_case_irrefutable,
5
+ get_pos_attributes,
6
+ pos_attribute_to_range,
7
+ set_is_internal_name,
8
+ set_is_var,
9
+ )
10
+ from ..name_generator import (
11
+ get_protocol_name,
12
+ get_final_name,
13
+ get_dataclass_name,
14
+ get_runtime_checkable_name,
15
+ )
16
+ import ast
17
+ from contextlib import contextmanager
18
+ from dataclasses import dataclass
19
+ from typing import Protocol, Iterable, Final, Unpack
20
+
21
+
22
+ def _add_import_for_protocol(mod: ast.Module):
23
+ name = get_protocol_name()
24
+ add_import_alias_top(mod, "typing", "Protocol", name)
25
+ return name
26
+
27
+
28
+ def get_protocol(
29
+ mod: ast.Module, ctx: ast.expr_context, **kwargs: Unpack[PosAttributes]
30
+ ) -> ast.Name:
31
+ protocol_name = _add_import_for_protocol(mod)
32
+ return set_is_internal_name(ast.Name(id=protocol_name, ctx=ctx, **kwargs))
33
+
34
+
35
+ def add_import_for_final(mod: ast.Module):
36
+ name = get_final_name()
37
+ add_import_alias_top(mod, "typing", "Final", name)
38
+ return name
39
+
40
+
41
+ def get_final(ctx: ast.expr_context, **kwargs: Unpack[PosAttributes]) -> ast.Name:
42
+ final_name = get_final_name()
43
+ return set_is_internal_name(ast.Name(id=final_name, ctx=ctx, **kwargs))
44
+
45
+
46
+ def add_import_get_final(
47
+ mod: ast.Module, ctx: ast.expr_context, **kwargs: Unpack[PosAttributes]
48
+ ) -> ast.Name:
49
+ final_name = add_import_for_final(mod)
50
+ return set_is_internal_name(ast.Name(id=final_name, ctx=ctx, **kwargs))
51
+
52
+
53
+ def _add_import_for_dataclass(mod: ast.Module):
54
+ name = get_dataclass_name()
55
+ add_import_alias_top(mod, "dataclasses", "dataclass", name)
56
+ return name
57
+
58
+
59
+ def get_dataclass(
60
+ mod: ast.Module, ctx: ast.expr_context, **kwargs: Unpack[PosAttributes]
61
+ ) -> ast.Name:
62
+ dataclass_name = _add_import_for_dataclass(mod)
63
+ return set_is_internal_name(ast.Name(id=dataclass_name, ctx=ctx, **kwargs))
64
+
65
+
66
+ def _add_import_for_runtime_checkable(mod: ast.Module):
67
+ name = get_runtime_checkable_name()
68
+ add_import_alias_top(mod, "typing", "runtime_checkable", name)
69
+ return name
70
+
71
+
72
+ def get_runtime_checkable(
73
+ mod: ast.Module, ctx: ast.expr_context, **kwargs: Unpack[PosAttributes]
74
+ ) -> ast.Name:
75
+ runtime_checkable_name = _add_import_for_runtime_checkable(mod)
76
+ return set_is_internal_name(ast.Name(id=runtime_checkable_name, ctx=ctx, **kwargs))
77
+
78
+
79
+ def get_insert_point_for_class(module: ast.Module) -> int:
80
+ for index, stmt in enumerate(module.body):
81
+ if not isinstance(stmt, (ast.Import, ast.ImportFrom)):
82
+ return index
83
+ return len(module.body)
@@ -1,46 +1,9 @@
1
- from ..Grammar.typhon_ast import add_import_alias_top, is_case_irrefutable
2
- from .name_generator import (
3
- get_protocol_name,
4
- get_final_name,
5
- get_dataclass_name,
6
- get_runtime_checkable_name,
7
- )
1
+ from ...Grammar.typhon_ast import is_case_irrefutable
8
2
  import ast
9
3
  from contextlib import contextmanager
10
4
  from dataclasses import dataclass
11
5
 
12
6
 
13
- def add_import_for_protocol(mod: ast.Module):
14
- name = get_protocol_name()
15
- add_import_alias_top(mod, "typing", "Protocol", name)
16
- return name
17
-
18
-
19
- def add_import_for_final(mod: ast.Module):
20
- name = get_final_name()
21
- add_import_alias_top(mod, "typing", "Final", name)
22
- return name
23
-
24
-
25
- def add_import_for_dataclass(mod: ast.Module):
26
- name = get_dataclass_name()
27
- add_import_alias_top(mod, "dataclasses", "dataclass", name)
28
- return name
29
-
30
-
31
- def add_import_for_runtime_checkable(mod: ast.Module):
32
- name = get_runtime_checkable_name()
33
- add_import_alias_top(mod, "typing", "runtime_checkable", name)
34
- return name
35
-
36
-
37
- def get_insert_point_for_class(module: ast.Module) -> int:
38
- for index, stmt in enumerate(module.body):
39
- if not isinstance(stmt, (ast.Import, ast.ImportFrom)):
40
- return index
41
- return len(module.body)
42
-
43
-
44
7
  @dataclass
45
8
  class _Loop:
46
9
  node: ast.AST
@@ -117,6 +80,7 @@ class _JumpAwayVisitor(ast.NodeVisitor):
117
80
  return jump_away
118
81
 
119
82
  def visit_Match(self, node: ast.Match):
83
+ # Check that all cases jump away, and at least one is irrefutable.
120
84
  jump_away = True
121
85
  found_irrefutable = False
122
86
  for case in node.cases:
@@ -0,0 +1,135 @@
1
+ from ...Driver.debugging import debug_print
2
+ from ...Grammar.typhon_ast import (
3
+ get_pos_attributes,
4
+ pos_attribute_to_range,
5
+ set_is_var,
6
+ PosAttributes,
7
+ )
8
+ import ast
9
+ from typing import Protocol, Iterable, Final
10
+ from .imports import (
11
+ get_dataclass,
12
+ add_import_get_final,
13
+ get_protocol,
14
+ get_runtime_checkable,
15
+ )
16
+
17
+
18
+ class NameAndAnnotation(Protocol):
19
+ name: Final[ast.Name]
20
+ annotation: Final[ast.expr]
21
+
22
+
23
+ def _class_contents_for_fields(
24
+ fields: Iterable[NameAndAnnotation],
25
+ final_imported_name: ast.Name,
26
+ ) -> list[ast.stmt]:
27
+ result: list[ast.stmt] = []
28
+ for field in fields:
29
+ name = field.name
30
+ if field.annotation:
31
+ # Currently annotation is always Final[...] for record fields.
32
+ annotation = ast.Subscript(
33
+ value=final_imported_name,
34
+ slice=field.annotation,
35
+ ctx=ast.Load(),
36
+ **get_pos_attributes(name),
37
+ )
38
+ else:
39
+ annotation = final_imported_name
40
+ ann_assign = ast.AnnAssign(
41
+ target=ast.Name(id=name.id, ctx=ast.Store(), **get_pos_attributes(name)),
42
+ annotation=annotation,
43
+ value=None,
44
+ simple=1,
45
+ **get_pos_attributes(name),
46
+ )
47
+ # "var" because dataclass is frozen, "Final" is not required.
48
+ set_is_var(ann_assign)
49
+ result.append(ann_assign)
50
+ return result
51
+
52
+
53
+ def _type_vars_for_fields(
54
+ pos: PosAttributes, type_variables: list[str]
55
+ ) -> list[ast.type_param]:
56
+ type_params: list[ast.type_param] = []
57
+ for tv in type_variables:
58
+ type_params.append(ast.TypeVar(name=tv, **pos_attribute_to_range(pos)))
59
+ return type_params
60
+
61
+
62
+ def make_protocol_definition(
63
+ mod: ast.Module,
64
+ class_name: str,
65
+ type_variables: list[str],
66
+ fields: Iterable[NameAndAnnotation],
67
+ pos: PosAttributes,
68
+ ) -> ast.ClassDef:
69
+ protocol_imported_name = get_protocol(mod, ctx=ast.Load(), **pos)
70
+ runtime_checkable_imported_name = get_runtime_checkable(mod, ctx=ast.Load(), **pos)
71
+ final_imported_name = add_import_get_final(mod, ctx=ast.Load(), **pos)
72
+ result = ast.ClassDef(
73
+ name=class_name,
74
+ type_params=_type_vars_for_fields(pos, type_variables),
75
+ bases=[protocol_imported_name],
76
+ keywords=[],
77
+ # Currently all fields are Final.
78
+ body=_class_contents_for_fields(fields, final_imported_name),
79
+ decorator_list=[runtime_checkable_imported_name],
80
+ **pos,
81
+ )
82
+ return result
83
+
84
+
85
+ def _dataclass_decorator(
86
+ dataclass_imported_name: ast.Name, repr: bool, pos: PosAttributes
87
+ ) -> ast.expr:
88
+ return ast.Call(
89
+ func=dataclass_imported_name,
90
+ args=[],
91
+ keywords=[
92
+ ast.keyword(arg="frozen", value=ast.Constant(value=True)),
93
+ ast.keyword(arg="repr", value=ast.Constant(value=repr)),
94
+ ast.keyword(arg="unsafe_hash", value=ast.Constant(value=True)),
95
+ ast.keyword(arg="kw_only", value=ast.Constant(value=True)),
96
+ ],
97
+ )
98
+
99
+
100
+ def make_dataclass_definition(
101
+ mod: ast.Module,
102
+ class_name: str,
103
+ type_variables: list[str],
104
+ fields: Iterable[NameAndAnnotation],
105
+ pos: PosAttributes,
106
+ ) -> ast.ClassDef:
107
+ dataclass_imported_name = get_dataclass(mod, ctx=ast.Load(), **pos)
108
+ final_imported_name = add_import_get_final(mod, ctx=ast.Load(), **pos)
109
+ result = ast.ClassDef(
110
+ name=class_name,
111
+ type_params=_type_vars_for_fields(pos, type_variables),
112
+ bases=[],
113
+ keywords=[],
114
+ body=_class_contents_for_fields(fields, final_imported_name),
115
+ decorator_list=[
116
+ _dataclass_decorator(dataclass_imported_name, repr=False, pos=pos)
117
+ ],
118
+ **pos,
119
+ )
120
+ return result
121
+
122
+
123
+ def make_dataclass_protocol_definition(
124
+ mod: ast.Module,
125
+ class_name: str,
126
+ type_variables: list[str],
127
+ fields: Iterable[NameAndAnnotation],
128
+ pos: PosAttributes,
129
+ ) -> ast.ClassDef:
130
+ dataclass_imported_name = get_dataclass(mod, ctx=ast.Load(), **pos)
131
+ result = make_protocol_definition(mod, class_name, type_variables, fields, pos)
132
+ result.decorator_list.append(
133
+ _dataclass_decorator(dataclass_imported_name, repr=True, pos=pos)
134
+ )
135
+ return result
@@ -276,6 +276,31 @@ class _ErrorHandlingMixin:
276
276
  )
277
277
 
278
278
 
279
+ class TyphonASTRawVisitor(
280
+ ast.NodeVisitor,
281
+ _TyphonExtendedNodeTransformerMixin,
282
+ ):
283
+ def visit_PossiblyAnnotatedNode(self, node: ast.AST):
284
+ self._visit_Possibly_Annotated_Node(node, self, False)
285
+
286
+ @override
287
+ def visit(self, node: ast.AST):
288
+ return self._visit(node, self, super().visit)
289
+
290
+ @override
291
+ def generic_visit(self, node: ast.AST):
292
+ # visit() calls _visit() but generic_visit() does not. So we need to dispatch here too.
293
+ if isinstance(node, ast.Name):
294
+ if is_function_literal(node):
295
+ return self._visit_FunctionLiteral(node, self, False)
296
+ elif is_function_type(node):
297
+ return self._visit_FunctionType(node, self, False)
298
+ elif is_control_comprehension(node):
299
+ return self._visit_ControlComprehension(node, self, False)
300
+ self.visit_PossiblyAnnotatedNode(node)
301
+ return super().generic_visit(node)
302
+
303
+
279
304
  class TyphonASTVisitor(
280
305
  ast.NodeVisitor,
281
306
  _ScopeManagerMixin,
@@ -0,0 +1,145 @@
1
+ import sys
2
+ from pathlib import Path
3
+ import subprocess
4
+ import toml
5
+ from ..Driver.debugging import debug_print
6
+ from typing import Literal, cast, Any
7
+ import json
8
+ from .result_diagnostic import Severity, Diagnostic, TypeCheckResult
9
+ from ..SourceMap.datatype import Range, Pos
10
+
11
+ # https://docs.basedpyrefly.com/dev/configuration/config-files/
12
+ type TypeCheckLevel = Literal[
13
+ "off",
14
+ "basic",
15
+ "strict",
16
+ "all",
17
+ "translate", # Default for Typhon translation
18
+ "script", # Default for Typhon scripts
19
+ ]
20
+ translate_config = {
21
+ "typeCheckingMode": "strict",
22
+ "reportUnusedExpression": "warning",
23
+ "reportUnusedClass": "warning",
24
+ "reportUnusedImport": "warning",
25
+ "reportUnusedFunction": "warning",
26
+ "reportUnusedVariable": "warning",
27
+ "reportUnusedCallResult": "warning",
28
+ "reportUnnecessaryIsInstance": "warning",
29
+ "reportUnnecessaryCast": "warning",
30
+ # "reportUnnecessaryComparison": "warning", # To check type of patterns
31
+ "reportUnnecessaryContains": "warning",
32
+ "reportDeprecated": "warning",
33
+ }
34
+ script_config = {
35
+ "typeCheckingMode": "strict",
36
+ "reportUnusedExpression": "none",
37
+ "reportUnusedClass": "none",
38
+ "reportUnusedImport": "none",
39
+ "reportUnusedFunction": "none",
40
+ "reportUnusedVariable": "none",
41
+ "reportUnusedCallResult": "none",
42
+ "reportUnnecessaryIsInstance": "none",
43
+ "reportUnnecessaryCast": "none",
44
+ # "reportUnnecessaryComparison": "none", # To check type of patterns
45
+ "reportUnnecessaryContains": "none",
46
+ "reportDeprecated": "none",
47
+ }
48
+
49
+
50
+ def write_pyrefly_config(
51
+ output_dir: Path, level: TypeCheckLevel = "translate", overwrite: bool = False
52
+ ) -> str:
53
+ config = {
54
+ "include": ["**/*.py"],
55
+ "exclude": ["**/__pycache__"],
56
+ "typeCheckingMode": "off",
57
+ }
58
+ if level == "translate":
59
+ config.update(translate_config)
60
+ elif level == "script":
61
+ config.update(script_config)
62
+ elif level == "basic":
63
+ config["typeCheckingMode"] = "basic"
64
+ elif level == "strict":
65
+ config["typeCheckingMode"] = "strict"
66
+ elif level == "all":
67
+ config["typeCheckingMode"] = "all"
68
+ else:
69
+ raise ValueError(f"Unknown type check level: {level}")
70
+ config_path = output_dir / "pyreflyconfig.json"
71
+ if not overwrite and config_path.exists():
72
+ debug_print(
73
+ f"Config file already exists at {config_path}. Use overwrite=True to replace."
74
+ )
75
+ return str(config_path)
76
+ toml_str = toml.dumps(config)
77
+ with open(config_path, "w") as f:
78
+ f.write(toml_str)
79
+ debug_print(f"Generated pyrefly config at {config_path}")
80
+ return str(config_path)
81
+
82
+
83
+ def _try_read_attr[T](d: Any, attr: str, default: T) -> T:
84
+ result = d.get(attr, default)
85
+ return cast(T, result)
86
+
87
+
88
+ def _parse_pos_range(pos: Any) -> Range:
89
+ start = _try_read_attr(pos, "start", {})
90
+ end = _try_read_attr(pos, "end", {})
91
+ return Range(
92
+ start=Pos(line=start.get("line", 0), column=start.get("character", 0)),
93
+ end=Pos(line=end.get("line", 0), column=end.get("character", 0)),
94
+ )
95
+
96
+
97
+ def _parse_diagnostic(diag: Any) -> Diagnostic:
98
+ return Diagnostic(
99
+ file_path=_try_read_attr(diag, "file", ""),
100
+ severity=Severity[_try_read_attr(diag, "severity", "INFO").upper()],
101
+ message=_try_read_attr(diag, "message", ""),
102
+ pos=_parse_pos_range(diag["range"]),
103
+ rule=_try_read_attr(diag, "rule", ""),
104
+ )
105
+
106
+
107
+ def parse_json_output(output: str, returncode: int, stderr: str) -> TypeCheckResult:
108
+ data = json.loads(output)
109
+ diagnostics = [
110
+ _parse_diagnostic(diag) for diag in data.get("generalDiagnostics", [])
111
+ ]
112
+ summary = _try_read_attr(data, "summary", {})
113
+ return TypeCheckResult(
114
+ returncode=returncode,
115
+ stderr=stderr,
116
+ files_analyzed=_try_read_attr(summary, "filesAnalyzed", 0),
117
+ num_errors=_try_read_attr(summary, "errorCount", 0),
118
+ num_warnings=_try_read_attr(summary, "warningCount", 0),
119
+ num_info=_try_read_attr(summary, "informationCount", 0),
120
+ time_in_sec=_try_read_attr(summary, "timeInSec", 0.0),
121
+ diagnostics=diagnostics,
122
+ )
123
+
124
+
125
+ def run_pyrefly(
126
+ py_file_or_dir: Path, level: TypeCheckLevel = "translate"
127
+ ) -> TypeCheckResult:
128
+ output = subprocess.run(
129
+ [
130
+ sys.executable,
131
+ "-m",
132
+ "pyrefly",
133
+ "check",
134
+ "--output-format",
135
+ "json",
136
+ str(py_file_or_dir),
137
+ ],
138
+ stdout=subprocess.PIPE,
139
+ stderr=subprocess.PIPE,
140
+ shell=False,
141
+ )
142
+ result = parse_json_output(
143
+ output.stdout.decode(), output.returncode, output.stderr.decode()
144
+ )
145
+ return result