@zzzen/pyright-internal 1.2.0-dev.20261018 → 1.2.0-dev.20261108

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 (238) hide show
  1. package/dist/analyzer/binder.js +29 -12
  2. package/dist/analyzer/binder.js.map +1 -1
  3. package/dist/analyzer/checker.js +4 -0
  4. package/dist/analyzer/checker.js.map +1 -1
  5. package/dist/analyzer/codeFlowEngine.js +3 -3
  6. package/dist/analyzer/codeFlowEngine.js.map +1 -1
  7. package/dist/analyzer/constructorTransform.js +12 -0
  8. package/dist/analyzer/constructorTransform.js.map +1 -1
  9. package/dist/analyzer/constructors.d.ts +1 -1
  10. package/dist/analyzer/constructors.js +1 -1
  11. package/dist/analyzer/constructors.js.map +1 -1
  12. package/dist/analyzer/dataClasses.js +14 -5
  13. package/dist/analyzer/dataClasses.js.map +1 -1
  14. package/dist/analyzer/decorators.d.ts +2 -2
  15. package/dist/analyzer/decorators.js +22 -2
  16. package/dist/analyzer/decorators.js.map +1 -1
  17. package/dist/analyzer/functionTransform.d.ts +5 -0
  18. package/dist/analyzer/functionTransform.js +6 -3
  19. package/dist/analyzer/functionTransform.js.map +1 -1
  20. package/dist/analyzer/missingBindingMetadata.d.ts +6 -0
  21. package/dist/analyzer/missingBindingMetadata.js +22 -0
  22. package/dist/analyzer/missingBindingMetadata.js.map +1 -0
  23. package/dist/analyzer/namedTuples.d.ts +5 -1
  24. package/dist/analyzer/namedTuples.js +64 -35
  25. package/dist/analyzer/namedTuples.js.map +1 -1
  26. package/dist/analyzer/operations.js +45 -1
  27. package/dist/analyzer/operations.js.map +1 -1
  28. package/dist/analyzer/parseTreeUtils.d.ts +2 -0
  29. package/dist/analyzer/parseTreeUtils.js +43 -0
  30. package/dist/analyzer/parseTreeUtils.js.map +1 -1
  31. package/dist/analyzer/patternMatching.d.ts +5 -1
  32. package/dist/analyzer/patternMatching.js +6 -1
  33. package/dist/analyzer/patternMatching.js.map +1 -1
  34. package/dist/analyzer/program.d.ts +3 -0
  35. package/dist/analyzer/program.js +71 -1
  36. package/dist/analyzer/program.js.map +1 -1
  37. package/dist/analyzer/sentinel.js +7 -1
  38. package/dist/analyzer/sentinel.js.map +1 -1
  39. package/dist/analyzer/sourceFile.js +13 -6
  40. package/dist/analyzer/sourceFile.js.map +1 -1
  41. package/dist/analyzer/tupleComparison.d.ts +6 -0
  42. package/dist/analyzer/tupleComparison.js +731 -0
  43. package/dist/analyzer/tupleComparison.js.map +1 -0
  44. package/dist/analyzer/tuples.d.ts +1 -1
  45. package/dist/analyzer/tuples.js +35 -4
  46. package/dist/analyzer/tuples.js.map +1 -1
  47. package/dist/analyzer/typeCacheUtils.d.ts +1 -0
  48. package/dist/analyzer/typeCacheUtils.js +10 -0
  49. package/dist/analyzer/typeCacheUtils.js.map +1 -1
  50. package/dist/analyzer/typeEvaluator/boundMethodSpecialization.d.ts +15 -0
  51. package/dist/analyzer/typeEvaluator/boundMethodSpecialization.js +98 -0
  52. package/dist/analyzer/typeEvaluator/boundMethodSpecialization.js.map +1 -0
  53. package/dist/analyzer/typeEvaluator/classConstruction.d.ts +25 -0
  54. package/dist/analyzer/typeEvaluator/classConstruction.js +251 -0
  55. package/dist/analyzer/typeEvaluator/classConstruction.js.map +1 -0
  56. package/dist/analyzer/typeEvaluator/collectionInference.d.ts +41 -0
  57. package/dist/analyzer/typeEvaluator/collectionInference.js +695 -0
  58. package/dist/analyzer/typeEvaluator/collectionInference.js.map +1 -0
  59. package/dist/analyzer/typeEvaluator/diagnostics.d.ts +33 -0
  60. package/dist/analyzer/typeEvaluator/diagnostics.js +198 -0
  61. package/dist/analyzer/typeEvaluator/diagnostics.js.map +1 -0
  62. package/dist/analyzer/typeEvaluator/evaluatorCore.d.ts +80 -0
  63. package/dist/analyzer/typeEvaluator/evaluatorCore.js +1126 -0
  64. package/dist/analyzer/typeEvaluator/evaluatorCore.js.map +1 -0
  65. package/dist/analyzer/typeEvaluator/evaluatorNarrowing.d.ts +17 -0
  66. package/dist/analyzer/typeEvaluator/evaluatorNarrowing.js +370 -0
  67. package/dist/analyzer/typeEvaluator/evaluatorNarrowing.js.map +1 -0
  68. package/dist/analyzer/typeEvaluator/expressionEvaluation.d.ts +81 -0
  69. package/dist/analyzer/typeEvaluator/expressionEvaluation.js +1802 -0
  70. package/dist/analyzer/typeEvaluator/expressionEvaluation.js.map +1 -0
  71. package/dist/analyzer/typeEvaluator/flowAnalysis.d.ts +33 -0
  72. package/dist/analyzer/typeEvaluator/flowAnalysis.js +217 -0
  73. package/dist/analyzer/typeEvaluator/flowAnalysis.js.map +1 -0
  74. package/dist/analyzer/typeEvaluator/internals.d.ts +137 -0
  75. package/dist/analyzer/typeEvaluator/internals.js +10 -0
  76. package/dist/analyzer/typeEvaluator/internals.js.map +1 -0
  77. package/dist/analyzer/typeEvaluator/literalTypes.d.ts +3 -0
  78. package/dist/analyzer/typeEvaluator/literalTypes.js +52 -0
  79. package/dist/analyzer/typeEvaluator/literalTypes.js.map +1 -0
  80. package/dist/analyzer/typeEvaluator/memberResolution.d.ts +79 -0
  81. package/dist/analyzer/typeEvaluator/memberResolution.js +1007 -0
  82. package/dist/analyzer/typeEvaluator/memberResolution.js.map +1 -0
  83. package/dist/analyzer/typeEvaluator/overrideValidation.d.ts +15 -0
  84. package/dist/analyzer/typeEvaluator/overrideValidation.js +422 -0
  85. package/dist/analyzer/typeEvaluator/overrideValidation.js.map +1 -0
  86. package/dist/analyzer/typeEvaluator/returnTypeInference.d.ts +33 -0
  87. package/dist/analyzer/typeEvaluator/returnTypeInference.js +226 -0
  88. package/dist/analyzer/typeEvaluator/returnTypeInference.js.map +1 -0
  89. package/dist/analyzer/typeEvaluator/specialFormCreation.d.ts +39 -0
  90. package/dist/analyzer/typeEvaluator/specialFormCreation.js +1728 -0
  91. package/dist/analyzer/typeEvaluator/specialFormCreation.js.map +1 -0
  92. package/dist/analyzer/typeEvaluator/tupleUnpacking.d.ts +2 -0
  93. package/dist/analyzer/typeEvaluator/tupleUnpacking.js +36 -0
  94. package/dist/analyzer/typeEvaluator/tupleUnpacking.js.map +1 -0
  95. package/dist/analyzer/typeEvaluator/typeCompatibility.d.ts +36 -0
  96. package/dist/analyzer/typeEvaluator/typeCompatibility.js +1320 -0
  97. package/dist/analyzer/typeEvaluator/typeCompatibility.js.map +1 -0
  98. package/dist/analyzer/typeEvaluator/typeGuardNarrowing.d.ts +186 -0
  99. package/dist/analyzer/typeEvaluator/typeGuardNarrowing.js +1683 -0
  100. package/dist/analyzer/typeEvaluator/typeGuardNarrowing.js.map +1 -0
  101. package/dist/analyzer/typeEvaluator/typeVarHandling.d.ts +48 -0
  102. package/dist/analyzer/typeEvaluator/typeVarHandling.js +756 -0
  103. package/dist/analyzer/typeEvaluator/typeVarHandling.js.map +1 -0
  104. package/dist/analyzer/typeEvaluator.d.ts +3 -31
  105. package/dist/analyzer/typeEvaluator.js +5140 -13656
  106. package/dist/analyzer/typeEvaluator.js.map +1 -1
  107. package/dist/analyzer/typeEvaluatorTypes.d.ts +1 -1
  108. package/dist/analyzer/typeEvaluatorTypes.js +2 -3
  109. package/dist/analyzer/typeEvaluatorTypes.js.map +1 -1
  110. package/dist/analyzer/typeGuards.d.ts +3 -2
  111. package/dist/analyzer/typeGuards.js +236 -1375
  112. package/dist/analyzer/typeGuards.js.map +1 -1
  113. package/dist/analyzer/typeUtils.js +9 -2
  114. package/dist/analyzer/typeUtils.js.map +1 -1
  115. package/dist/analyzer/typedDicts.js +111 -0
  116. package/dist/analyzer/typedDicts.js.map +1 -1
  117. package/dist/analyzer/types.d.ts +14 -0
  118. package/dist/analyzer/types.js +215 -10
  119. package/dist/analyzer/types.js.map +1 -1
  120. package/dist/browser/browserConnection.d.ts +12 -0
  121. package/dist/browser/browserConnection.js +23 -0
  122. package/dist/browser/browserConnection.js.map +1 -0
  123. package/dist/browser/browserFileSystem.d.ts +73 -0
  124. package/dist/browser/browserFileSystem.js +578 -0
  125. package/dist/browser/browserFileSystem.js.map +1 -0
  126. package/dist/browser/browserHost.d.ts +4 -0
  127. package/dist/browser/browserHost.js +10 -0
  128. package/dist/browser/browserHost.js.map +1 -0
  129. package/dist/browser/browserProcess.d.ts +1 -0
  130. package/dist/browser/browserProcess.js +17 -0
  131. package/dist/browser/browserProcess.js.map +1 -0
  132. package/dist/browser/browserServer.d.ts +8 -0
  133. package/dist/browser/browserServer.js +32 -0
  134. package/dist/browser/browserServer.js.map +1 -0
  135. package/dist/browser/cancellation/common.d.ts +10 -0
  136. package/dist/browser/cancellation/common.js +114 -0
  137. package/dist/browser/cancellation/common.js.map +1 -0
  138. package/dist/browser/cancellation/objectURLReceiverStrategy.d.ts +5 -0
  139. package/dist/browser/cancellation/objectURLReceiverStrategy.js +37 -0
  140. package/dist/browser/cancellation/objectURLReceiverStrategy.js.map +1 -0
  141. package/dist/browser/cancellation/objectURLSenderStrategy.d.ts +7 -0
  142. package/dist/browser/cancellation/objectURLSenderStrategy.js +32 -0
  143. package/dist/browser/cancellation/objectURLSenderStrategy.js.map +1 -0
  144. package/dist/browser/worker.d.ts +1 -0
  145. package/dist/browser/worker.js +33 -0
  146. package/dist/browser/worker.js.map +1 -0
  147. package/dist/common/processUtils.d.ts +6 -0
  148. package/dist/common/processUtils.js +56 -1
  149. package/dist/common/processUtils.js.map +1 -1
  150. package/dist/common/textEditTracker.js +1 -1
  151. package/dist/common/textEditTracker.js.map +1 -1
  152. package/dist/common/windowsProcessTree.d.ts +2 -0
  153. package/dist/common/windowsProcessTree.js +23 -0
  154. package/dist/common/windowsProcessTree.js.map +1 -1
  155. package/dist/common/windowsProcessTree.json +23 -3
  156. package/dist/languageServerBase.d.ts +3 -0
  157. package/dist/languageServerBase.js +44 -12
  158. package/dist/languageServerBase.js.map +1 -1
  159. package/dist/languageService/callHierarchyProvider.js +6 -6
  160. package/dist/languageService/callHierarchyProvider.js.map +1 -1
  161. package/dist/languageService/referencesProvider.js +3 -3
  162. package/dist/languageService/referencesProvider.js.map +1 -1
  163. package/dist/languageService/renameProvider.js +1 -1
  164. package/dist/languageService/renameProvider.js.map +1 -1
  165. package/dist/languageService/symbolIndexer.js +2 -2
  166. package/dist/languageService/symbolIndexer.js.map +1 -1
  167. package/dist/localization/localize.d.ts +2 -0
  168. package/dist/localization/localize.js +2 -0
  169. package/dist/localization/localize.js.map +1 -1
  170. package/dist/localization/package.nls.en-us.json +8 -0
  171. package/dist/parser/parseTreeTokenReuse.d.ts +34 -0
  172. package/dist/parser/parseTreeTokenReuse.js +897 -0
  173. package/dist/parser/parseTreeTokenReuse.js.map +1 -0
  174. package/dist/parser/tokenizer.d.ts +10 -1
  175. package/dist/parser/tokenizer.js +184 -130
  176. package/dist/parser/tokenizer.js.map +1 -1
  177. package/dist/pyrightServer.d.ts +34 -0
  178. package/dist/pyrightServer.js +239 -0
  179. package/dist/pyrightServer.js.map +1 -0
  180. package/dist/server.d.ts +3 -23
  181. package/dist/server.js +12 -232
  182. package/dist/server.js.map +1 -1
  183. package/dist/tests/benchmarks/typeInferenceComparison.d.ts +1 -0
  184. package/dist/tests/benchmarks/typeInferenceComparison.js +1235 -0
  185. package/dist/tests/benchmarks/typeInferenceComparison.js.map +1 -0
  186. package/dist/tests/benchmarks/typeInferencePyrightEntry.d.ts +1 -0
  187. package/dist/tests/benchmarks/typeInferencePyrightEntry.js +53 -0
  188. package/dist/tests/benchmarks/typeInferencePyrightEntry.js.map +1 -0
  189. package/dist/tests/browserCancellation.test.d.ts +1 -0
  190. package/dist/tests/browserCancellation.test.js +63 -0
  191. package/dist/tests/browserCancellation.test.js.map +1 -0
  192. package/dist/tests/browserServer.test.d.ts +1 -0
  193. package/dist/tests/browserServer.test.js +177 -0
  194. package/dist/tests/browserServer.test.js.map +1 -0
  195. package/dist/tests/checker.test.js +8 -0
  196. package/dist/tests/checker.test.js.map +1 -1
  197. package/dist/tests/constraintSolver.test.js +45 -0
  198. package/dist/tests/constraintSolver.test.js.map +1 -1
  199. package/dist/tests/fourSlashParser.test.js +35 -0
  200. package/dist/tests/fourSlashParser.test.js.map +1 -1
  201. package/dist/tests/harness/fourslash/fourSlashParser.js +14 -4
  202. package/dist/tests/harness/fourslash/fourSlashParser.js.map +1 -1
  203. package/dist/tests/languageServer.test.js +15 -0
  204. package/dist/tests/languageServer.test.js.map +1 -1
  205. package/dist/tests/lsp/languageServer.js +6 -6
  206. package/dist/tests/lsp/languageServer.js.map +1 -1
  207. package/dist/tests/mypyPrimerAnalysis.test.js +148 -1
  208. package/dist/tests/mypyPrimerAnalysis.test.js.map +1 -1
  209. package/dist/tests/typeCacheUtils.test.js +46 -0
  210. package/dist/tests/typeCacheUtils.test.js.map +1 -1
  211. package/dist/tests/typeEvaluator1.test.js +78 -1
  212. package/dist/tests/typeEvaluator1.test.js.map +1 -1
  213. package/dist/tests/typeEvaluator2.test.js +54 -1
  214. package/dist/tests/typeEvaluator2.test.js.map +1 -1
  215. package/dist/tests/typeEvaluator3.test.js +4 -0
  216. package/dist/tests/typeEvaluator3.test.js.map +1 -1
  217. package/dist/tests/typeEvaluator4.test.js +42 -3
  218. package/dist/tests/typeEvaluator4.test.js.map +1 -1
  219. package/dist/tests/typeEvaluator6.test.js +35 -0
  220. package/dist/tests/typeEvaluator6.test.js.map +1 -1
  221. package/dist/tests/typeEvaluator7.test.js +414 -0
  222. package/dist/tests/typeEvaluator7.test.js.map +1 -1
  223. package/dist/tests/typeEvaluator8.test.js +1291 -1
  224. package/dist/tests/typeEvaluator8.test.js.map +1 -1
  225. package/dist/tests/typeUtils.test.js +28 -0
  226. package/dist/tests/typeUtils.test.js.map +1 -1
  227. package/dist/tests/types.test.js +55 -0
  228. package/dist/tests/types.test.js.map +1 -1
  229. package/dist/typeServer/programWrapper.js +8 -2
  230. package/dist/typeServer/programWrapper.js.map +1 -1
  231. package/dist/typeServer/server.d.ts +12 -2
  232. package/dist/typeServer/server.js +97 -0
  233. package/dist/typeServer/server.js.map +1 -1
  234. package/dist/typeServer/stubGenerator.js +136 -3
  235. package/dist/typeServer/stubGenerator.js.map +1 -1
  236. package/dist/typeServer/typeServerConversionUtils.js +13 -0
  237. package/dist/typeServer/typeServerConversionUtils.js.map +1 -1
  238. package/package.json +3 -1
@@ -51,6 +51,7 @@ const diagnostic_1 = require("../common/diagnostic");
51
51
  const diagnosticRules_1 = require("../common/diagnosticRules");
52
52
  const pythonVersion_1 = require("../common/pythonVersion");
53
53
  const uri_1 = require("../common/uri/uri");
54
+ const localize_1 = require("../localization/localize");
54
55
  const testState_1 = require("./harness/fourslash/testState");
55
56
  const TestUtils = __importStar(require("./testUtils"));
56
57
  test('GenericType1', () => {
@@ -695,6 +696,25 @@ test('Protocol54', () => {
695
696
  const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocol54.py']);
696
697
  TestUtils.validateResults(analysisResults, 0);
697
698
  });
699
+ test('Protocol55', () => {
700
+ const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocol55.py']);
701
+ TestUtils.validateResults(analysisResults, 5);
702
+ const decoratorErrors = analysisResults[0].errors.filter((diag) => diag.getRule() === diagnosticRules_1.DiagnosticRule.reportGeneralTypeIssues);
703
+ assert.deepStrictEqual(decoratorErrors.map((diag) => ({ message: diag.message, range: diag.range })), [13, 24, 45, 156].map((line) => ({
704
+ message: localize_1.LocMessage.runtimeCheckableNotProtocol(),
705
+ range: { start: { line, character: 1 }, end: { line, character: 18 } },
706
+ })));
707
+ const protocolErrors = analysisResults[0].errors.filter((diag) => diag.getRule() === diagnosticRules_1.DiagnosticRule.reportArgumentType);
708
+ assert.deepStrictEqual(protocolErrors.map((diag) => diag.range.start.line), [97]);
709
+ });
710
+ test('Protocol55UntypedDecorators', () => {
711
+ const configOptions = new configOptions_1.ConfigOptions(uri_1.Uri.empty());
712
+ configOptions.diagnosticRuleSet.reportUntypedClassDecorator = 'error';
713
+ const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocol55.py'], configOptions);
714
+ TestUtils.validateResults(analysisResults, 12);
715
+ const untypedDecoratorErrors = analysisResults[0].errors.filter((diag) => diag.getRule() === diagnosticRules_1.DiagnosticRule.reportUntypedClassDecorator);
716
+ assert.deepStrictEqual(untypedDecoratorErrors.map((diag) => diag.range.start.line), [127, 135, 142, 149, 158, 165, 190]);
717
+ });
698
718
  test('ProtocolExplicit1', () => {
699
719
  const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocolExplicit1.py']);
700
720
  TestUtils.validateResults(analysisResults, 5);
@@ -703,6 +723,400 @@ test('ProtocolExplicit3', () => {
703
723
  const analysisResults = TestUtils.typeAnalyzeSampleFiles(['protocolExplicit3.py']);
704
724
  TestUtils.validateResults(analysisResults, 3);
705
725
  });
726
+ test.each(['cache', 'recursiveArgument', 'recursiveReturn'].flatMap((path) => ['standard', 'positionalOnly', 'keywordOnly'].flatMap((kind) => [
727
+ ['optional', 'required', false],
728
+ ['required', 'optional', false],
729
+ ['optional', 'optional', true],
730
+ ].map(([first, second, compatible]) => ({ path, kind, first, second, compatible })))))('TypedDictCallableIdentity $path $kind $first/$second', ({ path, kind, first, second, compatible }) => {
731
+ const suffix = kind === 'positionalOnly' ? ', /' : '';
732
+ const prefix = kind === 'keywordOnly' ? '*, ' : '';
733
+ const recursive = path !== 'cache';
734
+ const code = `
735
+ // @filename: test.py
736
+ //// from typing import Callable, ReadOnly, TypedDict
737
+ ////
738
+ //// def optional(${prefix}x: int = 0${suffix}) -> None:
739
+ //// pass
740
+ ////
741
+ //// def required(${prefix}x: int${suffix}) -> None:
742
+ //// pass
743
+ ////
744
+ ${recursive
745
+ ? `//// class A[T](TypedDict, total=False):
746
+ //// next: ReadOnly["A[T]"]
747
+ //// value: ReadOnly[T]
748
+ ////
749
+ //// class B[T, U](TypedDict, total=False):
750
+ //// next: ReadOnly["B[U, T]"]
751
+ //// value: ReadOnly[T]
752
+ //// other: ReadOnly[U]
753
+ ////
754
+ //// def source[T, U](t: T, u: U) -> B[T, U]:
755
+ //// child: B[U, T] = {"value": u, "other": t}
756
+ //// return {"value": t, "other": u, "next": child}
757
+ ////
758
+ //// def consume(result: A[Callable[[], None]]) -> None:
759
+ //// if "next" in result and "value" in result["next"]:
760
+ //// result["next"]["value"]()
761
+ ////
762
+ ${path === 'recursiveReturn'
763
+ ? `//// def bad() -> A[Callable[[], None]]:
764
+ //// /*error*/return source(${first}, ${second})
765
+ ////
766
+ //// consume(bad())`
767
+ : `//// /*error*/consume(source(${first}, ${second}))`}`
768
+ : `//// class A(TypedDict):
769
+ //// value: ReadOnly[Callable[[], None]]
770
+ ////
771
+ //// class B[T](TypedDict):
772
+ //// value: ReadOnly[T]
773
+ ////
774
+ //// class Source[T, U](TypedDict):
775
+ //// first: ReadOnly[B[T]]
776
+ //// second: ReadOnly[B[U]]
777
+ ////
778
+ //// class Dest(TypedDict):
779
+ //// first: ReadOnly[A]
780
+ //// second: ReadOnly[A]
781
+ ////
782
+ //// def source[T, U](t: T, u: U) -> Source[T, U]:
783
+ //// return {"first": {"value": t}, "second": {"value": u}}
784
+ ////
785
+ //// def bad() -> Dest:
786
+ //// /*error*/return source(${first}, ${second})
787
+ ////
788
+ //// bad()["second"]["value"]()`}
789
+ `;
790
+ const state = (0, testState_1.parseAndGetTestState)(code).state;
791
+ state.configOptions.defaultPythonVersion = pythonVersion_1.pythonVersion3_13;
792
+ const file = state.testData.files[0];
793
+ const results = TestUtils.getAnalysisResults(state.program, [file.fileUri], state.configOptions);
794
+ TestUtils.validateResults(results, compatible ? 0 : 1);
795
+ if (!compatible) {
796
+ const diagnostic = results[0].errors[0];
797
+ assert.strictEqual(diagnostic.getRule(), path === 'recursiveArgument' ? diagnosticRules_1.DiagnosticRule.reportArgumentType : diagnosticRules_1.DiagnosticRule.reportReturnType);
798
+ const marker = state.getMarkerByName('error');
799
+ const line = file.content.slice(0, marker.position).split('\n').length - 1;
800
+ assert.strictEqual(diagnostic.range.start.line, line);
801
+ expect(diagnostic.message).toContain(kind === 'positionalOnly' ? '(x: int' : '"x"');
802
+ if (path === 'cache') {
803
+ expect(diagnostic.message).toContain(first === 'required' ? '"first"' : '"second"');
804
+ }
805
+ }
806
+ });
807
+ test.each([
808
+ ['optional', 'required', false],
809
+ ['required', 'optional', false],
810
+ ['optional', 'optional', true],
811
+ ])('TypedDictCallableConstructor %s/%s', (first, second, compatible) => {
812
+ const code = `
813
+ // @filename: test.py
814
+ //// from typing import Callable, ReadOnly, TypedDict, assert_type
815
+ ////
816
+ //// class A[T](TypedDict):
817
+ //// value: ReadOnly[T]
818
+ ////
819
+ //// class B[T](TypedDict):
820
+ //// value: ReadOnly[T]
821
+ ////
822
+ //// class Dest(TypedDict):
823
+ //// first: ReadOnly[A[Callable[[], int]]]
824
+ //// second: ReadOnly[A[Callable[[], int]]]
825
+ ////
826
+ //// class Source[T, U](TypedDict):
827
+ //// first: ReadOnly[B[T]]
828
+ //// second: ReadOnly[B[U]]
829
+ ////
830
+ //// def optional(x: int = 0) -> int:
831
+ //// return x
832
+ ////
833
+ //// def required(x: int) -> int:
834
+ //// return x
835
+ ////
836
+ //// def expose() -> Dest:
837
+ //// source = Source(first=B(value=${first}), second=B(value=${second}))
838
+ //// /*error*/return source
839
+ ////
840
+ //// def consumer() -> int:
841
+ //// assert_type(expose()["second"]["value"], Callable[[], int])
842
+ //// return expose()["second"]["value"]()
843
+ `;
844
+ const state = (0, testState_1.parseAndGetTestState)(code).state;
845
+ state.configOptions.defaultPythonVersion = pythonVersion_1.pythonVersion3_13;
846
+ const file = state.testData.files[0];
847
+ const results = TestUtils.getAnalysisResults(state.program, [file.fileUri], state.configOptions);
848
+ TestUtils.validateResults(results, compatible ? 0 : 1);
849
+ if (!compatible) {
850
+ const diagnostic = results[0].errors[0];
851
+ assert.strictEqual(diagnostic.getRule(), diagnosticRules_1.DiagnosticRule.reportReturnType);
852
+ const marker = state.getMarkerByName('error');
853
+ const line = file.content.slice(0, marker.position).split('\n').length - 1;
854
+ assert.strictEqual(diagnostic.range.start.line, line);
855
+ expect(diagnostic.message).toContain(first === 'required' ? '"first"' : '"second"');
856
+ }
857
+ });
858
+ test('TypedDictNestedSiblingDiagnostics', () => {
859
+ const classes = ['Int', 'Str']
860
+ .map((prefix) => `
861
+ //// class ${prefix}Leaf(TypedDict):
862
+ //// value: ReadOnly[${prefix === 'Int' ? 'int' : 'str'}]
863
+ ////
864
+ //// class ${prefix}Pair(TypedDict):
865
+ //// first: ReadOnly[${prefix}Leaf]
866
+ //// second: ReadOnly[${prefix}Leaf]
867
+ ////
868
+ //// class ${prefix}Root(TypedDict):
869
+ //// inside: ReadOnly[${prefix}Pair]
870
+ ////
871
+ //// class ${prefix}Outer(TypedDict):
872
+ //// outer: ReadOnly[${prefix}Root]
873
+ ////`)
874
+ .join('\n');
875
+ const code = `
876
+ // @filename: test.py
877
+ //// from typing import TypedDict
878
+ //// from typing_extensions import ReadOnly
879
+ ////
880
+ ${classes}
881
+ //// def check(a: IntOuter, b: StrOuter):
882
+ //// /*a*/a
883
+ //// /*b*/b
884
+ `;
885
+ const state = (0, testState_1.parseAndGetTestState)(code).state;
886
+ const evaluator = state.program.evaluator;
887
+ const [aType, bType] = ['a', 'b'].map((marker) => {
888
+ const node = (0, testState_1.getNodeAtMarker)(state, marker);
889
+ assert.strictEqual(node.nodeType, 38 /* ParseNodeType.Name */);
890
+ return evaluator.getTypeOfExpression(node).type;
891
+ });
892
+ const diagnostics = new diagnostic_1.DiagnosticAddendum();
893
+ assert.strictEqual(evaluator.assignType(aType, bType, diagnostics), false);
894
+ const rendered = diagnostics.getString(undefined, 1000);
895
+ expect(rendered).toContain('"first"');
896
+ expect(rendered).toContain('"second"');
897
+ assert.strictEqual(rendered.split('"str" is not assignable to "int"').length - 1, 2);
898
+ });
899
+ test.each([64, 65].flatMap((width) => [false, true].map((hotFirst) => ({ width, hotFirst }))))('TypedDictDestinationWorkingSet width=$width hotFirst=$hotFirst', ({ width, hotFirst }) => {
900
+ const layers = 4;
901
+ const classes = Array.from({ length: layers }, (_, layer) => {
902
+ const indices = Array.from({ length: width }, (_, index) => index);
903
+ const fields = (prefix) => {
904
+ if (layer === 0) {
905
+ return '//// value: ReadOnly[int]';
906
+ }
907
+ const hot = Array.from({ length: 8 }, (_, index) => (hotFirst ? index : width - 8 + index));
908
+ const repeatedHot = Array.from({ length: 8 }, () => hot).flat();
909
+ const targets = hotFirst ? [...repeatedHot, ...indices] : [...indices, ...repeatedHot];
910
+ return targets
911
+ .map((target, field) => `//// field${field}: ReadOnly["${prefix === 'Source' ? `Source${layer - 1}` : `Dest${layer - 1}_${target}`}"]`)
912
+ .join('\n');
913
+ };
914
+ return (`//// class Source${layer}(TypedDict):\n${fields('Source')}\n////\n` +
915
+ indices
916
+ .map((index) => `//// class Dest${layer}_${index}(TypedDict):\n${fields('Dest')}\n////`)
917
+ .join('\n'));
918
+ }).join('\n');
919
+ const code = `
920
+ // @filename: test.py
921
+ //// from typing import TypedDict
922
+ //// from typing_extensions import ReadOnly
923
+ ////
924
+ ${classes}
925
+ //// def check(dest: Dest${layers - 1}_0, source: Source${layers - 1}):
926
+ //// /*dest*/dest
927
+ //// /*source*/source
928
+ `;
929
+ const state = (0, testState_1.parseAndGetTestState)(code).state;
930
+ const evaluator = state.program.evaluator;
931
+ const [destType, sourceType] = ['dest', 'source'].map((marker) => {
932
+ const node = (0, testState_1.getNodeAtMarker)(state, marker);
933
+ assert.strictEqual(node.nodeType, 38 /* ParseNodeType.Name */);
934
+ return evaluator.getTypeOfExpression(node).type;
935
+ });
936
+ const assignType = evaluator.assignType;
937
+ let comparisons = 0;
938
+ const spy = jest.spyOn(evaluator, 'assignType').mockImplementation((...args) => {
939
+ assert.ok(++comparisons < 20000, 'Destination classes evicted each other from the comparison cache');
940
+ return assignType(...args);
941
+ });
942
+ try {
943
+ assert.strictEqual(evaluator.assignType(destType, sourceType), true);
944
+ }
945
+ finally {
946
+ spy.mockRestore();
947
+ }
948
+ });
949
+ test('TypedDictRecursive1', () => {
950
+ const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typedDictRecursive1.py']);
951
+ TestUtils.validateResults(analysisResults, 0);
952
+ });
953
+ test('TypedDictRecursive2', () => {
954
+ const configOptions = new configOptions_1.ConfigOptions(uri_1.Uri.empty());
955
+ configOptions.defaultPythonVersion = pythonVersion_1.pythonVersion3_13;
956
+ const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typedDictRecursive2.py'], configOptions);
957
+ TestUtils.validateResults(analysisResults, 5);
958
+ const wrapperDiagnostic = analysisResults[0].errors.find((diag) => diag.message.includes('"StrWrapper"'));
959
+ assert.ok(wrapperDiagnostic);
960
+ for (const name of ['parent', 'child', 'again']) {
961
+ expect(wrapperDiagnostic.message).toContain(`"${name}"`);
962
+ }
963
+ });
964
+ test('TypedDictRecursive3', () => {
965
+ const configOptions = new configOptions_1.ConfigOptions(uri_1.Uri.empty());
966
+ configOptions.defaultPythonVersion = pythonVersion_1.pythonVersion3_13;
967
+ configOptions.diagnosticRuleSet.enableExperimentalFeatures = true;
968
+ const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typedDictRecursive3.py'], configOptions);
969
+ TestUtils.validateResults(analysisResults, 0);
970
+ });
971
+ test('TypedDictRecursiveRecursionBudget', () => {
972
+ const chains = ['A', 'B']
973
+ .map((prefix) => Array.from({ length: 19 }, (_, index) => `
974
+ //// class ${prefix}Chain${index}(TypedDict):
975
+ //// next: ReadOnly["${prefix}${index === 18 ? 'Leaf' : `Chain${index + 1}`}"]
976
+ ////`).join('\n'))
977
+ .join('\n');
978
+ const code = `
979
+ // @filename: test.py
980
+ //// from typing import TypedDict
981
+ //// from typing_extensions import ReadOnly
982
+ ////
983
+ //// class ALeaf(TypedDict):
984
+ //// value: ReadOnly[int]
985
+ ////
986
+ //// class BLeaf(TypedDict):
987
+ //// value: ReadOnly[str]
988
+ ////
989
+ ${chains}
990
+ //// class ARoot(TypedDict):
991
+ //// deep: ReadOnly[AChain0]
992
+ //// shallow: ReadOnly[ALeaf]
993
+ ////
994
+ //// class BRoot(TypedDict):
995
+ //// deep: ReadOnly[BChain0]
996
+ //// shallow: ReadOnly[BLeaf]
997
+ ////
998
+ //// def check(a: ARoot, b: BRoot):
999
+ //// /*a*/a
1000
+ //// /*b*/b
1001
+ `;
1002
+ const state = (0, testState_1.parseAndGetTestState)(code).state;
1003
+ const evaluator = state.program.evaluator;
1004
+ const [aType, bType] = ['a', 'b'].map((marker) => {
1005
+ const node = (0, testState_1.getNodeAtMarker)(state, marker);
1006
+ assert.strictEqual(node.nodeType, 38 /* ParseNodeType.Name */);
1007
+ return evaluator.getTypeOfExpression(node).type;
1008
+ });
1009
+ assert.strictEqual(evaluator.assignType(aType, bType), false);
1010
+ const diagnostics = new diagnostic_1.DiagnosticAddendum();
1011
+ assert.strictEqual(evaluator.assignType(aType, bType, diagnostics), false);
1012
+ expect(diagnostics.getString()).toContain('"shallow"');
1013
+ });
1014
+ test.each([true, false])('TypedDictRecursiveSharedGraph compatible=%s', (compatible) => {
1015
+ const width = compatible ? 64 : 16;
1016
+ const layers = compatible ? 4 : 3;
1017
+ const classes = ['A', 'B']
1018
+ .map((prefix) => Array.from({ length: layers }, (_, layer) => Array.from({ length: width }, (_, index) => {
1019
+ const fields = Array.from({ length: layer === 0 ? 1 : width }, (_, field) => `//// field${field}: ReadOnly["${layer === 0 ? `${prefix}Leaf` : `${prefix}${layer - 1}_${field}`}"]`).join('\n');
1020
+ return `//// class ${prefix}${layer}_${index}(TypedDict):\n${fields}\n////`;
1021
+ }).join('\n')).join('\n'))
1022
+ .join('\n');
1023
+ const code = `
1024
+ // @filename: test.py
1025
+ //// from typing import TypedDict
1026
+ //// from typing_extensions import ReadOnly
1027
+ ////
1028
+ //// class ALeaf(TypedDict):
1029
+ //// value: ReadOnly[int]
1030
+ ////
1031
+ //// class BLeaf(TypedDict):
1032
+ //// value: ReadOnly[${compatible ? 'int' : 'str'}]
1033
+ ////
1034
+ ${classes}
1035
+ //// def check(a: A${layers - 1}_0, b: B${layers - 1}_0):
1036
+ //// /*a*/a
1037
+ //// /*b*/b
1038
+ `;
1039
+ const state = (0, testState_1.parseAndGetTestState)(code).state;
1040
+ const evaluator = state.program.evaluator;
1041
+ const [aType, bType] = ['a', 'b'].map((marker) => {
1042
+ const node = (0, testState_1.getNodeAtMarker)(state, marker);
1043
+ assert.strictEqual(node.nodeType, 38 /* ParseNodeType.Name */);
1044
+ return evaluator.getTypeOfExpression(node).type;
1045
+ });
1046
+ const assignType = evaluator.assignType;
1047
+ let comparisons = 0;
1048
+ const spy = jest.spyOn(evaluator, 'assignType').mockImplementation((...args) => {
1049
+ assert.ok(++comparisons < 20000, 'Shared TypedDict subgraphs were repeatedly recomputed');
1050
+ return assignType(...args);
1051
+ });
1052
+ try {
1053
+ assert.strictEqual(evaluator.assignType(aType, bType), compatible);
1054
+ if (!compatible) {
1055
+ const diagnostics = new diagnostic_1.DiagnosticAddendum();
1056
+ assert.strictEqual(evaluator.assignType(aType, bType, diagnostics), false);
1057
+ expect(diagnostics.getString(undefined, 1000)).toContain('"str" is not assignable to "int"');
1058
+ }
1059
+ }
1060
+ finally {
1061
+ spy.mockRestore();
1062
+ }
1063
+ });
1064
+ test.each([
1065
+ [false, true],
1066
+ [true, true],
1067
+ [false, false],
1068
+ [true, false],
1069
+ ])('TypedDictRecursiveWide constraints=%s compatible=%s', (withConstraints, expectedCompatibility) => {
1070
+ const fields = (prefix) => Array.from({ length: 56 }, (_, index) => `//// rel${index}: bool | "${prefix}Args"`).join('\n');
1071
+ const code = `
1072
+ // @filename: test.py
1073
+ //// from typing import TypedDict
1074
+ ////
1075
+ //// class AInclude(TypedDict, total=False):
1076
+ ${fields('A')}
1077
+ //// value: int
1078
+ ////
1079
+ //// class AArgs(TypedDict, total=False):
1080
+ //// include: "AInclude"
1081
+ ////
1082
+ //// class BInclude(TypedDict, total=False):
1083
+ ${fields('B')}
1084
+ //// value: ${expectedCompatibility ? 'int' : 'str'}
1085
+ ////
1086
+ //// class BArgs(TypedDict, total=False):
1087
+ //// include: "BInclude"
1088
+ ////
1089
+ //// def check(a: AInclude, b: BInclude):
1090
+ //// /*a*/a
1091
+ //// /*b*/b
1092
+ `;
1093
+ const state = (0, testState_1.parseAndGetTestState)(code).state;
1094
+ const evaluator = state.program.evaluator;
1095
+ const [aType, bType] = ['a', 'b'].map((marker) => {
1096
+ const node = (0, testState_1.getNodeAtMarker)(state, marker);
1097
+ assert.strictEqual(node.nodeType, 38 /* ParseNodeType.Name */);
1098
+ return evaluator.getTypeOfExpression(node).type;
1099
+ });
1100
+ const assignType = evaluator.assignType;
1101
+ let comparisons = 0;
1102
+ const spy = jest.spyOn(evaluator, 'assignType').mockImplementation((...args) => {
1103
+ // Bound work rather than wall-clock time, and fail promptly if recursion returns.
1104
+ assert.ok(++comparisons < 2000, 'Recursive TypedDict comparison exceeded its work budget');
1105
+ return assignType(...args);
1106
+ });
1107
+ try {
1108
+ const constraints = withConstraints ? new constraintTracker_1.ConstraintTracker() : undefined;
1109
+ assert.strictEqual(evaluator.assignType(bType, aType, undefined, constraints), expectedCompatibility);
1110
+ const diagnostics = new diagnostic_1.DiagnosticAddendum();
1111
+ assert.strictEqual(evaluator.assignType(aType, bType, diagnostics, constraints), expectedCompatibility);
1112
+ if (!expectedCompatibility) {
1113
+ expect(diagnostics.getString(undefined, 1000)).toContain('"value"');
1114
+ }
1115
+ }
1116
+ finally {
1117
+ spy.mockRestore();
1118
+ }
1119
+ });
706
1120
  test('TypedDict1', () => {
707
1121
  const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typedDict1.py']);
708
1122
  TestUtils.validateResults(analysisResults, 11);