@zzzen/pyright-internal 1.2.0-dev.20260920 → 1.2.0-dev.20260927
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.
- package/dist/analyzer/checker.d.ts +2 -1
- package/dist/analyzer/checker.js +15 -1
- package/dist/analyzer/checker.js.map +1 -1
- package/dist/analyzer/codeFlowEngine.js +9 -3
- package/dist/analyzer/codeFlowEngine.js.map +1 -1
- package/dist/analyzer/operations.js +1 -1
- package/dist/analyzer/operations.js.map +1 -1
- package/dist/analyzer/overloadResult.d.ts +15 -0
- package/dist/analyzer/overloadResult.js +233 -0
- package/dist/analyzer/overloadResult.js.map +1 -0
- package/dist/analyzer/overloadResultAdmission.d.ts +20 -0
- package/dist/analyzer/overloadResultAdmission.js +448 -0
- package/dist/analyzer/overloadResultAdmission.js.map +1 -0
- package/dist/analyzer/overloadResultController.d.ts +227 -0
- package/dist/analyzer/overloadResultController.js +904 -0
- package/dist/analyzer/overloadResultController.js.map +1 -0
- package/dist/analyzer/overloadResultSelector.d.ts +24 -0
- package/dist/analyzer/overloadResultSelector.js +131 -0
- package/dist/analyzer/overloadResultSelector.js.map +1 -0
- package/dist/analyzer/parseTreeUtils.d.ts +1 -0
- package/dist/analyzer/parseTreeUtils.js +4 -0
- package/dist/analyzer/parseTreeUtils.js.map +1 -1
- package/dist/analyzer/program.d.ts +13 -2
- package/dist/analyzer/program.js +98 -46
- package/dist/analyzer/program.js.map +1 -1
- package/dist/analyzer/protocols.d.ts +1 -0
- package/dist/analyzer/protocols.js +286 -6
- package/dist/analyzer/protocols.js.map +1 -1
- package/dist/analyzer/sourceFile.d.ts +6 -2
- package/dist/analyzer/sourceFile.js +9 -3
- package/dist/analyzer/sourceFile.js.map +1 -1
- package/dist/analyzer/tracePrinter.js +4 -0
- package/dist/analyzer/tracePrinter.js.map +1 -1
- package/dist/analyzer/typeCacheUtils.d.ts +4 -0
- package/dist/analyzer/typeCacheUtils.js +36 -0
- package/dist/analyzer/typeCacheUtils.js.map +1 -1
- package/dist/analyzer/typeComplexity.js +2 -0
- package/dist/analyzer/typeComplexity.js.map +1 -1
- package/dist/analyzer/typeEvaluator.d.ts +10 -1
- package/dist/analyzer/typeEvaluator.js +351 -11
- package/dist/analyzer/typeEvaluator.js.map +1 -1
- package/dist/analyzer/typeEvaluatorTypes.d.ts +30 -1
- package/dist/analyzer/typeEvaluatorTypes.js.map +1 -1
- package/dist/analyzer/typeGuards.d.ts +1 -1
- package/dist/analyzer/typePrinter.js +8 -0
- package/dist/analyzer/typePrinter.js.map +1 -1
- package/dist/analyzer/typeUtils.d.ts +1 -1
- package/dist/analyzer/typeUtils.js +29 -26
- package/dist/analyzer/typeUtils.js.map +1 -1
- package/dist/analyzer/typeWalker.d.ts +2 -1
- package/dist/analyzer/typeWalker.js +12 -0
- package/dist/analyzer/typeWalker.js.map +1 -1
- package/dist/analyzer/typedDicts.js +1 -0
- package/dist/analyzer/typedDicts.js.map +1 -1
- package/dist/analyzer/types.d.ts +16 -2
- package/dist/analyzer/types.js +94 -2
- package/dist/analyzer/types.js.map +1 -1
- package/dist/common/configOptions.d.ts +1 -0
- package/dist/common/configOptions.js +2 -0
- package/dist/common/configOptions.js.map +1 -1
- package/dist/languageServerBase.js +2 -2
- package/dist/languageServerBase.js.map +1 -1
- package/dist/languageService/callHierarchyProvider.js +3 -2
- package/dist/languageService/callHierarchyProvider.js.map +1 -1
- package/dist/languageService/completionProvider.js +4 -1
- package/dist/languageService/completionProvider.js.map +1 -1
- package/dist/tests/config.test.js +48 -0
- package/dist/tests/config.test.js.map +1 -1
- package/dist/tests/fourslash/showcallhierarchy.incomingCalls.aliasedFunction.fourslash.js +10 -6
- package/dist/tests/fourslash/showcallhierarchy.incomingCalls.aliasedFunction.fourslash.js.map +1 -1
- package/dist/tests/fourslash/showcallhierarchy.incomingCalls.function.fourslash.js +16 -10
- package/dist/tests/fourslash/showcallhierarchy.incomingCalls.function.fourslash.js.map +1 -1
- package/dist/tests/harness/fourslash/testState.js +10 -9
- package/dist/tests/harness/fourslash/testState.js.map +1 -1
- package/dist/tests/languageServer.test.js +129 -0
- package/dist/tests/languageServer.test.js.map +1 -1
- package/dist/tests/overloadResult.test.d.ts +1 -0
- package/dist/tests/overloadResult.test.js +182 -0
- package/dist/tests/overloadResult.test.js.map +1 -0
- package/dist/tests/overloadResultController.test.d.ts +1 -0
- package/dist/tests/overloadResultController.test.js +2146 -0
- package/dist/tests/overloadResultController.test.js.map +1 -0
- package/dist/tests/protocolRequirement.test.d.ts +1 -0
- package/dist/tests/protocolRequirement.test.js +122 -0
- package/dist/tests/protocolRequirement.test.js.map +1 -0
- package/dist/tests/protocolRequirementAdversarial.test.d.ts +1 -0
- package/dist/tests/protocolRequirementAdversarial.test.js +92 -0
- package/dist/tests/protocolRequirementAdversarial.test.js.map +1 -0
- package/dist/tests/protocolRequirementBoundary.test.d.ts +1 -0
- package/dist/tests/protocolRequirementBoundary.test.js +119 -0
- package/dist/tests/protocolRequirementBoundary.test.js.map +1 -0
- package/dist/tests/protocolRequirementComplexity.test.d.ts +1 -0
- package/dist/tests/protocolRequirementComplexity.test.js +159 -0
- package/dist/tests/protocolRequirementComplexity.test.js.map +1 -0
- package/dist/tests/protocolRequirementCycles.test.d.ts +1 -0
- package/dist/tests/protocolRequirementCycles.test.js +160 -0
- package/dist/tests/protocolRequirementCycles.test.js.map +1 -0
- package/dist/tests/protocolRequirementGeneral.test.d.ts +1 -0
- package/dist/tests/protocolRequirementGeneral.test.js +120 -0
- package/dist/tests/protocolRequirementGeneral.test.js.map +1 -0
- package/dist/tests/protocolRequirementInstances.test.d.ts +1 -0
- package/dist/tests/protocolRequirementInstances.test.js +164 -0
- package/dist/tests/protocolRequirementInstances.test.js.map +1 -0
- package/dist/tests/protocolRequirementListStub.test.d.ts +1 -0
- package/dist/tests/protocolRequirementListStub.test.js +244 -0
- package/dist/tests/protocolRequirementListStub.test.js.map +1 -0
- package/dist/tests/protocolRequirementPopulated.test.d.ts +1 -0
- package/dist/tests/protocolRequirementPopulated.test.js +172 -0
- package/dist/tests/protocolRequirementPopulated.test.js.map +1 -0
- package/dist/tests/protocolRequirementRecursive.test.d.ts +1 -0
- package/dist/tests/protocolRequirementRecursive.test.js +118 -0
- package/dist/tests/protocolRequirementRecursive.test.js.map +1 -0
- package/dist/tests/protocolRequirementSpeculative.test.d.ts +1 -0
- package/dist/tests/protocolRequirementSpeculative.test.js +200 -0
- package/dist/tests/protocolRequirementSpeculative.test.js.map +1 -0
- package/dist/tests/protocolRequirementState.test.d.ts +1 -0
- package/dist/tests/protocolRequirementState.test.js +161 -0
- package/dist/tests/protocolRequirementState.test.js.map +1 -0
- package/dist/tests/testState.test.js +64 -0
- package/dist/tests/testState.test.js.map +1 -1
- package/dist/tests/testUtils.d.ts +6 -1
- package/dist/tests/testUtils.js +11 -6
- package/dist/tests/testUtils.js.map +1 -1
- package/dist/tests/typeCacheIsolation.test.d.ts +1 -0
- package/dist/tests/typeCacheIsolation.test.js +109 -0
- package/dist/tests/typeCacheIsolation.test.js.map +1 -0
- package/dist/tests/typeCacheUtils.test.js +59 -0
- package/dist/tests/typeCacheUtils.test.js.map +1 -1
- package/dist/tests/typeEvaluator1.test.js +5 -1
- package/dist/tests/typeEvaluator1.test.js.map +1 -1
- package/dist/tests/typeEvaluator4.test.js +6 -2
- package/dist/tests/typeEvaluator4.test.js.map +1 -1
- package/dist/tests/typeEvaluator5.test.js +25 -0
- package/dist/tests/typeEvaluator5.test.js.map +1 -1
- package/dist/tests/typeEvaluator6.test.js +7 -23
- package/dist/tests/typeEvaluator6.test.js.map +1 -1
- package/dist/tests/typeEvaluator7.test.js +237 -2
- package/dist/tests/typeEvaluator7.test.js.map +1 -1
- package/dist/tests/typeServer/typeServer.inProc.test.js +86 -0
- package/dist/tests/typeServer/typeServer.inProc.test.js.map +1 -1
- package/dist/tests/typeUtils.test.js +36 -0
- package/dist/tests/typeUtils.test.js.map +1 -1
- package/dist/trace/nativeAdapter.js +1 -5
- package/dist/trace/nativeAdapter.js.map +1 -1
- package/dist/typeServer/protocol/typeServerProtocol.d.ts +8 -2
- package/dist/typeServer/protocol/typeServerProtocol.js +1 -0
- package/dist/typeServer/protocol/typeServerProtocol.js.map +1 -1
- package/dist/typeServer/typeServerConversionUtils.js +9 -0
- package/dist/typeServer/typeServerConversionUtils.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,2146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* overloadResultController.test.ts
|
|
4
|
+
* Copyright (c) Microsoft Corporation.
|
|
5
|
+
* Licensed under the MIT license.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
const assert = __importStar(require("assert"));
|
|
42
|
+
const vscode_languageserver_1 = require("vscode-languageserver");
|
|
43
|
+
const analyzerNodeInfo_1 = require("../analyzer/analyzerNodeInfo");
|
|
44
|
+
const cacheManager_1 = require("../analyzer/cacheManager");
|
|
45
|
+
const importResolver_1 = require("../analyzer/importResolver");
|
|
46
|
+
const parseTreeUtils_1 = require("../analyzer/parseTreeUtils");
|
|
47
|
+
const parseTreeWalker_1 = require("../analyzer/parseTreeWalker");
|
|
48
|
+
const program_1 = require("../analyzer/program");
|
|
49
|
+
const TypeEvaluatorModule = __importStar(require("../analyzer/typeEvaluator"));
|
|
50
|
+
const types_1 = require("../analyzer/types");
|
|
51
|
+
const cancellationUtils_1 = require("../common/cancellationUtils");
|
|
52
|
+
const configOptions_1 = require("../common/configOptions");
|
|
53
|
+
const console_1 = require("../common/console");
|
|
54
|
+
const deferred_1 = require("../common/deferred");
|
|
55
|
+
const fullAccessHost_1 = require("../common/fullAccessHost");
|
|
56
|
+
const pythonVersion_1 = require("../common/pythonVersion");
|
|
57
|
+
const realFileSystem_1 = require("../common/realFileSystem");
|
|
58
|
+
const serviceProviderExtensions_1 = require("../common/serviceProviderExtensions");
|
|
59
|
+
const uriUtils_1 = require("../common/uri/uriUtils");
|
|
60
|
+
const hoverProvider_1 = require("../languageService/hoverProvider");
|
|
61
|
+
const completionProvider_1 = require("../languageService/completionProvider");
|
|
62
|
+
const testUtils_1 = require("./testUtils");
|
|
63
|
+
class Nodes extends parseTreeWalker_1.ParseTreeWalker {
|
|
64
|
+
constructor() {
|
|
65
|
+
super(...arguments);
|
|
66
|
+
this.nodes = [];
|
|
67
|
+
}
|
|
68
|
+
visitNode(node) {
|
|
69
|
+
this.nodes.push(node);
|
|
70
|
+
return super.visitNode(node);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const fixture = (0, testUtils_1.readSampleFile)('overloadResultController1.py');
|
|
74
|
+
function resultSummary(evaluator, result) {
|
|
75
|
+
const { type, bindToSelfType, unpackedType, typeList, inlinedTypeDict, classType, narrowedTypeForSet, overloadsUsedForCall, expectedTypeDiagAddendum, ...metadata } = result;
|
|
76
|
+
return {
|
|
77
|
+
...metadata,
|
|
78
|
+
type: evaluator.printType(type),
|
|
79
|
+
bindToSelfType: bindToSelfType && evaluator.printType(bindToSelfType),
|
|
80
|
+
unpackedType: unpackedType && evaluator.printType(unpackedType),
|
|
81
|
+
inlinedTypeDict: inlinedTypeDict && evaluator.printType(inlinedTypeDict),
|
|
82
|
+
classType: classType && evaluator.printType(classType),
|
|
83
|
+
narrowedTypeForSet: narrowedTypeForSet && evaluator.printType(narrowedTypeForSet),
|
|
84
|
+
overloadsUsedForCall: overloadsUsedForCall?.map((t) => evaluator.printType(t)),
|
|
85
|
+
expectedTypeDiagAddendum: expectedTypeDiagAddendum?.getString(),
|
|
86
|
+
typeList: typeList?.map(({ node, ...value }) => ({
|
|
87
|
+
node: { start: node.start, length: node.length, nodeType: node.nodeType },
|
|
88
|
+
value: resultSummary(evaluator, value),
|
|
89
|
+
})),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function create(content = fixture, options = {}, automaticConfiguration = false, observe = true, checking) {
|
|
93
|
+
const temp = new realFileSystem_1.RealTempFile();
|
|
94
|
+
const cacheManager = new cacheManager_1.CacheManager();
|
|
95
|
+
const service = (0, serviceProviderExtensions_1.createServiceProvider)((0, realFileSystem_1.createFromRealFileSystem)(temp), new console_1.NullConsole(), temp, cacheManager);
|
|
96
|
+
const uri = uriUtils_1.UriEx.file((0, testUtils_1.resolveSampleFilePath)('overloadResultController1.py'));
|
|
97
|
+
const config = new configOptions_1.ConfigOptions(uri.getDirectory());
|
|
98
|
+
config.defaultPythonVersion = pythonVersion_1.pythonVersion3_12;
|
|
99
|
+
config.internalTestMode = true;
|
|
100
|
+
if (automaticConfiguration !== 'default') {
|
|
101
|
+
config.experimentalOverloadResults = automaticConfiguration;
|
|
102
|
+
}
|
|
103
|
+
const resolver = new importResolver_1.ImportResolver(service, config, new fullAccessHost_1.FullAccessHost(service));
|
|
104
|
+
const events = [];
|
|
105
|
+
const experiment = {
|
|
106
|
+
seeds(module) {
|
|
107
|
+
const info = (0, analyzerNodeInfo_1.createAnalyzerNodeInfoAccessor)(program.analyzerNodeInfoReader);
|
|
108
|
+
if (!info.getFileInfo(module).fileUri.equals(uri)) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const walker = new Nodes();
|
|
112
|
+
walker.walk(module);
|
|
113
|
+
return walker.nodes
|
|
114
|
+
.filter((n) => n.nodeType === 9 /* ParseNodeType.Call */ &&
|
|
115
|
+
n.d.leftExpr.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
116
|
+
n.d.leftExpr.d.value === 'choose')
|
|
117
|
+
.map((node) => ({
|
|
118
|
+
node,
|
|
119
|
+
uncertainty: 2 /* TypeCategory.Any */,
|
|
120
|
+
candidates(evaluator) {
|
|
121
|
+
const type = evaluator.getTypeOfExpression(node.d.leftExpr).type;
|
|
122
|
+
assert.ok((0, types_1.isOverloaded)(type));
|
|
123
|
+
return types_1.OverloadedType.getOverloads(type).map((f) => types_1.FunctionType.getEffectiveReturnType(f));
|
|
124
|
+
},
|
|
125
|
+
}));
|
|
126
|
+
},
|
|
127
|
+
...(options || {}),
|
|
128
|
+
observe: observe
|
|
129
|
+
? (event) => {
|
|
130
|
+
events.push(event);
|
|
131
|
+
if (options) {
|
|
132
|
+
options.observe?.(event);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
: undefined,
|
|
136
|
+
};
|
|
137
|
+
const program = new program_1.Program(resolver, config, service, undefined, undefined, undefined, checking, options === false ? undefined : options.automatic ? { ...experiment, seeds: undefined } : experiment);
|
|
138
|
+
program.setTrackedFiles([uri]);
|
|
139
|
+
program.setFileOpened(uri, 1, content);
|
|
140
|
+
program.getBoundSourceFile(uri);
|
|
141
|
+
const walker = new Nodes();
|
|
142
|
+
walker.walk(program.getParseResults(uri).parserOutput.parseTree);
|
|
143
|
+
const module = program.getParseResults(uri).parserOutput.parseTree;
|
|
144
|
+
const info = (0, analyzerNodeInfo_1.createAnalyzerNodeInfoAccessor)(program.analyzerNodeInfoReader);
|
|
145
|
+
const name = (value, fn = 'positive', occurrence = 0) => {
|
|
146
|
+
const names = walker.nodes.filter((n) => n.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
147
|
+
n.d.value === value &&
|
|
148
|
+
(0, parseTreeUtils_1.getEnclosingFunction)(n)?.d.name.d.value === fn);
|
|
149
|
+
assert.ok(names[occurrence]);
|
|
150
|
+
return names[occurrence];
|
|
151
|
+
};
|
|
152
|
+
const analyze = () => {
|
|
153
|
+
let count = 0;
|
|
154
|
+
while (program.analyze()) {
|
|
155
|
+
assert.ok(++count < 100);
|
|
156
|
+
}
|
|
157
|
+
return program
|
|
158
|
+
.getSourceFile(uri)
|
|
159
|
+
.getDiagnostics(config)
|
|
160
|
+
.map((d) => ({ category: d.category, rule: d.getRule(), message: d.message, range: d.range }));
|
|
161
|
+
};
|
|
162
|
+
let disposed = false;
|
|
163
|
+
return {
|
|
164
|
+
program,
|
|
165
|
+
cacheManager,
|
|
166
|
+
uri,
|
|
167
|
+
config,
|
|
168
|
+
events,
|
|
169
|
+
module,
|
|
170
|
+
nodes: walker.nodes,
|
|
171
|
+
name,
|
|
172
|
+
analyze,
|
|
173
|
+
info,
|
|
174
|
+
dispose() {
|
|
175
|
+
if (!disposed) {
|
|
176
|
+
program.dispose();
|
|
177
|
+
service.dispose();
|
|
178
|
+
disposed = true;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
test.each(['default', false])('AutomaticOverloadResult memory pressure during lazy import preserves checking config=%s', (config) => {
|
|
184
|
+
const source = 'from os import name\nbefore: int = "bad"\nafter: str = 1\n';
|
|
185
|
+
const reference = create(source, false, config);
|
|
186
|
+
const test = create(source, false, config);
|
|
187
|
+
const nextUri = test.uri.getDirectory().combinePaths('overloadResultCachePressureNext.py');
|
|
188
|
+
test.program.setTrackedFiles([test.uri, nextUri]);
|
|
189
|
+
test.program.setFileOpened(nextUri, 1, 'next_error: int = "bad"\n');
|
|
190
|
+
let pressure = false;
|
|
191
|
+
const heap = jest.spyOn(test.cacheManager, 'getUsedHeapRatio').mockImplementation(() => {
|
|
192
|
+
if (!pressure) {
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
pressure = false;
|
|
196
|
+
return 1;
|
|
197
|
+
});
|
|
198
|
+
const empty = jest.spyOn(test.program, 'emptyCache');
|
|
199
|
+
const evaluator = test.program.evaluator;
|
|
200
|
+
const name = test.nodes.find((node) => node.nodeType === 38 /* ParseNodeType.Name */);
|
|
201
|
+
let nextEvaluator;
|
|
202
|
+
test.program.setPreCheckCallback((parsed, current) => {
|
|
203
|
+
if (parsed.parseTree === test.module) {
|
|
204
|
+
pressure = true;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
nextEvaluator = current;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
try {
|
|
211
|
+
const expected = reference.analyze();
|
|
212
|
+
assert.equal(expected.filter((d) => d.category === 0 /* DiagnosticCategory.Error */).length, 2);
|
|
213
|
+
assert.deepStrictEqual(test.analyze(), expected);
|
|
214
|
+
assert.equal(empty.mock.calls.length, 2);
|
|
215
|
+
assert.notStrictEqual(test.program.evaluator, evaluator);
|
|
216
|
+
assert.strictEqual(nextEvaluator, test.program.evaluator);
|
|
217
|
+
const nextDiagnostics = test.program.getSourceFile(nextUri).getDiagnostics(test.config);
|
|
218
|
+
assert.equal(nextDiagnostics.length, 1);
|
|
219
|
+
assert.equal(nextDiagnostics[0].getRule(), 'reportAssignmentType');
|
|
220
|
+
if (config === 'default') {
|
|
221
|
+
assert.throws(() => evaluator.getTypeOfExpression(name), /Retired overload-result controller/);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
heap.mockRestore();
|
|
226
|
+
empty.mockRestore();
|
|
227
|
+
reference.dispose();
|
|
228
|
+
test.dispose();
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
test.each([false, true])('AutomaticOverloadResult memory pressure inside an owned operation queryFirst=%s', (queryFirst) => {
|
|
232
|
+
const source = (0, testUtils_1.readSampleFile)('overloadResultCachePressure1.py');
|
|
233
|
+
const reference = create(source, { automatic: true });
|
|
234
|
+
let pressure = false;
|
|
235
|
+
let triggered = false;
|
|
236
|
+
const test = create(source, {
|
|
237
|
+
automatic: true,
|
|
238
|
+
observe(event) {
|
|
239
|
+
if (!triggered &&
|
|
240
|
+
event.kind === 'beforeCall' &&
|
|
241
|
+
event.node.d.leftExpr.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
242
|
+
event.node.d.leftExpr.d.value === 'cold') {
|
|
243
|
+
pressure = true;
|
|
244
|
+
triggered = true;
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
const original = test.program.evaluator;
|
|
249
|
+
const controller = test.program.experimentalOverloadResultController;
|
|
250
|
+
const heap = jest.spyOn(test.cacheManager, 'getUsedHeapRatio').mockImplementation(() => {
|
|
251
|
+
if (!pressure) {
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
pressure = false;
|
|
255
|
+
return 1;
|
|
256
|
+
});
|
|
257
|
+
const empty = jest.spyOn(test.cacheManager, 'emptyCache');
|
|
258
|
+
try {
|
|
259
|
+
if (queryFirst) {
|
|
260
|
+
test.program.run((program) => {
|
|
261
|
+
const call = test.nodes.find((node) => node.nodeType === 9 /* ParseNodeType.Call */ &&
|
|
262
|
+
node.d.leftExpr.nodeType === 35 /* ParseNodeType.MemberAccess */ &&
|
|
263
|
+
node.d.leftExpr.d.member.d.value === 'append' &&
|
|
264
|
+
source.substring(node.start, node.start + node.length).includes('cold()'));
|
|
265
|
+
program.evaluator.getType(call);
|
|
266
|
+
assert.strictEqual(program.evaluator, original);
|
|
267
|
+
assert.ok((0, types_1.isOverloadResult)(program.evaluator.getType(test.name('ret'))));
|
|
268
|
+
}, vscode_languageserver_1.CancellationToken.None);
|
|
269
|
+
}
|
|
270
|
+
const expected = reference.analyze();
|
|
271
|
+
assert.deepStrictEqual(expected
|
|
272
|
+
.filter((d) => d.category === 0 /* DiagnosticCategory.Error */)
|
|
273
|
+
.map((d) => [d.range.start.line, d.rule]), [
|
|
274
|
+
['before: int', 'reportAssignmentType'],
|
|
275
|
+
['ret.append(cold())', 'reportArgumentType'],
|
|
276
|
+
['ret.extend([1, "mixed"])', 'reportArgumentType'],
|
|
277
|
+
['bad: list[bytes]', 'reportAssignmentType'],
|
|
278
|
+
['ret.append(3.14)', 'reportArgumentType'],
|
|
279
|
+
['ret.nonexistent()', 'reportAttributeAccessIssue'],
|
|
280
|
+
['after: int', 'reportAssignmentType'],
|
|
281
|
+
['after_file: str', 'reportAssignmentType'],
|
|
282
|
+
].map(([text, rule]) => [source.slice(0, source.indexOf(text)).split('\n').length - 1, rule]));
|
|
283
|
+
assert.ok(expected.some((d) => d.message.includes('OverloadResult[list[int], list[str]]')));
|
|
284
|
+
assert.deepStrictEqual(test.analyze(), expected);
|
|
285
|
+
assert.equal(empty.mock.calls.length, 1);
|
|
286
|
+
assert.notStrictEqual(test.program.evaluator, original);
|
|
287
|
+
assert.equal(controller.getStats(test.module), undefined);
|
|
288
|
+
assert.throws(() => original.getType(test.name('ret')), /Retired overload-result controller/);
|
|
289
|
+
const nodes = new Nodes();
|
|
290
|
+
nodes.walk(test.program.getParseResults(test.uri).parserOutput.parseTree);
|
|
291
|
+
const alias = nodes.nodes.find((node) => node.nodeType === 38 /* ParseNodeType.Name */ && node.d.value === 'alias');
|
|
292
|
+
test.program.run((program) => {
|
|
293
|
+
assert.deepStrictEqual(resultSummary(program.evaluator, program.evaluator.getTypeOfExpression(alias)), resultSummary(reference.program.evaluator, reference.program.evaluator.getTypeOfExpression(reference.name('alias'))));
|
|
294
|
+
}, vscode_languageserver_1.CancellationToken.None);
|
|
295
|
+
assert.deepStrictEqual(test.analyze(), expected);
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
heap.mockRestore();
|
|
299
|
+
empty.mockRestore();
|
|
300
|
+
reference.dispose();
|
|
301
|
+
test.dispose();
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
test.each(['complete', 'cancel', 'error'])('AutomaticOverloadResult deferred cache cleanup unwinds nested async requests: %s', async (outcome) => {
|
|
305
|
+
const source = 'def positive():\n bad: int = "wrong"\n';
|
|
306
|
+
const reference = create(source, false, 'default');
|
|
307
|
+
const test = create(source, false, 'default');
|
|
308
|
+
const evaluator = test.program.evaluator;
|
|
309
|
+
const node = test.name('bad');
|
|
310
|
+
try {
|
|
311
|
+
const request = test.program.run(async (program) => {
|
|
312
|
+
test.cacheManager.emptyCache();
|
|
313
|
+
test.program.run(() => {
|
|
314
|
+
test.cacheManager.emptyCache();
|
|
315
|
+
}, vscode_languageserver_1.CancellationToken.None);
|
|
316
|
+
await Promise.resolve();
|
|
317
|
+
assert.strictEqual(program.evaluator, evaluator);
|
|
318
|
+
if (outcome === 'cancel') {
|
|
319
|
+
throw new cancellationUtils_1.OperationCanceledException();
|
|
320
|
+
}
|
|
321
|
+
if (outcome === 'error') {
|
|
322
|
+
throw new Error('cache pressure interruption');
|
|
323
|
+
}
|
|
324
|
+
}, vscode_languageserver_1.CancellationToken.None);
|
|
325
|
+
if (outcome === 'complete') {
|
|
326
|
+
await request;
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
await assert.rejects(request, (error) => outcome === 'cancel'
|
|
330
|
+
? cancellationUtils_1.OperationCanceledException.is(error)
|
|
331
|
+
: error instanceof Error && error.message === 'cache pressure interruption');
|
|
332
|
+
}
|
|
333
|
+
assert.notStrictEqual(test.program.evaluator, evaluator);
|
|
334
|
+
assert.throws(() => evaluator.getType(node), /Retired overload-result controller/);
|
|
335
|
+
assert.deepStrictEqual(test.analyze(), reference.analyze());
|
|
336
|
+
const current = test.program.evaluator;
|
|
337
|
+
test.cacheManager.emptyCache();
|
|
338
|
+
assert.notStrictEqual(test.program.evaluator, current);
|
|
339
|
+
}
|
|
340
|
+
finally {
|
|
341
|
+
reference.dispose();
|
|
342
|
+
test.dispose();
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
test.each(['complete', 'cancel', 'error'])('AutomaticOverloadResult pending cache cleanup cannot recreate a disposed program: %s', async (outcome) => {
|
|
346
|
+
const test = create('def positive():\n bad: int = "wrong"\n', false, 'default');
|
|
347
|
+
const evaluator = test.program.evaluator;
|
|
348
|
+
const node = test.name('bad');
|
|
349
|
+
const resume = (0, deferred_1.createDeferred)();
|
|
350
|
+
const error = outcome === 'cancel' ? new cancellationUtils_1.OperationCanceledException() : new Error('shutdown interruption');
|
|
351
|
+
try {
|
|
352
|
+
const request = test.program.run(async () => {
|
|
353
|
+
test.cacheManager.emptyCache();
|
|
354
|
+
await resume.promise;
|
|
355
|
+
if (outcome !== 'complete') {
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
return 42;
|
|
359
|
+
}, vscode_languageserver_1.CancellationToken.None);
|
|
360
|
+
test.dispose();
|
|
361
|
+
resume.resolve();
|
|
362
|
+
if (outcome === 'complete') {
|
|
363
|
+
assert.equal(await request, 42);
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
await assert.rejects(request, (actual) => actual === error);
|
|
367
|
+
}
|
|
368
|
+
assert.strictEqual(test.program.evaluator, evaluator);
|
|
369
|
+
assert.throws(() => evaluator.getType(node), /Retired overload-result controller/);
|
|
370
|
+
}
|
|
371
|
+
finally {
|
|
372
|
+
resume.resolve();
|
|
373
|
+
test.dispose();
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
test.each([false, true])('AutomaticOverloadResult live checker handoff queryFirst=%s', (queryFirst) => {
|
|
377
|
+
const baseline = create(fixture, { automatic: true, checkerHandoff: false }, false, false);
|
|
378
|
+
const automatic = create(fixture, { automatic: true, checkerHandoff: true }, false, false);
|
|
379
|
+
try {
|
|
380
|
+
if (queryFirst) {
|
|
381
|
+
for (const test of [baseline, automatic]) {
|
|
382
|
+
for (const node of [...test.nodes].reverse()) {
|
|
383
|
+
if (node.nodeType === 38 /* ParseNodeType.Name */ && node.d.value === 'ret') {
|
|
384
|
+
test.program.evaluator.getType(node);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const expected = baseline.analyze();
|
|
390
|
+
assert.deepStrictEqual(automatic.analyze(), expected);
|
|
391
|
+
const before = baseline.program.experimentalOverloadResultController.getStats(baseline.module);
|
|
392
|
+
const after = automatic.program.experimentalOverloadResultController.getStats(automatic.module);
|
|
393
|
+
assert.equal(after.candidateCalls, before.candidateCalls);
|
|
394
|
+
assert.equal(after.candidateTrials, before.candidateTrials);
|
|
395
|
+
assert.equal(after.interruptedSlots, before.interruptedSlots);
|
|
396
|
+
// Reverse reads prepare earlier records; only the two trailing append
|
|
397
|
+
// statements remain fresh checker demands in that order.
|
|
398
|
+
assert.equal(after.checkerHandoffs, queryFirst ? 2 : 6);
|
|
399
|
+
assert.equal(after.ordinaryCalls, before.ordinaryCalls - (queryFirst ? 2 : 14));
|
|
400
|
+
assert.equal(automatic.program.experimentalOverloadResultController.getAutomaticStats().selected, 2);
|
|
401
|
+
for (const name of ['ret', 'alias', 'scalar', 'integer', 'string']) {
|
|
402
|
+
assert.deepStrictEqual(resultSummary(automatic.program.evaluator, automatic.program.evaluator.getTypeResult(automatic.name(name))), resultSummary(baseline.program.evaluator, baseline.program.evaluator.getTypeResult(baseline.name(name))));
|
|
403
|
+
}
|
|
404
|
+
for (let i = 0; i < 3; i++) {
|
|
405
|
+
for (const test of [baseline, automatic]) {
|
|
406
|
+
test.program.experimentalOverloadResultController.evict(test.module);
|
|
407
|
+
test.program.evaluator.evaluateTypesForStatement(test.name('ret').parent);
|
|
408
|
+
test.program.evaluator.getTypeOfExpression(test.name('ret', 'positive', 1), 256 /* EvalFlags.TypeExpression */, {
|
|
409
|
+
expectedType: types_1.AnyType.create(),
|
|
410
|
+
});
|
|
411
|
+
assert.deepStrictEqual(test.analyze(), expected);
|
|
412
|
+
assert.deepStrictEqual(test.info.getFileInfo(test.module).diagnosticSink.fetchAndClear(), []);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
baseline.dispose();
|
|
418
|
+
automatic.dispose();
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
test.each([0, 1, 2, 3, 4, 5, 7, 8, 9, 10])('AutomaticOverloadResult checker handoff preserves candidate cutoffs calls=%s', (consumerCalls) => {
|
|
422
|
+
const baseline = create(fixture, { automatic: true, checkerHandoff: false, consumerCalls }, false, false);
|
|
423
|
+
const automatic = create(fixture, { automatic: true, checkerHandoff: true, consumerCalls }, false, false);
|
|
424
|
+
try {
|
|
425
|
+
assert.deepStrictEqual(automatic.analyze(), baseline.analyze());
|
|
426
|
+
const before = baseline.program.experimentalOverloadResultController.getStats(baseline.module);
|
|
427
|
+
const after = automatic.program.experimentalOverloadResultController.getStats(automatic.module);
|
|
428
|
+
assert.equal(after.candidateCalls, before.candidateCalls);
|
|
429
|
+
assert.equal(after.candidateTrials, before.candidateTrials);
|
|
430
|
+
assert.equal(after.candidateCallCeiling, before.candidateCallCeiling);
|
|
431
|
+
assert.equal(after.interruptedSlots, before.interruptedSlots);
|
|
432
|
+
assert.ok(after.ordinaryCalls <= before.ordinaryCalls);
|
|
433
|
+
assert.equal(after.checkerHandoffs > 0, consumerCalls >= 2);
|
|
434
|
+
assert.deepStrictEqual(automatic.info.getFileInfo(automatic.module).diagnosticSink.fetchAndClear(), []);
|
|
435
|
+
}
|
|
436
|
+
finally {
|
|
437
|
+
baseline.dispose();
|
|
438
|
+
automatic.dispose();
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
test('AutomaticOverloadResult checker handoff preserves checker-only unused-call diagnostics', () => {
|
|
442
|
+
const source = '# pyright: reportUnusedCallResult=error\n' +
|
|
443
|
+
fixture.slice(0, fixture.indexOf('def project')) +
|
|
444
|
+
`
|
|
445
|
+
def check(value: list[Any]):
|
|
446
|
+
ret = choose(value)
|
|
447
|
+
ret.pop().upper()
|
|
448
|
+
`;
|
|
449
|
+
const baseline = create(source, { automatic: true, checkerHandoff: false }, false, false);
|
|
450
|
+
const automatic = create(source, { automatic: true, checkerHandoff: true }, false, false);
|
|
451
|
+
try {
|
|
452
|
+
const expected = baseline.analyze();
|
|
453
|
+
assert.ok(expected.some((d) => d.rule === 'reportUnusedCallResult'));
|
|
454
|
+
assert.deepStrictEqual(automatic.analyze(), expected);
|
|
455
|
+
assert.equal(automatic.program.experimentalOverloadResultController.getStats(automatic.module).checkerHandoffs, 1);
|
|
456
|
+
}
|
|
457
|
+
finally {
|
|
458
|
+
baseline.dispose();
|
|
459
|
+
automatic.dispose();
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
test.each(['replace', 'incomplete', 'in-place'])('AutomaticOverloadResult checker handoff declines changed native result %s', (change) => {
|
|
463
|
+
const factory = TypeEvaluatorModule.createTypeEvaluator;
|
|
464
|
+
let declines = 0;
|
|
465
|
+
const spy = jest.spyOn(TypeEvaluatorModule, 'createTypeEvaluator').mockImplementation((...args) => {
|
|
466
|
+
const controller = args[1].experimentalOverloadResult;
|
|
467
|
+
if (controller) {
|
|
468
|
+
const install = controller.installCheckerHandoff;
|
|
469
|
+
controller.installCheckerHandoff = (handoff) => install((root, expression, result, callback) => {
|
|
470
|
+
const oldErrors = result.typeErrors;
|
|
471
|
+
const hadErrors = Object.prototype.hasOwnProperty.call(result, 'typeErrors');
|
|
472
|
+
if (change === 'in-place') {
|
|
473
|
+
result.typeErrors = true;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
evaluator.setTypeResultForNode(expression, {
|
|
477
|
+
...result,
|
|
478
|
+
...(change === 'incomplete' ? { isIncomplete: true } : {}),
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
const accepted = handoff(root, expression, result, callback);
|
|
483
|
+
assert.equal(accepted, false);
|
|
484
|
+
declines++;
|
|
485
|
+
return accepted;
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
if (hadErrors) {
|
|
489
|
+
result.typeErrors = oldErrors;
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
delete result.typeErrors;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
const evaluator = factory(...args);
|
|
498
|
+
return evaluator;
|
|
499
|
+
});
|
|
500
|
+
const test = create(fixture, { automatic: true, checkerHandoff: true }, false, false);
|
|
501
|
+
try {
|
|
502
|
+
const diagnostics = test.analyze();
|
|
503
|
+
assert.equal(declines, 6);
|
|
504
|
+
assert.equal(test.program.experimentalOverloadResultController.getStats(test.module).checkerHandoffs, 0);
|
|
505
|
+
assert.deepStrictEqual(test.info.getFileInfo(test.module).diagnosticSink.fetchAndClear(), []);
|
|
506
|
+
spy.mockRestore();
|
|
507
|
+
const baseline = create(fixture, { automatic: true, checkerHandoff: false }, false, false);
|
|
508
|
+
try {
|
|
509
|
+
assert.deepStrictEqual(diagnostics, baseline.analyze());
|
|
510
|
+
}
|
|
511
|
+
finally {
|
|
512
|
+
baseline.dispose();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
finally {
|
|
516
|
+
spy.mockRestore();
|
|
517
|
+
test.dispose();
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
test.each(['exception', 'cancel'])('AutomaticOverloadResult live checker transaction unwinds on %s', (fault) => {
|
|
521
|
+
const test = create(fixture, { automatic: true, checkerHandoff: true }, false, false);
|
|
522
|
+
try {
|
|
523
|
+
const controller = test.program.experimentalOverloadResultController;
|
|
524
|
+
const evaluator = test.program.evaluator;
|
|
525
|
+
const member = test.name('append');
|
|
526
|
+
const call = member.parent.parent;
|
|
527
|
+
const root = call.parent;
|
|
528
|
+
assert.equal(root.nodeType, 47 /* ParseNodeType.StatementList */);
|
|
529
|
+
// Activation now follows a real producer request, not check(module).
|
|
530
|
+
evaluator.getType(test.name('ret'));
|
|
531
|
+
const owner = controller.check(test.module);
|
|
532
|
+
const sink = test.info.getFileInfo(test.module).diagnosticSink;
|
|
533
|
+
assert.throws(() => owner.walk(root, () => {
|
|
534
|
+
assert.ok(evaluator.getCachedType(call));
|
|
535
|
+
if (fault === 'cancel') {
|
|
536
|
+
evaluator.runWithCancellationToken(vscode_languageserver_1.CancellationToken.Cancelled, evaluator.checkForCancellation);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
throw new Error('checker interruption');
|
|
540
|
+
}
|
|
541
|
+
}), (error) => fault === 'cancel'
|
|
542
|
+
? cancellationUtils_1.OperationCanceledException.is(error)
|
|
543
|
+
: error instanceof Error && error.message === 'checker interruption');
|
|
544
|
+
assert.strictEqual(test.info.getFileInfo(test.module).diagnosticSink, sink);
|
|
545
|
+
assert.deepStrictEqual(sink.fetchAndClear(), []);
|
|
546
|
+
assert.equal(evaluator.getCachedType(call), undefined);
|
|
547
|
+
assert.ok(!evaluator.isSpeculativeModeInUse(undefined));
|
|
548
|
+
assert.equal(controller.getStats(test.module).checkerHandoffs, 0);
|
|
549
|
+
owner.walk(root, () => evaluator.getType(call));
|
|
550
|
+
assert.equal(controller.getStats(test.module).checkerHandoffs, 1);
|
|
551
|
+
assert.ok(controller.getStats(test.module).retryReservations > 0);
|
|
552
|
+
assert.deepStrictEqual(sink.fetchAndClear(), []);
|
|
553
|
+
owner.dispose();
|
|
554
|
+
}
|
|
555
|
+
finally {
|
|
556
|
+
test.dispose();
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
test('AutomaticOverloadResult real matching preserves the fixed supported graph', () => {
|
|
560
|
+
const fixed = create();
|
|
561
|
+
const automatic = create(fixture, { automatic: true });
|
|
562
|
+
try {
|
|
563
|
+
assert.deepStrictEqual(automatic.analyze(), fixed.analyze());
|
|
564
|
+
assert.ok(automatic.events.some((e) => e.kind === 'selection' && e.selection.reason === 'selected'), JSON.stringify(automatic.events.flatMap((e) => (e.kind === 'selection' ? [e.selection.reason] : []))));
|
|
565
|
+
const evaluator = automatic.program.evaluator;
|
|
566
|
+
assert.equal(evaluator.printType(evaluator.getType(automatic.name('ret'))), 'OverloadResult[list[int], list[str]]');
|
|
567
|
+
}
|
|
568
|
+
finally {
|
|
569
|
+
fixed.dispose();
|
|
570
|
+
automatic.dispose();
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
test.each([false, true])('AutomaticOverloadResult public consumers and reentry queryFirst=%s', (queryFirst) => {
|
|
574
|
+
const test = create(fixture, { automatic: true });
|
|
575
|
+
try {
|
|
576
|
+
const evaluator = test.program.evaluator;
|
|
577
|
+
if (queryFirst) {
|
|
578
|
+
assert.ok((0, types_1.isOverloadResult)(evaluator.getType(test.name('alias'))));
|
|
579
|
+
}
|
|
580
|
+
const diagnostics = test.analyze();
|
|
581
|
+
const alias = test.name('alias');
|
|
582
|
+
const canonical = evaluator.getType(alias);
|
|
583
|
+
assert.ok(canonical && (0, types_1.isOverloadResult)(canonical));
|
|
584
|
+
const position = (offset) => {
|
|
585
|
+
const prefix = fixture.slice(0, offset);
|
|
586
|
+
return { line: prefix.split('\n').length - 1, character: offset - prefix.lastIndexOf('\n') - 1 };
|
|
587
|
+
};
|
|
588
|
+
const hover = new hoverProvider_1.HoverProvider(test.program, test.uri, position(alias.start), 'markdown', vscode_languageserver_1.CancellationToken.None).getHover();
|
|
589
|
+
assert.ok(JSON.stringify(hover).includes('OverloadResult[list[int], list[str]]'));
|
|
590
|
+
for (const [text, labels] of [
|
|
591
|
+
['ret.append("x")', ['append', 'clear']],
|
|
592
|
+
['scalar.upper()', ['upper', 'bit_length']],
|
|
593
|
+
]) {
|
|
594
|
+
const offset = fixture.indexOf(text) + text.indexOf('.') + 1;
|
|
595
|
+
const provider = new completionProvider_1.CompletionProvider(test.program, test.uri, position(offset), { format: 'markdown', snippet: false, lazyEdit: false }, vscode_languageserver_1.CancellationToken.None);
|
|
596
|
+
const completions = provider.getCompletions();
|
|
597
|
+
for (const label of labels) {
|
|
598
|
+
const completion = completions?.items.find((item) => item.label === label);
|
|
599
|
+
assert.ok(completion, `${text}: ${label}`);
|
|
600
|
+
provider.resolveCompletionItem(completion);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
const controller = test.program.experimentalOverloadResultController;
|
|
604
|
+
const selections = test.events.filter((e) => e.kind === 'selection').length;
|
|
605
|
+
const target = test.name('string');
|
|
606
|
+
assert.ok(target.parent?.parent?.nodeType === 3 /* ParseNodeType.Assignment */);
|
|
607
|
+
for (let round = 0; round < 3; round++) {
|
|
608
|
+
controller.evict(test.module);
|
|
609
|
+
evaluator.evaluateTypesForStatement(alias.parent);
|
|
610
|
+
evaluator.evaluateTypesForStatement(target.parent.parent);
|
|
611
|
+
assert.equal(evaluator.printType(evaluator.getCachedType(target)), 'list[str]');
|
|
612
|
+
assert.strictEqual(evaluator.getType(alias), canonical);
|
|
613
|
+
assert.strictEqual(evaluator.getTypeResult(alias).type, canonical);
|
|
614
|
+
assert.strictEqual(evaluator.getTypeOfExpression(alias).type, canonical);
|
|
615
|
+
assert.deepStrictEqual(test.info.getFileInfo(test.module).diagnosticSink.fetchAndClear(), []);
|
|
616
|
+
}
|
|
617
|
+
assert.equal(test.events.filter((e) => e.kind === 'selection').length, selections);
|
|
618
|
+
assert.deepStrictEqual(test.analyze(), diagnostics);
|
|
619
|
+
}
|
|
620
|
+
finally {
|
|
621
|
+
test.dispose();
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
test.each([false, true])('OverloadResultController cold reverse use queries and no-return reentry automatic=%s', (automatic) => {
|
|
625
|
+
const warm = create(fixture, { automatic });
|
|
626
|
+
const cold = create(fixture, { automatic });
|
|
627
|
+
try {
|
|
628
|
+
const expected = warm.analyze();
|
|
629
|
+
const targets = cold.nodes.filter((node) => node.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
630
|
+
['ret', 'alias', 'string', 'integer', 'projected', 'scalar'].includes(node.d.value));
|
|
631
|
+
for (const target of [...targets].reverse()) {
|
|
632
|
+
cold.program.evaluator.getType(target);
|
|
633
|
+
}
|
|
634
|
+
// Queries of declined/ordinary names can emit their ordinary errors
|
|
635
|
+
// before the walk. Compare the full multiset, including duplicates.
|
|
636
|
+
const ordered = (diagnostics) => [...diagnostics].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
637
|
+
assert.deepStrictEqual(ordered(cold.analyze()), ordered(expected));
|
|
638
|
+
for (const target of targets) {
|
|
639
|
+
const other = warm.nodes.find((node) => node.nodeType === 38 /* ParseNodeType.Name */ && node.start === target.start);
|
|
640
|
+
assert.deepStrictEqual(resultSummary(cold.program.evaluator, cold.program.evaluator.getTypeOfExpression(target)), resultSummary(warm.program.evaluator, warm.program.evaluator.getTypeOfExpression(other)));
|
|
641
|
+
}
|
|
642
|
+
assert.deepStrictEqual(cold.info.getFileInfo(cold.module).diagnosticSink.fetchAndClear(), []);
|
|
643
|
+
}
|
|
644
|
+
finally {
|
|
645
|
+
warm.dispose();
|
|
646
|
+
cold.dispose();
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
test('AutomaticOverloadResult exact target and declined controls', () => {
|
|
650
|
+
const source = (0, testUtils_1.readSampleFile)('overloadResultAutomatic1.py');
|
|
651
|
+
const ordinary = create(source, false);
|
|
652
|
+
const enabled = create(source, { automatic: true });
|
|
653
|
+
try {
|
|
654
|
+
// Rejected early-use graphs must remain ordinary even before checking.
|
|
655
|
+
const evaluator = enabled.program.evaluator;
|
|
656
|
+
assert.equal(evaluator.printType(evaluator.getType(enabled.name('ret', 'early', 1))), 'list[int]');
|
|
657
|
+
const expected = ordinary.analyze();
|
|
658
|
+
const actual = enabled.analyze();
|
|
659
|
+
const inFunction = (diagnostics, fn) => {
|
|
660
|
+
const node = (0, parseTreeUtils_1.getEnclosingFunction)(enabled.name('ret', fn));
|
|
661
|
+
const start = source.slice(0, node.start).split('\n').length - 1;
|
|
662
|
+
const end = source.slice(0, node.start + node.length).split('\n').length - 1;
|
|
663
|
+
return diagnostics.filter((d) => d.range.start.line >= start && d.range.start.line <= end);
|
|
664
|
+
};
|
|
665
|
+
for (const fn of [
|
|
666
|
+
'prefix',
|
|
667
|
+
'equal_returns',
|
|
668
|
+
'default_argument',
|
|
669
|
+
'type_variable',
|
|
670
|
+
'unsupported_proof',
|
|
671
|
+
'one_mapping',
|
|
672
|
+
'concrete',
|
|
673
|
+
'unknown',
|
|
674
|
+
'any_argument',
|
|
675
|
+
'union_argument',
|
|
676
|
+
'keywords',
|
|
677
|
+
'unpack',
|
|
678
|
+
'receiver',
|
|
679
|
+
'early',
|
|
680
|
+
]) {
|
|
681
|
+
assert.deepStrictEqual(inFunction(actual, fn), inFunction(expected, fn), fn);
|
|
682
|
+
assert.deepStrictEqual(resultSummary(evaluator, evaluator.getTypeOfExpression(enabled.name('ret', fn))), resultSummary(ordinary.program.evaluator, ordinary.program.evaluator.getTypeOfExpression(ordinary.name('ret', fn))), fn);
|
|
683
|
+
}
|
|
684
|
+
for (const [fn, type] of [
|
|
685
|
+
['original', 'OverloadResult[list[int], list[str]]'],
|
|
686
|
+
['nested_invariant', 'OverloadResult[list[list[int]], list[list[str]]]'],
|
|
687
|
+
['custom_nominal', 'OverloadResult[Invariant[int], Invariant[str]]'],
|
|
688
|
+
['independent', 'OverloadResult[list[int], list[str]]'],
|
|
689
|
+
]) {
|
|
690
|
+
assert.equal(evaluator.printType(evaluator.getType(enabled.name('ret', fn))), type, fn);
|
|
691
|
+
}
|
|
692
|
+
const selections = enabled.events.flatMap((e) => e.kind === 'selection' || e.kind === 'activationProbe' ? [e] : []);
|
|
693
|
+
const prefix = selections.find((e) => (0, parseTreeUtils_1.getEnclosingFunction)(e.node)?.d.name.d.value === 'prefix');
|
|
694
|
+
assert.equal(prefix.selection.reason, 'unsupported-return-shape');
|
|
695
|
+
assert.deepStrictEqual(prefix.selection.candidates, []);
|
|
696
|
+
const pair = selections.find((e) => (0, parseTreeUtils_1.getEnclosingFunction)(e.node)?.d.name.d.value === 'original');
|
|
697
|
+
assert.deepStrictEqual(pair.selection.coverage, ['not-covered', 'not-covered']);
|
|
698
|
+
assert.equal(selections.find((e) => (0, parseTreeUtils_1.getEnclosingFunction)(e.node)?.d.name.d.value === 'equal_returns').selection.reason, 'equivalent-or-single-return');
|
|
699
|
+
const original = inFunction(actual, 'original');
|
|
700
|
+
assert.deepStrictEqual(original.filter((d) => d.rule === 'reportInvalidTypeForm'), inFunction(expected, 'original').filter((d) => d.rule === 'reportInvalidTypeForm'));
|
|
701
|
+
assert.equal(original.filter((d) => d.rule === 'reportAssertTypeFailure').length, 2);
|
|
702
|
+
assert.ok(original.some((d) => d.message.includes('OverloadResult[list[int], list[str]]')));
|
|
703
|
+
for (const text of ['ret.append(3.14)', 'ret.nonexistent()', 'invalid: list[bytes]', 'unrelated: int']) {
|
|
704
|
+
const line = source.slice(0, source.indexOf(text)).split('\n').length - 1;
|
|
705
|
+
assert.deepStrictEqual(actual.filter((d) => d.range.start.line === line), expected.filter((d) => d.range.start.line === line), text);
|
|
706
|
+
}
|
|
707
|
+
assert.deepStrictEqual(enabled.info.getFileInfo(enabled.module).diagnosticSink.fetchAndClear(), []);
|
|
708
|
+
}
|
|
709
|
+
finally {
|
|
710
|
+
ordinary.dispose();
|
|
711
|
+
enabled.dispose();
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
test('AutomaticOverloadResult re-enables the explicit-off internal Program control without injected seeds', () => {
|
|
715
|
+
const test = create((0, testUtils_1.readSampleFile)('overloadResultAutomatic1.py'), false);
|
|
716
|
+
try {
|
|
717
|
+
test.config.experimentalOverloadResults = true;
|
|
718
|
+
test.program.setConfigOptions(test.config);
|
|
719
|
+
test.analyze();
|
|
720
|
+
const controller = test.program.experimentalOverloadResultController;
|
|
721
|
+
assert.ok((0, types_1.isOverloadResult)(test.program.evaluator.getType(test.name('ret', 'original'))));
|
|
722
|
+
assert.ok(controller.getAutomaticStats().selected > 0);
|
|
723
|
+
}
|
|
724
|
+
finally {
|
|
725
|
+
test.dispose();
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
test('AutomaticOverloadResult omitted default preserves reviewed diagnostics and metadata without handoffs', () => {
|
|
729
|
+
const source = (0, testUtils_1.readSampleFile)('overloadResultAutomatic1.py');
|
|
730
|
+
const normal = create(source, false, 'default', false);
|
|
731
|
+
const reviewed = create(source, { automatic: true, checkerHandoff: false }, false, false);
|
|
732
|
+
try {
|
|
733
|
+
assert.strictEqual(normal.config.experimentalOverloadResults, true);
|
|
734
|
+
assert.deepStrictEqual(normal.analyze(), reviewed.analyze());
|
|
735
|
+
for (const fn of ['original', 'nested_invariant', 'custom_nominal', 'concrete', 'unknown', 'early']) {
|
|
736
|
+
const actual = normal.program.evaluator;
|
|
737
|
+
const expected = reviewed.program.evaluator;
|
|
738
|
+
assert.deepStrictEqual(resultSummary(actual, actual.getTypeOfExpression(normal.name('ret', fn))), resultSummary(expected, expected.getTypeOfExpression(reviewed.name('ret', fn))), fn);
|
|
739
|
+
}
|
|
740
|
+
const evaluator = normal.program.evaluator;
|
|
741
|
+
assert.strictEqual(evaluator.printType(evaluator.getType(normal.name('ret', 'original'))), 'OverloadResult[list[int], list[str]]');
|
|
742
|
+
assert.strictEqual(evaluator.printType(evaluator.getType(normal.name('ret', 'concrete'))), 'list[str]');
|
|
743
|
+
const controller = normal.program.experimentalOverloadResultController;
|
|
744
|
+
assert.ok(controller.getAutomaticStats().selected > 0);
|
|
745
|
+
assert.strictEqual(controller.getStats(normal.module).checkerHandoffs, 0);
|
|
746
|
+
assert.deepStrictEqual(normal.info.getFileInfo(normal.module).diagnosticSink.fetchAndClear(), []);
|
|
747
|
+
}
|
|
748
|
+
finally {
|
|
749
|
+
normal.dispose();
|
|
750
|
+
reviewed.dispose();
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
test.each([
|
|
754
|
+
{ name: 'omitted configuration', config: 'default', options: false, automatic: true, carrier: true },
|
|
755
|
+
{ name: 'explicit internal on', config: true, options: false, automatic: true, carrier: true },
|
|
756
|
+
{ name: 'explicit internal off', config: false, options: false, automatic: false, carrier: false },
|
|
757
|
+
{
|
|
758
|
+
name: 'injected automatic overrides internal off without implying handoff',
|
|
759
|
+
config: false,
|
|
760
|
+
options: { automatic: true },
|
|
761
|
+
automatic: true,
|
|
762
|
+
carrier: true,
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
name: 'injected fixed seeds override automatic default',
|
|
766
|
+
config: 'default',
|
|
767
|
+
options: { automatic: false },
|
|
768
|
+
automatic: false,
|
|
769
|
+
carrier: true,
|
|
770
|
+
},
|
|
771
|
+
])('AutomaticOverloadResult option precedence: $name', ({ config, options, automatic, carrier }) => {
|
|
772
|
+
const target = create(fixture, options, config, false);
|
|
773
|
+
try {
|
|
774
|
+
target.analyze();
|
|
775
|
+
const evaluator = target.program.evaluator;
|
|
776
|
+
assert.strictEqual((0, types_1.isOverloadResult)(evaluator.getType(target.name('ret'))), carrier);
|
|
777
|
+
if (!carrier) {
|
|
778
|
+
assert.strictEqual(evaluator.printType(evaluator.getType(target.name('ret'))), 'list[int]');
|
|
779
|
+
assert.strictEqual(target.program.experimentalOverloadResultController, undefined);
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
const controller = target.program.experimentalOverloadResultController;
|
|
783
|
+
assert.strictEqual(controller.getAutomaticStats().selected > 0, automatic);
|
|
784
|
+
assert.strictEqual(controller.getStats(target.module).checkerHandoffs, 0);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
finally {
|
|
788
|
+
target.dispose();
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
test('AutomaticOverloadResult explicit test-only checking controller overrides the automatic default', () => {
|
|
792
|
+
let expressionFactories = 0;
|
|
793
|
+
const target = create(fixture, false, 'default', false, {
|
|
794
|
+
expression: () => {
|
|
795
|
+
expressionFactories++;
|
|
796
|
+
return undefined;
|
|
797
|
+
},
|
|
798
|
+
check: () => undefined,
|
|
799
|
+
});
|
|
800
|
+
try {
|
|
801
|
+
target.analyze();
|
|
802
|
+
assert.ok(expressionFactories > 0);
|
|
803
|
+
assert.strictEqual(target.config.experimentalOverloadResults, true);
|
|
804
|
+
assert.strictEqual(target.program.experimentalOverloadResultController, undefined);
|
|
805
|
+
const evaluator = target.program.evaluator;
|
|
806
|
+
assert.strictEqual(evaluator.printType(evaluator.getType(target.name('ret'))), 'list[int]');
|
|
807
|
+
}
|
|
808
|
+
finally {
|
|
809
|
+
target.dispose();
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
test.each(['check-first', 'value-first', 'use-first'])('AutomaticOverloadResult gradual returns preserve exact ordinary errors: %s', (order) => {
|
|
813
|
+
const source = (0, testUtils_1.readSampleFile)('overloadResultDynamicReturns1.py');
|
|
814
|
+
const ordinary = create(source, false);
|
|
815
|
+
const automatic = create(source, { automatic: true, checkerHandoff: false });
|
|
816
|
+
const expected = [
|
|
817
|
+
{
|
|
818
|
+
category: 0 /* DiagnosticCategory.Error */,
|
|
819
|
+
rule: 'reportAttributeAccessIssue',
|
|
820
|
+
message: 'Cannot access attribute "missing_member" for class "list[int]"\n' +
|
|
821
|
+
'\u00a0\u00a0Attribute "missing_member" is unknown',
|
|
822
|
+
range: { start: { line: 13, character: 8 }, end: { line: 13, character: 22 } },
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
category: 0 /* DiagnosticCategory.Error */,
|
|
826
|
+
rule: 'reportAttributeAccessIssue',
|
|
827
|
+
message: 'Cannot access attribute "missing_member" for class "int"\n' +
|
|
828
|
+
'\u00a0\u00a0Attribute "missing_member" is unknown',
|
|
829
|
+
range: { start: { line: 26, character: 14 }, end: { line: 26, character: 28 } },
|
|
830
|
+
},
|
|
831
|
+
];
|
|
832
|
+
const uses = (test) => test.nodes.filter((node) => node.nodeType === 9 /* ParseNodeType.Call */ &&
|
|
833
|
+
node.d.leftExpr.nodeType === 35 /* ParseNodeType.MemberAccess */ &&
|
|
834
|
+
node.d.leftExpr.d.member.d.value === 'missing_member');
|
|
835
|
+
try {
|
|
836
|
+
for (const test of [ordinary, automatic]) {
|
|
837
|
+
if (order === 'value-first') {
|
|
838
|
+
for (const fn of ['root_dynamic_return', 'nested_dynamic_return']) {
|
|
839
|
+
const type = test.program.evaluator.getType(test.name('ret', fn));
|
|
840
|
+
assert.ok(!(0, types_1.isOverloadResult)(type));
|
|
841
|
+
assert.equal(test.program.evaluator.printType(type), 'list[int]');
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
else if (order === 'use-first') {
|
|
845
|
+
uses(test).forEach((node) => test.program.evaluator.getTypeOfExpression(node));
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
assert.deepStrictEqual(ordinary.analyze(), expected);
|
|
849
|
+
assert.deepStrictEqual(automatic.analyze(), expected);
|
|
850
|
+
const evaluator = automatic.program.evaluator;
|
|
851
|
+
const controller = automatic.program.experimentalOverloadResultController;
|
|
852
|
+
assert.equal(controller.getAutomaticStats().selected, 0);
|
|
853
|
+
const rejected = automatic.events.filter((e) => e.kind === 'activationProbe' && e.selection.reason === 'unsupported-return-shape');
|
|
854
|
+
assert.equal(new Set(rejected.map((e) => e.kind === 'activationProbe' && e.node)).size, 2);
|
|
855
|
+
assert.equal(controller.getAutomaticStats().selections, 0);
|
|
856
|
+
assert.equal(controller.getStats(automatic.module), undefined);
|
|
857
|
+
const selections = controller.getAutomaticStats().selections;
|
|
858
|
+
for (let round = 0; round < 3; round++) {
|
|
859
|
+
controller.evict(automatic.module);
|
|
860
|
+
for (const fn of ['root_dynamic_return', 'nested_dynamic_return']) {
|
|
861
|
+
const target = automatic.name('ret', fn);
|
|
862
|
+
evaluator.evaluateTypesForStatement(target.parent);
|
|
863
|
+
assert.equal(evaluator.printType(evaluator.getCachedType(target)), 'list[int]');
|
|
864
|
+
assert.deepStrictEqual(resultSummary(evaluator, evaluator.getTypeOfExpression(target)), resultSummary(ordinary.program.evaluator, ordinary.program.evaluator.getTypeOfExpression(ordinary.name('ret', fn))));
|
|
865
|
+
assert.ok(!(0, types_1.isOverloadResult)(evaluator.getType(target)));
|
|
866
|
+
assert.ok(!(0, types_1.isOverloadResult)(evaluator.getTypeResult(target).type));
|
|
867
|
+
}
|
|
868
|
+
uses(automatic).forEach((node, index) => assert.deepStrictEqual(resultSummary(evaluator, evaluator.getTypeOfExpression(node)), resultSummary(ordinary.program.evaluator, ordinary.program.evaluator.getTypeOfExpression(uses(ordinary)[index]))));
|
|
869
|
+
assert.deepStrictEqual(automatic.info.getFileInfo(automatic.module).diagnosticSink.fetchAndClear(), []);
|
|
870
|
+
assert.deepStrictEqual(automatic.analyze(), expected);
|
|
871
|
+
assert.equal(controller.getAutomaticStats().selections, selections);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
finally {
|
|
875
|
+
ordinary.dispose();
|
|
876
|
+
automatic.dispose();
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
test.each(['int', 'str'])('AutomaticOverloadResult static covering prefix retains %s before object', (first) => {
|
|
880
|
+
const second = first === 'int' ? 'str' : 'int';
|
|
881
|
+
const source = `from typing import Any, overload
|
|
882
|
+
@overload
|
|
883
|
+
def choose(value: list[${first}]) -> list[${first}]: ...
|
|
884
|
+
@overload
|
|
885
|
+
def choose(value: list[Any]) -> object: ...
|
|
886
|
+
@overload
|
|
887
|
+
def choose(value: list[${second}]) -> list[${second}]: ...
|
|
888
|
+
def choose(value: Any) -> Any:
|
|
889
|
+
return value
|
|
890
|
+
def positive(value: list[Any]):
|
|
891
|
+
ret = choose(value)
|
|
892
|
+
ret.clear()
|
|
893
|
+
ret.append(3.14)
|
|
894
|
+
`;
|
|
895
|
+
const ordinary = create(source, false);
|
|
896
|
+
const automatic = create(source, { automatic: true });
|
|
897
|
+
try {
|
|
898
|
+
assert.deepStrictEqual(automatic.analyze(), ordinary.analyze());
|
|
899
|
+
const evaluator = automatic.program.evaluator;
|
|
900
|
+
assert.equal(evaluator.printType(evaluator.getType(automatic.name('ret'))), `OverloadResult[list[${first}], object]`);
|
|
901
|
+
const selection = automatic.events.find((e) => e.kind === 'selection');
|
|
902
|
+
assert.ok(selection?.kind === 'selection');
|
|
903
|
+
assert.deepStrictEqual(selection.selection.coverage, ['not-covered', 'covered']);
|
|
904
|
+
assert.deepStrictEqual(selection.selection.matches, [0, 1]);
|
|
905
|
+
assert.equal(selection.selection.candidates.length, 2);
|
|
906
|
+
}
|
|
907
|
+
finally {
|
|
908
|
+
ordinary.dispose();
|
|
909
|
+
automatic.dispose();
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
test.each([
|
|
913
|
+
'Any',
|
|
914
|
+
'list[Any]',
|
|
915
|
+
'list[list[Any]]',
|
|
916
|
+
'dict[str, list[Any]]',
|
|
917
|
+
'GradualAlias',
|
|
918
|
+
'list',
|
|
919
|
+
'list[list]',
|
|
920
|
+
'list[int] | Any',
|
|
921
|
+
'tuple[Any]',
|
|
922
|
+
'Callable[..., Any]',
|
|
923
|
+
])('AutomaticOverloadResult rejects gradual or unsupported return %s before use-first queries', (returnType) => {
|
|
924
|
+
const source = `from typing import Any, Callable, overload
|
|
925
|
+
GradualAlias = dict[str, list[Any]]
|
|
926
|
+
@overload
|
|
927
|
+
def choose(value: list[int]) -> list[int]: ...
|
|
928
|
+
@overload
|
|
929
|
+
def choose(value: list[str]) -> ${returnType}: ...
|
|
930
|
+
def choose(value: Any) -> Any:
|
|
931
|
+
return value
|
|
932
|
+
def positive(value: list[Any]):
|
|
933
|
+
ret = choose(value)
|
|
934
|
+
ret.missing_member()
|
|
935
|
+
`;
|
|
936
|
+
const ordinary = create(source, false);
|
|
937
|
+
const automatic = create(source, false, true);
|
|
938
|
+
try {
|
|
939
|
+
const evaluator = automatic.program.evaluator;
|
|
940
|
+
const use = automatic.nodes.find((node) => node.nodeType === 9 /* ParseNodeType.Call */ &&
|
|
941
|
+
node.d.leftExpr.nodeType === 35 /* ParseNodeType.MemberAccess */ &&
|
|
942
|
+
node.d.leftExpr.d.member.d.value === 'missing_member');
|
|
943
|
+
evaluator.getTypeOfExpression(use);
|
|
944
|
+
assert.deepStrictEqual(automatic.analyze(), ordinary.analyze());
|
|
945
|
+
assert.ok(automatic.analyze().some((d) => d.rule === 'reportAttributeAccessIssue'));
|
|
946
|
+
assert.equal(automatic.program.experimentalOverloadResultController.getAutomaticStats().selected, 0);
|
|
947
|
+
assert.equal(evaluator.printType(evaluator.getType(automatic.name('ret'))), 'list[int]');
|
|
948
|
+
assert.deepStrictEqual(resultSummary(evaluator, evaluator.getTypeOfExpression(automatic.name('ret'))), resultSummary(ordinary.program.evaluator, ordinary.program.evaluator.getTypeOfExpression(ordinary.name('ret'))));
|
|
949
|
+
assert.deepStrictEqual(automatic.info.getFileInfo(automatic.module).diagnosticSink.fetchAndClear(), []);
|
|
950
|
+
}
|
|
951
|
+
finally {
|
|
952
|
+
ordinary.dispose();
|
|
953
|
+
automatic.dispose();
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
test('AutomaticOverloadResult original implicit reveal reports the actual canonical value', () => {
|
|
957
|
+
// A reveal compares the whole carrier, not one successful alternative.
|
|
958
|
+
const source = (0, testUtils_1.readSampleFile)('overloadCall11.py').replace('expected_text="OverloadResult[list[int], list[str]]"', 'expected_text="list[int]"');
|
|
959
|
+
const ordinary = create(source, false);
|
|
960
|
+
const automatic = create(source, { automatic: true });
|
|
961
|
+
try {
|
|
962
|
+
assert.equal(ordinary.analyze().filter((d) => d.category === 0 /* DiagnosticCategory.Error */).length, 2);
|
|
963
|
+
const actual = automatic.analyze();
|
|
964
|
+
const evaluator = automatic.program.evaluator;
|
|
965
|
+
assert.equal(evaluator.printType(evaluator.getType(automatic.name('result', 'check'))), 'OverloadResult[list[int], list[str]]');
|
|
966
|
+
assert.ok(actual.some((d) => d.message.includes('OverloadResult[list[int], list[str]]')));
|
|
967
|
+
assert.equal(actual.filter((d) => d.category === 0 /* DiagnosticCategory.Error */).length, 1);
|
|
968
|
+
assert.ok(!actual.some((d) => d.rule === 'reportAssignmentType' || d.rule === 'reportArgumentType'));
|
|
969
|
+
}
|
|
970
|
+
finally {
|
|
971
|
+
ordinary.dispose();
|
|
972
|
+
automatic.dispose();
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
test.each([false, true])('AutomaticOverloadResult public subnode queries queryFirst=%s', (queryFirst) => {
|
|
976
|
+
const source = `from typing import Any, overload, reveal_type
|
|
977
|
+
@overload
|
|
978
|
+
def choose(value: list[int]) -> list[int]: ...
|
|
979
|
+
@overload
|
|
980
|
+
def choose(value: list[str]) -> list[str]: ...
|
|
981
|
+
def choose(value: Any) -> Any:
|
|
982
|
+
return value
|
|
983
|
+
def ordinary(value: list[int]):
|
|
984
|
+
ret = choose(value)
|
|
985
|
+
reveal_type(ret)
|
|
986
|
+
ret.append(1)
|
|
987
|
+
def active(value: list[Any]):
|
|
988
|
+
ret = choose(value)
|
|
989
|
+
reveal_type(ret)
|
|
990
|
+
ret.append(1)
|
|
991
|
+
`;
|
|
992
|
+
const ordinary = create(source, false);
|
|
993
|
+
const automatic = create(source, false, true);
|
|
994
|
+
try {
|
|
995
|
+
if (!queryFirst) {
|
|
996
|
+
ordinary.analyze();
|
|
997
|
+
automatic.analyze();
|
|
998
|
+
}
|
|
999
|
+
const evaluator = automatic.program.evaluator;
|
|
1000
|
+
const readerArgument = automatic.name('ret', 'ordinary', 1);
|
|
1001
|
+
const baselineArgument = ordinary.name('ret', 'ordinary', 1);
|
|
1002
|
+
const readerResult = evaluator.getTypeResult(readerArgument);
|
|
1003
|
+
const baselineResult = ordinary.program.evaluator.getTypeResult(baselineArgument);
|
|
1004
|
+
assert.ok(readerResult && baselineResult);
|
|
1005
|
+
assert.deepStrictEqual(resultSummary(evaluator, readerResult), resultSummary(ordinary.program.evaluator, baselineResult));
|
|
1006
|
+
assert.equal(evaluator.printType(evaluator.getType(readerArgument)), 'list[int]');
|
|
1007
|
+
const restored = automatic.program.experimentalOverloadResultController.getStats(automatic.module);
|
|
1008
|
+
for (let i = 0; i < 3; i++) {
|
|
1009
|
+
assert.deepStrictEqual(resultSummary(evaluator, evaluator.getTypeResult(readerArgument)), resultSummary(ordinary.program.evaluator, baselineResult));
|
|
1010
|
+
assert.equal(evaluator.printType(evaluator.getType(readerArgument)), 'list[int]');
|
|
1011
|
+
}
|
|
1012
|
+
assert.deepStrictEqual(automatic.program.experimentalOverloadResultController.getStats(automatic.module), restored);
|
|
1013
|
+
const member = automatic.name('append', 'active');
|
|
1014
|
+
const parent = member.parent;
|
|
1015
|
+
assert.ok(parent.nodeType === 35 /* ParseNodeType.MemberAccess */);
|
|
1016
|
+
assert.strictEqual(evaluator.getType(member), evaluator.getType(parent));
|
|
1017
|
+
assert.strictEqual(evaluator.getTypeResult(member), evaluator.getTypeResult(parent));
|
|
1018
|
+
assert.ok((0, types_1.isOverloadResult)(evaluator.getType(member)));
|
|
1019
|
+
const position = (node) => {
|
|
1020
|
+
const before = source.slice(0, node.start);
|
|
1021
|
+
return { line: before.split('\n').length - 1, character: node.start - before.lastIndexOf('\n') - 1 };
|
|
1022
|
+
};
|
|
1023
|
+
for (const [node, expected] of [
|
|
1024
|
+
[readerArgument, 'list[int]'],
|
|
1025
|
+
[member, 'OverloadResult'],
|
|
1026
|
+
]) {
|
|
1027
|
+
const hover = new hoverProvider_1.HoverProvider(automatic.program, automatic.uri, position(node), 'markdown', vscode_languageserver_1.CancellationToken.None).getHover();
|
|
1028
|
+
assert.ok(JSON.stringify(hover).includes(expected));
|
|
1029
|
+
assert.ok(!JSON.stringify(hover).includes('Unknown'));
|
|
1030
|
+
}
|
|
1031
|
+
const expectedDiagnostics = automatic.analyze();
|
|
1032
|
+
for (let i = 0; i < 3; i++) {
|
|
1033
|
+
automatic.program.experimentalOverloadResultController.evict(automatic.module);
|
|
1034
|
+
const evicted = automatic.program.experimentalOverloadResultController.getStats(automatic.module);
|
|
1035
|
+
assert.equal(evaluator.printType(evaluator.getType(readerArgument)), 'list[int]');
|
|
1036
|
+
assert.equal(automatic.program.experimentalOverloadResultController.getStats(automatic.module).ordinaryRequests, evicted.ordinaryRequests);
|
|
1037
|
+
evaluator.evaluateTypesForStatement(automatic.name('ret', 'ordinary').parent);
|
|
1038
|
+
evaluator.evaluateTypesForStatement(automatic.name('ret', 'active').parent);
|
|
1039
|
+
assert.equal(evaluator.printType(evaluator.getType(readerArgument)), 'list[int]');
|
|
1040
|
+
assert.strictEqual(evaluator.getType(member), evaluator.getType(parent));
|
|
1041
|
+
assert.deepStrictEqual(automatic.analyze(), expectedDiagnostics);
|
|
1042
|
+
assert.deepStrictEqual(automatic.info.getFileInfo(automatic.module).diagnosticSink.fetchAndClear(), []);
|
|
1043
|
+
}
|
|
1044
|
+
assert.equal(automatic.program.experimentalOverloadResultController.getAutomaticStats().selected, 1);
|
|
1045
|
+
}
|
|
1046
|
+
finally {
|
|
1047
|
+
ordinary.dispose();
|
|
1048
|
+
automatic.dispose();
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
test.each([false, true])('AutomaticOverloadResult activation preserves producer diagnostics queryFirst=%s', (queryFirst) => {
|
|
1052
|
+
const source = fixture.slice(0, fixture.indexOf('def project')) +
|
|
1053
|
+
`def wrong(value: list[Any]):
|
|
1054
|
+
ret: list[bytes] = choose(value)
|
|
1055
|
+
ret.append(b"ok")
|
|
1056
|
+
def invalid(value: list[Any]):
|
|
1057
|
+
ret: list[int()] = choose(value)
|
|
1058
|
+
ret.append("ok")
|
|
1059
|
+
def good(value: list[Any]):
|
|
1060
|
+
ret: list[int] = choose(value)
|
|
1061
|
+
ret.append("bad")
|
|
1062
|
+
`;
|
|
1063
|
+
const ordinary = create(source, false);
|
|
1064
|
+
const automatic = create(source, { automatic: true, checkerHandoff: false });
|
|
1065
|
+
try {
|
|
1066
|
+
if (!queryFirst) {
|
|
1067
|
+
ordinary.analyze();
|
|
1068
|
+
automatic.analyze();
|
|
1069
|
+
}
|
|
1070
|
+
for (const fn of ['good', 'invalid', 'wrong']) {
|
|
1071
|
+
assert.deepStrictEqual(resultSummary(automatic.program.evaluator, automatic.program.evaluator.getTypeResult(automatic.name('ret', fn, 1))), resultSummary(ordinary.program.evaluator, ordinary.program.evaluator.getTypeResult(ordinary.name('ret', fn, 1))));
|
|
1072
|
+
}
|
|
1073
|
+
const earlyErrors = automatic.info.getFileInfo(automatic.module).diagnosticSink.getErrors();
|
|
1074
|
+
assert.equal(earlyErrors.length, queryFirst ? 1 : 0);
|
|
1075
|
+
if (queryFirst) {
|
|
1076
|
+
// The annotation is validated before the call in both lifecycles.
|
|
1077
|
+
const prefix = source.slice(0, source.indexOf('int()'));
|
|
1078
|
+
const line = prefix.split('\n').length - 1;
|
|
1079
|
+
const character = prefix.length - prefix.lastIndexOf('\n') - 1;
|
|
1080
|
+
assert.equal(earlyErrors[0].getRule(), 'reportInvalidTypeForm');
|
|
1081
|
+
assert.equal(earlyErrors[0].message, 'Call expression not allowed in type expression');
|
|
1082
|
+
assert.deepStrictEqual(earlyErrors[0].range, {
|
|
1083
|
+
start: { line, character },
|
|
1084
|
+
end: { line, character: character + 5 },
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
assert.deepStrictEqual(automatic.analyze(), ordinary.analyze());
|
|
1088
|
+
const errors = automatic.analyze().filter((d) => d.category === 0 /* DiagnosticCategory.Error */);
|
|
1089
|
+
assert.deepStrictEqual(errors.map((d) => d.rule).sort(), [
|
|
1090
|
+
'reportArgumentType',
|
|
1091
|
+
'reportAssignmentType',
|
|
1092
|
+
'reportInvalidTypeForm',
|
|
1093
|
+
]);
|
|
1094
|
+
}
|
|
1095
|
+
finally {
|
|
1096
|
+
ordinary.dispose();
|
|
1097
|
+
automatic.dispose();
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
test.each(['analysis-first', 'later-producer-first', 'later-consumer-first', 'later-function-first'])('AutomaticOverloadResult independent activation order and replacement: %s', (order) => {
|
|
1101
|
+
const original = (0, testUtils_1.readSampleFile)('overloadResultActivationOrder1.py');
|
|
1102
|
+
const edited = original.replace('first = choose(value)', 'first = [b"edited"]');
|
|
1103
|
+
const automatic = create(original, { automatic: true, checkerHandoff: false });
|
|
1104
|
+
const reference = create(original, { automatic: true, checkerHandoff: false });
|
|
1105
|
+
const ordinary = create(original, false);
|
|
1106
|
+
const targets = [
|
|
1107
|
+
['first', 'OverloadResult[list[int], list[str]]'],
|
|
1108
|
+
['first_alias', 'OverloadResult[list[int], list[str]]'],
|
|
1109
|
+
['first_projected', 'OverloadResult[Sequence[int], Sequence[str]]'],
|
|
1110
|
+
['first_scalar', 'OverloadResult[int, str]'],
|
|
1111
|
+
['first_int', 'list[int]'],
|
|
1112
|
+
['first_str', 'list[str]'],
|
|
1113
|
+
['second', 'OverloadResult[list[bytes], list[float]]'],
|
|
1114
|
+
['second_alias', 'OverloadResult[list[bytes], list[float]]'],
|
|
1115
|
+
['second_projected', 'OverloadResult[Sequence[bytes], Sequence[float]]'],
|
|
1116
|
+
['second_scalar', 'OverloadResult[bytes, float]'],
|
|
1117
|
+
['second_bytes', 'list[bytes]'],
|
|
1118
|
+
['second_float', 'list[float]'],
|
|
1119
|
+
['remote', 'OverloadResult[list[bytes], list[float]]'],
|
|
1120
|
+
['bad_producer', 'list[bytes]'],
|
|
1121
|
+
['ordinary', 'list[int]'],
|
|
1122
|
+
['escaped', 'list[int]'],
|
|
1123
|
+
];
|
|
1124
|
+
const current = (test) => {
|
|
1125
|
+
test.program.getBoundSourceFile(test.uri);
|
|
1126
|
+
const module = test.program.getParseResults(test.uri).parserOutput.parseTree;
|
|
1127
|
+
const nodes = new Nodes();
|
|
1128
|
+
nodes.walk(module);
|
|
1129
|
+
const name = (value) => {
|
|
1130
|
+
const fn = value === 'remote' ? 'independent' : value === 'escaped' ? 'declined' : 'positive';
|
|
1131
|
+
const node = nodes.nodes.find((n) => n.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
1132
|
+
n.d.value === value &&
|
|
1133
|
+
(0, parseTreeUtils_1.getEnclosingFunction)(n)?.d.name.d.value === fn);
|
|
1134
|
+
assert.ok(node, value);
|
|
1135
|
+
return node;
|
|
1136
|
+
};
|
|
1137
|
+
return { module, name, evaluator: test.program.evaluator };
|
|
1138
|
+
};
|
|
1139
|
+
const ordered = (diagnostics) => [...diagnostics].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
1140
|
+
const fullDiagnostics = (test) => test.program
|
|
1141
|
+
.getSourceFile(test.uri)
|
|
1142
|
+
.getDiagnostics(test.config)
|
|
1143
|
+
.map((d) => d.toJsonObj());
|
|
1144
|
+
const sink = (test, module) => test.info
|
|
1145
|
+
.getFileInfo(module)
|
|
1146
|
+
.diagnosticSink.getErrors()
|
|
1147
|
+
.map((d) => d.toJsonObj());
|
|
1148
|
+
try {
|
|
1149
|
+
for (const [generation, source] of [original, edited, original].entries()) {
|
|
1150
|
+
const previous = current(automatic);
|
|
1151
|
+
const previousType = generation ? previous.evaluator.getType(previous.name('second')) : undefined;
|
|
1152
|
+
const eventStart = automatic.events.length;
|
|
1153
|
+
const activationsBefore = automatic.program.experimentalOverloadResultController.getAutomaticStats().activations;
|
|
1154
|
+
if (generation) {
|
|
1155
|
+
for (const test of [automatic, reference, ordinary]) {
|
|
1156
|
+
test.program.setFileOpened(test.uri, generation + 1, source);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
const actual = current(automatic);
|
|
1160
|
+
const expected = current(reference);
|
|
1161
|
+
const native = current(ordinary);
|
|
1162
|
+
const nativeDiagnostics = ordinary.analyze();
|
|
1163
|
+
const referenceDiagnostics = reference.analyze();
|
|
1164
|
+
const line = (text) => source.slice(0, source.indexOf(text)).split('\n').length - 1;
|
|
1165
|
+
const failures = [
|
|
1166
|
+
'between: int = "bad"',
|
|
1167
|
+
'first_invalid: list[bytes]',
|
|
1168
|
+
'first.extend([1, "x"])',
|
|
1169
|
+
'second.extend([b"x", 1.0])',
|
|
1170
|
+
'first.append(3.14)',
|
|
1171
|
+
'second.append("neither")',
|
|
1172
|
+
'first.nonexistent()',
|
|
1173
|
+
'second.nonexistent()',
|
|
1174
|
+
'bad_producer: list[bytes]',
|
|
1175
|
+
'ordinary.append("bad")',
|
|
1176
|
+
'escaped.append("not_supported")',
|
|
1177
|
+
]
|
|
1178
|
+
.filter((text) => generation !== 1 || text !== 'first_invalid: list[bytes]')
|
|
1179
|
+
.map(line);
|
|
1180
|
+
// After editing away the first producer, all of its operations
|
|
1181
|
+
// must have precisely their native (non-alternative) behavior.
|
|
1182
|
+
const firstLines = source
|
|
1183
|
+
.split('\n')
|
|
1184
|
+
.flatMap((text, index) => generation === 1 && text.trimStart().startsWith('first') ? [index] : []);
|
|
1185
|
+
const keepDiagnostic = (d) => d.category !== 0 /* DiagnosticCategory.Error */ ||
|
|
1186
|
+
[...failures, ...firstLines].includes(d.range.start.line);
|
|
1187
|
+
const diagnostics = nativeDiagnostics.filter(keepDiagnostic);
|
|
1188
|
+
const fullExpected = fullDiagnostics(ordinary).filter((_, index) => keepDiagnostic(nativeDiagnostics[index]));
|
|
1189
|
+
for (const failure of failures) {
|
|
1190
|
+
const errors = diagnostics.filter((d) => d.category === 0 /* DiagnosticCategory.Error */ && d.range.start.line === failure);
|
|
1191
|
+
assert.equal(errors.length, 1, JSON.stringify({ generation, line: failure, errors }));
|
|
1192
|
+
}
|
|
1193
|
+
assert.deepStrictEqual(ordered(referenceDiagnostics), ordered(diagnostics));
|
|
1194
|
+
assert.deepStrictEqual(ordered(fullDiagnostics(reference)), ordered(fullExpected));
|
|
1195
|
+
if (order === 'analysis-first') {
|
|
1196
|
+
assert.deepStrictEqual(ordered(automatic.analyze()), ordered(diagnostics));
|
|
1197
|
+
}
|
|
1198
|
+
else {
|
|
1199
|
+
const firstQuery = order === 'later-function-first'
|
|
1200
|
+
? 'remote'
|
|
1201
|
+
: order === 'later-consumer-first'
|
|
1202
|
+
? 'second_scalar'
|
|
1203
|
+
: 'second';
|
|
1204
|
+
const value = actual.evaluator.getTypeResult(actual.name(firstQuery));
|
|
1205
|
+
assert.ok((0, types_1.isOverloadResult)(value.type));
|
|
1206
|
+
assert.deepStrictEqual(resultSummary(actual.evaluator, value), resultSummary(expected.evaluator, expected.evaluator.getTypeResult(expected.name(firstQuery))));
|
|
1207
|
+
if (order === 'later-function-first') {
|
|
1208
|
+
const probe = automatic.events.slice(eventStart).find((e) => e.kind === 'activationProbe');
|
|
1209
|
+
assert.ok(probe?.kind === 'activationProbe');
|
|
1210
|
+
assert.equal((0, parseTreeUtils_1.getEnclosingFunction)(probe.node)?.d.name.d.value, 'independent');
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
for (let repeat = 0; repeat < 3; repeat++) {
|
|
1214
|
+
for (const [name, type] of [...targets].reverse()) {
|
|
1215
|
+
const node = actual.name(name);
|
|
1216
|
+
const other = expected.name(name);
|
|
1217
|
+
const query = actual.evaluator.getTypeResult(node);
|
|
1218
|
+
const expectedType = generation === 1 && name.startsWith('first')
|
|
1219
|
+
? native.evaluator.printType(native.evaluator.getType(native.name(name)))
|
|
1220
|
+
: type;
|
|
1221
|
+
assert.equal(actual.evaluator.printType(query.type), expectedType, name);
|
|
1222
|
+
assert.strictEqual(actual.evaluator.getType(node), query.type, name);
|
|
1223
|
+
assert.deepStrictEqual(resultSummary(actual.evaluator, query), resultSummary(expected.evaluator, expected.evaluator.getTypeResult(other)), name);
|
|
1224
|
+
assert.deepStrictEqual(resultSummary(actual.evaluator, actual.evaluator.getTypeOfExpression(node)), resultSummary(expected.evaluator, expected.evaluator.getTypeOfExpression(other)), name);
|
|
1225
|
+
}
|
|
1226
|
+
assert.deepStrictEqual(ordered(automatic.analyze()), ordered(diagnostics));
|
|
1227
|
+
assert.deepStrictEqual(ordered(fullDiagnostics(automatic)), ordered(fullExpected));
|
|
1228
|
+
assert.deepStrictEqual(sink(automatic, actual.module), sink(reference, expected.module));
|
|
1229
|
+
assert.deepStrictEqual(automatic.info.getFileInfo(actual.module).diagnosticSink.fetchAndClear(), []);
|
|
1230
|
+
}
|
|
1231
|
+
for (const name of ['first_scalar', 'second_scalar']) {
|
|
1232
|
+
const node = actual.name(name);
|
|
1233
|
+
const prefix = source.slice(0, node.start);
|
|
1234
|
+
const position = {
|
|
1235
|
+
line: prefix.split('\n').length - 1,
|
|
1236
|
+
character: node.start - prefix.lastIndexOf('\n') - 1,
|
|
1237
|
+
};
|
|
1238
|
+
const hover = (test) => new hoverProvider_1.HoverProvider(test.program, test.uri, position, 'markdown', vscode_languageserver_1.CancellationToken.None).getHover();
|
|
1239
|
+
const actualHover = hover(automatic);
|
|
1240
|
+
assert.deepStrictEqual(actualHover, hover(reference));
|
|
1241
|
+
assert.ok(JSON.stringify(actualHover).includes(actual.evaluator.printType(actual.evaluator.getType(node))));
|
|
1242
|
+
}
|
|
1243
|
+
const events = automatic.events.slice(eventStart);
|
|
1244
|
+
const admission = events.find((e) => e.kind === 'admission');
|
|
1245
|
+
assert.ok(admission?.kind === 'admission' && admission.admission.complete);
|
|
1246
|
+
const controller = automatic.program.experimentalOverloadResultController;
|
|
1247
|
+
const owners = [];
|
|
1248
|
+
for (const event of events) {
|
|
1249
|
+
if (event.kind === 'enter') {
|
|
1250
|
+
owners.push(event);
|
|
1251
|
+
}
|
|
1252
|
+
else if (event.kind === 'trial') {
|
|
1253
|
+
const owner = owners.pop();
|
|
1254
|
+
assert.strictEqual(owner?.root, event.root);
|
|
1255
|
+
assert.equal(owner?.mode, event.mode);
|
|
1256
|
+
}
|
|
1257
|
+
else if (event.kind === 'activationProbe' && owners.length) {
|
|
1258
|
+
assert.equal(owners.length, 1);
|
|
1259
|
+
assert.equal(owners[0].mode, 'baseline');
|
|
1260
|
+
assert.equal(controller.roots.get(owners[0].root).has(event.node.id), false);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
assert.equal(owners.length, 0);
|
|
1264
|
+
for (const name of generation === 1 ? ['second', 'remote'] : ['first', 'second', 'remote']) {
|
|
1265
|
+
const record = admission.admission.definitions.get(actual.name(name));
|
|
1266
|
+
assert.ok(record?.admitted && record.seed);
|
|
1267
|
+
assert.ok(controller.roots.has(record.root));
|
|
1268
|
+
assert.ok(events.some((e) => e.kind === 'selection' &&
|
|
1269
|
+
e.node === record.seed.node &&
|
|
1270
|
+
e.selection.reason === 'selected'));
|
|
1271
|
+
assert.ok(events.some((e) => e.kind === 'value' && e.root === record.root && (0, types_1.isOverloadResult)(e.canonical.type)));
|
|
1272
|
+
}
|
|
1273
|
+
for (const name of ['ordinary', 'escaped']) {
|
|
1274
|
+
const record = admission.admission.definitions.get(actual.name(name));
|
|
1275
|
+
assert.ok(record);
|
|
1276
|
+
assert.equal(controller.roots.has(record.root), false);
|
|
1277
|
+
assert.ok(!(0, types_1.isOverloadResult)(actual.evaluator.getType(actual.name(name))));
|
|
1278
|
+
}
|
|
1279
|
+
const annotated = admission.admission.definitions.get(actual.name('bad_producer'));
|
|
1280
|
+
assert.ok(annotated?.admitted && annotated.annotated && annotated.seed);
|
|
1281
|
+
assert.ok(controller.roots.has(annotated.root));
|
|
1282
|
+
assert.ok(events.some((e) => e.kind === 'activationProbe' &&
|
|
1283
|
+
e.node === annotated.seed.node &&
|
|
1284
|
+
e.selection.reason === 'selected'));
|
|
1285
|
+
assert.ok(events.some((e) => e.kind === 'value' && e.root === annotated.root && !(0, types_1.isOverloadResult)(e.canonical.type)));
|
|
1286
|
+
for (const name of generation === 1 ? ['second'] : ['first', 'second']) {
|
|
1287
|
+
const record = [...admission.admission.records.values()].find((r) => r.statement.start === source.indexOf(`${name}.extend(`));
|
|
1288
|
+
assert.ok(record?.admitted && controller.roots.has(record.root));
|
|
1289
|
+
const trials = events.filter((e) => e.kind === 'trial' && e.root === record.root && e.mode === 'candidate');
|
|
1290
|
+
assert.equal(trials.length, 2);
|
|
1291
|
+
assert.ok(trials.every((e) => e.kind === 'trial' && e.failed && !e.exception));
|
|
1292
|
+
}
|
|
1293
|
+
assert.ok(!events.some((e) => e.kind === 'value' && e.cutoff));
|
|
1294
|
+
assert.equal(controller.hasPendingActivation, false);
|
|
1295
|
+
assert.equal(controller.getAutomaticStats().activations - activationsBefore, generation === 1 ? 3 : 4);
|
|
1296
|
+
assert.deepStrictEqual(automatic.info.getFileInfo(actual.module).diagnosticSink.fetchAndClear(), []);
|
|
1297
|
+
if (generation) {
|
|
1298
|
+
assert.ok(events.some((e) => e.kind === 'retire' && e.module === previous.module));
|
|
1299
|
+
assert.notStrictEqual(actual.evaluator.getType(actual.name('second')), previousType);
|
|
1300
|
+
assert.throws(() => previous.evaluator.getType(previous.name('second')), /Retired overload-result controller|Expired overload-result parse generation/);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
finally {
|
|
1305
|
+
automatic.dispose();
|
|
1306
|
+
reference.dispose();
|
|
1307
|
+
ordinary.dispose();
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
test.each(['getType', 'getTypeResult'])('AutomaticOverloadResult external call speculation leaves activation pending for cached %s', (api) => {
|
|
1311
|
+
const source = fixture.slice(0, fixture.indexOf('def project')) +
|
|
1312
|
+
`def ordinary(value: int):
|
|
1313
|
+
cached = value
|
|
1314
|
+
return cached
|
|
1315
|
+
def positive(value: list[Any]):
|
|
1316
|
+
ret = choose(value)
|
|
1317
|
+
ret.append("ok")
|
|
1318
|
+
`;
|
|
1319
|
+
const test = create(source, { automatic: true, checkerHandoff: false });
|
|
1320
|
+
const reference = create(source, { automatic: true, checkerHandoff: false });
|
|
1321
|
+
try {
|
|
1322
|
+
const evaluator = test.program.evaluator;
|
|
1323
|
+
const controller = test.program.experimentalOverloadResultController;
|
|
1324
|
+
const cached = test.name('cached', 'ordinary', 1);
|
|
1325
|
+
assert.equal(evaluator.printType(evaluator.getType(cached)), 'int');
|
|
1326
|
+
const call = test.nodes.find((n) => n.nodeType === 9 /* ParseNodeType.Call */ &&
|
|
1327
|
+
n.d.leftExpr.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
1328
|
+
n.d.leftExpr.d.value === 'choose');
|
|
1329
|
+
evaluator.useSpeculativeMode(call, () => evaluator.getTypeOfExpression(call));
|
|
1330
|
+
assert.equal(controller.hasPendingActivation, true);
|
|
1331
|
+
assert.equal(controller.roots.size, 0);
|
|
1332
|
+
assert.equal(controller.getAutomaticStats().modules, 0);
|
|
1333
|
+
evaluator[api](cached);
|
|
1334
|
+
assert.equal(controller.hasPendingActivation, false);
|
|
1335
|
+
assert.ok((0, types_1.isOverloadResult)(evaluator.getType(test.name('ret'))));
|
|
1336
|
+
assert.deepStrictEqual(test.analyze(), reference.analyze());
|
|
1337
|
+
assert.deepStrictEqual(resultSummary(evaluator, evaluator.getTypeResult(cached)), resultSummary(reference.program.evaluator, reference.program.evaluator.getTypeResult(reference.name('cached', 'ordinary', 1))));
|
|
1338
|
+
assert.deepStrictEqual(test.info.getFileInfo(test.module).diagnosticSink.fetchAndClear(), []);
|
|
1339
|
+
}
|
|
1340
|
+
finally {
|
|
1341
|
+
test.dispose();
|
|
1342
|
+
reference.dispose();
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
test.each([false, true])('AutomaticOverloadResult concrete producers stay ordinary queryFirst=%s', (queryFirst) => {
|
|
1346
|
+
const source = fixture.slice(0, fixture.indexOf('def project')) +
|
|
1347
|
+
`def ordinary(value: list[int]):
|
|
1348
|
+
ret = choose(value)
|
|
1349
|
+
reveal_type(ret)
|
|
1350
|
+
ret.append("invalid")
|
|
1351
|
+
`;
|
|
1352
|
+
const ordinary = create(source, false);
|
|
1353
|
+
const automatic = create(source, { automatic: true, checkerHandoff: false });
|
|
1354
|
+
const controller = automatic.program.experimentalOverloadResultController;
|
|
1355
|
+
const route = controller.route;
|
|
1356
|
+
const project = controller.project;
|
|
1357
|
+
let routes = 0;
|
|
1358
|
+
let projections = 0;
|
|
1359
|
+
controller.route = (request) => {
|
|
1360
|
+
routes++;
|
|
1361
|
+
return route(request);
|
|
1362
|
+
};
|
|
1363
|
+
controller.project = (node, result) => {
|
|
1364
|
+
projections++;
|
|
1365
|
+
return project(node, result);
|
|
1366
|
+
};
|
|
1367
|
+
try {
|
|
1368
|
+
if (!queryFirst) {
|
|
1369
|
+
ordinary.analyze();
|
|
1370
|
+
automatic.analyze();
|
|
1371
|
+
}
|
|
1372
|
+
for (const occurrence of [2, 1, 0]) {
|
|
1373
|
+
assert.deepStrictEqual(resultSummary(automatic.program.evaluator, automatic.program.evaluator.getTypeResult(automatic.name('ret', 'ordinary', occurrence))), resultSummary(ordinary.program.evaluator, ordinary.program.evaluator.getTypeResult(ordinary.name('ret', 'ordinary', occurrence))));
|
|
1374
|
+
}
|
|
1375
|
+
assert.deepStrictEqual(automatic.analyze(), ordinary.analyze());
|
|
1376
|
+
assert.equal(automatic.program.experimentalOverloadResultController.getStats(automatic.module), undefined);
|
|
1377
|
+
const stats = automatic.program.experimentalOverloadResultController.getAutomaticStats();
|
|
1378
|
+
assert.equal(stats.modules, 0);
|
|
1379
|
+
assert.equal(stats.activationProbes, 0);
|
|
1380
|
+
assert.equal(stats.selections, 0);
|
|
1381
|
+
assert.equal(routes, 0);
|
|
1382
|
+
assert.equal(projections, 0);
|
|
1383
|
+
assert.ok(!automatic.events.some((e) => e.kind === 'trial'));
|
|
1384
|
+
assert.deepStrictEqual(automatic.info.getFileInfo(automatic.module).diagnosticSink.fetchAndClear(), []);
|
|
1385
|
+
const evaluator = automatic.program.evaluator;
|
|
1386
|
+
automatic.dispose();
|
|
1387
|
+
assert.throws(() => evaluator.getTypeResult(automatic.name('ret', 'ordinary')), /Retired overload-result controller/);
|
|
1388
|
+
}
|
|
1389
|
+
finally {
|
|
1390
|
+
ordinary.dispose();
|
|
1391
|
+
automatic.dispose();
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1394
|
+
test('OverloadResultController ordinary query reuse requires the owned cache entries and request', () => {
|
|
1395
|
+
const source = fixture.slice(0, fixture.indexOf('def project')) +
|
|
1396
|
+
`def ordinary(value: list[int]):
|
|
1397
|
+
ret = choose(value)
|
|
1398
|
+
reveal_type(ret)
|
|
1399
|
+
`;
|
|
1400
|
+
// A fixed empty candidate set retains a declined owner. Automatic concrete
|
|
1401
|
+
// producers no longer acquire one, but owned restoration still needs this guard.
|
|
1402
|
+
const test = create(source, {
|
|
1403
|
+
seeds(module) {
|
|
1404
|
+
const nodes = new Nodes();
|
|
1405
|
+
nodes.walk(module);
|
|
1406
|
+
return nodes.nodes
|
|
1407
|
+
.filter((node) => node.nodeType === 9 /* ParseNodeType.Call */ &&
|
|
1408
|
+
node.d.leftExpr.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
1409
|
+
node.d.leftExpr.d.value === 'choose')
|
|
1410
|
+
.map((node) => ({ node, uncertainty: 2 /* TypeCategory.Any */, candidates: () => [] }));
|
|
1411
|
+
},
|
|
1412
|
+
});
|
|
1413
|
+
try {
|
|
1414
|
+
const original = test.analyze();
|
|
1415
|
+
const evaluator = test.program.evaluator;
|
|
1416
|
+
const controller = test.program.experimentalOverloadResultController;
|
|
1417
|
+
const node = test.name('ret', 'ordinary', 1);
|
|
1418
|
+
const expected = evaluator.getTypeResult(node);
|
|
1419
|
+
const requests = () => controller.getStats(test.module).ordinaryRequests;
|
|
1420
|
+
const before = requests();
|
|
1421
|
+
assert.throws(() => evaluator.runWithCancellationToken(vscode_languageserver_1.CancellationToken.Cancelled, () => evaluator.getTypeResult(node)), (error) => cancellationUtils_1.OperationCanceledException.is(error));
|
|
1422
|
+
assert.deepStrictEqual(resultSummary(evaluator, evaluator.getTypeResult(node)), resultSummary(evaluator, expected));
|
|
1423
|
+
assert.equal(requests(), before);
|
|
1424
|
+
// Equal types do not establish that an entry was restored by this owner.
|
|
1425
|
+
for (const replacement of [expected, { ...expected, isIncomplete: true }]) {
|
|
1426
|
+
evaluator.setTypeResultForNode(node, replacement);
|
|
1427
|
+
const replaced = requests();
|
|
1428
|
+
assert.deepStrictEqual(resultSummary(evaluator, evaluator.getTypeResult(node)), resultSummary(evaluator, expected));
|
|
1429
|
+
assert.ok(requests() > replaced);
|
|
1430
|
+
}
|
|
1431
|
+
const expression = requests();
|
|
1432
|
+
evaluator.getTypeOfExpression(node);
|
|
1433
|
+
assert.ok(requests() > expression);
|
|
1434
|
+
const contextual = controller.getStats(test.module).contextualRequests;
|
|
1435
|
+
evaluator.getTypeOfExpression(node, 256 /* EvalFlags.TypeExpression */, { expectedType: types_1.AnyType.create() });
|
|
1436
|
+
assert.equal(controller.getStats(test.module).contextualRequests, contextual + 1);
|
|
1437
|
+
assert.deepStrictEqual(resultSummary(evaluator, evaluator.getTypeResult(node)), resultSummary(evaluator, expected));
|
|
1438
|
+
assert.deepStrictEqual(test.info.getFileInfo(test.module).diagnosticSink.fetchAndClear(), []);
|
|
1439
|
+
assert.deepStrictEqual(test.analyze(), original);
|
|
1440
|
+
}
|
|
1441
|
+
finally {
|
|
1442
|
+
test.dispose();
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1445
|
+
test.each([false, true])('AutomaticOverloadResult implicit reader type roles queryFirst=%s', (queryFirst) => {
|
|
1446
|
+
const header = fixture.slice(0, fixture.indexOf('def project'));
|
|
1447
|
+
const source = header.replace(', reveal_type', '') +
|
|
1448
|
+
`
|
|
1449
|
+
def positive(value: list[Any]):
|
|
1450
|
+
ret = choose(value)
|
|
1451
|
+
reveal_type(ret, expected_type=list[int])
|
|
1452
|
+
reveal_type(1, expected_type=ret)
|
|
1453
|
+
ret.append("x")
|
|
1454
|
+
`;
|
|
1455
|
+
const ordinary = create(source, false);
|
|
1456
|
+
const automatic = create(source, { automatic: true });
|
|
1457
|
+
try {
|
|
1458
|
+
if (queryFirst) {
|
|
1459
|
+
automatic.program.evaluator.getType(automatic.name('ret'));
|
|
1460
|
+
}
|
|
1461
|
+
const expected = ordinary.analyze();
|
|
1462
|
+
const actual = automatic.analyze();
|
|
1463
|
+
const invalid = expected.filter((d) => d.rule === 'reportInvalidTypeForm');
|
|
1464
|
+
assert.equal(invalid.length, 1);
|
|
1465
|
+
assert.deepStrictEqual(actual.filter((d) => d.rule === 'reportInvalidTypeForm'), invalid);
|
|
1466
|
+
assert.ok(actual.some((d) => d.message.includes('OverloadResult[list[int], list[str]]')));
|
|
1467
|
+
assert.ok(!actual.some((d) => d.rule === 'reportArgumentType'));
|
|
1468
|
+
}
|
|
1469
|
+
finally {
|
|
1470
|
+
ordinary.dispose();
|
|
1471
|
+
automatic.dispose();
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
test.each([
|
|
1475
|
+
'constructor12.py',
|
|
1476
|
+
'constructor34.py',
|
|
1477
|
+
'protocol54.py',
|
|
1478
|
+
'typeVarDefaultClass5.py',
|
|
1479
|
+
'typeVarDefaultClass6.py',
|
|
1480
|
+
'overloadCall6.py',
|
|
1481
|
+
'overloadCall12.py',
|
|
1482
|
+
])('AutomaticOverloadResult preserves complete ordinary source diagnostics for %s', (sample) => {
|
|
1483
|
+
const source = (0, testUtils_1.readSampleFile)(sample);
|
|
1484
|
+
const ordinary = create(source, false);
|
|
1485
|
+
const automatic = create(source, { automatic: true });
|
|
1486
|
+
try {
|
|
1487
|
+
assert.deepStrictEqual(automatic.analyze(), ordinary.analyze());
|
|
1488
|
+
assert.equal(automatic.program.experimentalOverloadResultController.getAutomaticStats().selected, 0);
|
|
1489
|
+
if (sample === 'constructor34.py') {
|
|
1490
|
+
assert.equal(automatic.analyze().filter((d) => d.category === 0 /* DiagnosticCategory.Error */).length, 6);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
finally {
|
|
1494
|
+
ordinary.dispose();
|
|
1495
|
+
automatic.dispose();
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
test('AutomaticOverloadResult proof cutoff and zero consumer discovery preserve ordinary contracts', () => {
|
|
1499
|
+
const source = (0, testUtils_1.readSampleFile)('overloadResultAutomatic1.py');
|
|
1500
|
+
const ordinary = create(source, false);
|
|
1501
|
+
const cutoff = create(source, { automatic: true, limits: { proofUnits: 1 } });
|
|
1502
|
+
const zero = create(source, { automatic: true, consumerCalls: 0 });
|
|
1503
|
+
try {
|
|
1504
|
+
assert.deepStrictEqual(cutoff.analyze(), ordinary.analyze());
|
|
1505
|
+
assert.equal(cutoff.program.experimentalOverloadResultController.getAutomaticStats().activationProbes, 0);
|
|
1506
|
+
assert.equal(cutoff.program.experimentalOverloadResultController.getStats(cutoff.module), undefined);
|
|
1507
|
+
const expectedTypeName = (test) => test.nodes.find((node) => node.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
1508
|
+
node.start === source.indexOf('assert_type(1, ret)') + 'assert_type(1, '.length);
|
|
1509
|
+
const ordinaryResult = ordinary.program.evaluator.getTypeOfExpression(expectedTypeName(ordinary));
|
|
1510
|
+
const cutoffResult = cutoff.program.evaluator.getTypeOfExpression(expectedTypeName(cutoff));
|
|
1511
|
+
assert.equal(ordinary.program.evaluator.printType(ordinaryResult.type), 'Unknown');
|
|
1512
|
+
assert.deepStrictEqual(resultSummary(cutoff.program.evaluator, cutoffResult), resultSummary(ordinary.program.evaluator, ordinaryResult));
|
|
1513
|
+
for (let i = 0; i < 3; i++) {
|
|
1514
|
+
cutoff.program.experimentalOverloadResultController.evict(cutoff.module);
|
|
1515
|
+
cutoff.program.evaluator.evaluateTypesForStatement(cutoff.name('ret', 'original').parent);
|
|
1516
|
+
assert.deepStrictEqual(resultSummary(cutoff.program.evaluator, cutoff.program.evaluator.getTypeOfExpression(expectedTypeName(cutoff))), resultSummary(ordinary.program.evaluator, ordinaryResult));
|
|
1517
|
+
assert.equal(cutoff.program.evaluator.printType(cutoff.program.evaluator.getType(expectedTypeName(cutoff))), 'Unknown');
|
|
1518
|
+
assert.deepStrictEqual(cutoff.info.getFileInfo(cutoff.module).diagnosticSink.fetchAndClear(), []);
|
|
1519
|
+
}
|
|
1520
|
+
zero.analyze();
|
|
1521
|
+
const evaluator = zero.program.evaluator;
|
|
1522
|
+
const ret = evaluator.getType(zero.name('ret', 'original'));
|
|
1523
|
+
assert.ok(ret && (0, types_1.isOverloadResult)(ret));
|
|
1524
|
+
assert.strictEqual(evaluator.getType(zero.name('alias', 'original')), ret);
|
|
1525
|
+
}
|
|
1526
|
+
finally {
|
|
1527
|
+
ordinary.dispose();
|
|
1528
|
+
cutoff.dispose();
|
|
1529
|
+
zero.dispose();
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
test('AutomaticOverloadResult reversed overloads retain generic projection and both scalar member witnesses', () => {
|
|
1533
|
+
const source = fixture
|
|
1534
|
+
.replace(/\r\n/g, '\n')
|
|
1535
|
+
.replace('def choose(value: list[int]) -> list[int]: ...\n@overload\ndef choose(value: list[str]) -> list[str]: ...', 'def choose(value: list[str]) -> list[str]: ...\n@overload\ndef choose(value: list[int]) -> list[int]: ...');
|
|
1536
|
+
const fixed = create(source);
|
|
1537
|
+
const automatic = create(source, { automatic: true });
|
|
1538
|
+
try {
|
|
1539
|
+
assert.deepStrictEqual(automatic.analyze(), fixed.analyze());
|
|
1540
|
+
const evaluator = automatic.program.evaluator;
|
|
1541
|
+
for (const [name, type] of [
|
|
1542
|
+
['ret', 'OverloadResult[list[str], list[int]]'],
|
|
1543
|
+
['projected', 'OverloadResult[Sequence[str], Sequence[int]]'],
|
|
1544
|
+
['scalar', 'OverloadResult[str, int]'],
|
|
1545
|
+
]) {
|
|
1546
|
+
assert.equal(evaluator.printType(evaluator.getType(automatic.name(name))), type);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
finally {
|
|
1550
|
+
fixed.dispose();
|
|
1551
|
+
automatic.dispose();
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
test('OverloadResultController installed without seeds preserves complete ordinary diagnostics and types', () => {
|
|
1555
|
+
const ordinary = create(fixture, false);
|
|
1556
|
+
const empty = create(fixture, { seeds: () => [] });
|
|
1557
|
+
try {
|
|
1558
|
+
assert.deepStrictEqual(empty.analyze(), ordinary.analyze());
|
|
1559
|
+
for (const fn of ['positive', 'concrete', 'gradual', 'unknown', 'failed']) {
|
|
1560
|
+
const a = ordinary.program.evaluator.getTypeOfExpression(ordinary.name('ret', fn));
|
|
1561
|
+
const b = empty.program.evaluator.getTypeOfExpression(empty.name('ret', fn));
|
|
1562
|
+
assert.deepStrictEqual(resultSummary(empty.program.evaluator, b), resultSummary(ordinary.program.evaluator, a));
|
|
1563
|
+
}
|
|
1564
|
+
assert.equal(empty.events.length, 0);
|
|
1565
|
+
assert.equal(empty.program.experimentalOverloadResultController.getStats(empty.module), undefined);
|
|
1566
|
+
}
|
|
1567
|
+
finally {
|
|
1568
|
+
ordinary.dispose();
|
|
1569
|
+
empty.dispose();
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
test('OverloadResultController canonical queries, aliases and actual assignment reentry', () => {
|
|
1573
|
+
const test = create();
|
|
1574
|
+
try {
|
|
1575
|
+
const diagnostics = test.analyze();
|
|
1576
|
+
const evaluator = test.program.evaluator;
|
|
1577
|
+
const alias = test.name('alias');
|
|
1578
|
+
const canonical = evaluator.getTypeOfExpression(alias).type;
|
|
1579
|
+
assert.ok((0, types_1.isOverloadResult)(canonical));
|
|
1580
|
+
assert.strictEqual(evaluator.getType(test.name('ret')), canonical);
|
|
1581
|
+
assert.strictEqual(evaluator.getType(alias), canonical);
|
|
1582
|
+
assert.strictEqual(evaluator.getTypeResult(alias).type, canonical);
|
|
1583
|
+
assert.equal(evaluator.printType(evaluator.getType(test.name('string'))), 'list[str]');
|
|
1584
|
+
assert.ok((0, types_1.isOverloadResult)(evaluator.getType(test.name('projected'))));
|
|
1585
|
+
assert.ok((0, types_1.isOverloadResult)(evaluator.getType(test.name('scalar'))));
|
|
1586
|
+
const offset = fixture.slice(0, alias.start);
|
|
1587
|
+
const hover = new hoverProvider_1.HoverProvider(test.program, test.uri, {
|
|
1588
|
+
line: offset.split('\n').length - 1,
|
|
1589
|
+
character: offset.length - offset.lastIndexOf('\n') - 1,
|
|
1590
|
+
}, 'markdown', vscode_languageserver_1.CancellationToken.None).getHover();
|
|
1591
|
+
assert.ok(JSON.stringify(hover).includes('OverloadResult'));
|
|
1592
|
+
const controller = test.program.experimentalOverloadResultController;
|
|
1593
|
+
const target = test.name('string');
|
|
1594
|
+
assert.ok(target.parent?.nodeType === 54 /* ParseNodeType.TypeAnnotation */);
|
|
1595
|
+
const assignment = target.parent.parent;
|
|
1596
|
+
assert.ok(assignment?.nodeType === 3 /* ParseNodeType.Assignment */);
|
|
1597
|
+
const before = controller.getStats(test.module);
|
|
1598
|
+
for (let round = 0; round < 3; round++) {
|
|
1599
|
+
controller.evict(test.module);
|
|
1600
|
+
assert.equal(evaluator.getCachedType(target), undefined);
|
|
1601
|
+
evaluator.evaluateTypesForStatement(alias.parent);
|
|
1602
|
+
evaluator.evaluateTypesForStatement(assignment);
|
|
1603
|
+
assert.equal(evaluator.printType(evaluator.getCachedType(target)), 'list[str]');
|
|
1604
|
+
assert.ok(!(0, types_1.isOverloadResult)(evaluator.getCachedType(alias)));
|
|
1605
|
+
assert.strictEqual(evaluator.getType(alias), canonical);
|
|
1606
|
+
}
|
|
1607
|
+
assert.deepStrictEqual(test.info.getFileInfo(test.module).diagnosticSink.fetchAndClear(), []);
|
|
1608
|
+
const after = controller.getStats(test.module);
|
|
1609
|
+
assert.equal(before.candidateCalls, after.candidateCalls);
|
|
1610
|
+
assert.equal(before.outcomes, after.outcomes);
|
|
1611
|
+
assert.ok(after.ordinaryRequests > before.ordinaryRequests);
|
|
1612
|
+
assert.deepStrictEqual(test.analyze(), diagnostics);
|
|
1613
|
+
assert.ok(diagnostics.some((d) => d.rule === 'reportAssignmentType'));
|
|
1614
|
+
assert.ok(diagnostics.some((d) => d.rule === 'reportAttributeAccessIssue'));
|
|
1615
|
+
}
|
|
1616
|
+
finally {
|
|
1617
|
+
test.dispose();
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
test('OverloadResultController declines unsupported uses before early queries in both orders', () => {
|
|
1621
|
+
const normal = create(fixture, false);
|
|
1622
|
+
const forward = create();
|
|
1623
|
+
const reverse = create();
|
|
1624
|
+
try {
|
|
1625
|
+
const unsupported = ['repeated', 'escaped', 'captured', 'joined', 'returned', 'attribute', 'distinct'];
|
|
1626
|
+
for (const [test, names] of [
|
|
1627
|
+
[forward, unsupported],
|
|
1628
|
+
[reverse, [...unsupported].reverse()],
|
|
1629
|
+
]) {
|
|
1630
|
+
for (const fn of names) {
|
|
1631
|
+
const node = test.name('ret', fn, 1);
|
|
1632
|
+
const result = test.program.evaluator.getTypeOfExpression(node);
|
|
1633
|
+
const baseline = normal.program.evaluator.getTypeOfExpression(normal.name('ret', fn, 1));
|
|
1634
|
+
assert.ok(!(0, types_1.isOverloadResult)(result.type));
|
|
1635
|
+
assert.deepStrictEqual(resultSummary(test.program.evaluator, result), resultSummary(normal.program.evaluator, baseline));
|
|
1636
|
+
}
|
|
1637
|
+
assert.ok((0, types_1.isOverloadResult)(test.program.evaluator.getType(test.name('independent', 'repeated'))));
|
|
1638
|
+
}
|
|
1639
|
+
assert.deepStrictEqual(forward.analyze(), reverse.analyze());
|
|
1640
|
+
assert.equal(forward.events.filter((e) => e.kind === 'admission').length, 1);
|
|
1641
|
+
const base = normal.analyze();
|
|
1642
|
+
const enabled = forward.analyze();
|
|
1643
|
+
for (const d of enabled.filter((d) => d.range.start.line >= 46)) {
|
|
1644
|
+
assert.ok(base.some((b) => JSON.stringify(b) === JSON.stringify(d)));
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
finally {
|
|
1648
|
+
normal.dispose();
|
|
1649
|
+
forward.dispose();
|
|
1650
|
+
reverse.dispose();
|
|
1651
|
+
}
|
|
1652
|
+
});
|
|
1653
|
+
test.each([false, true])('OverloadResultController genuine expected contexts automatic=%s', (automatic) => {
|
|
1654
|
+
const test = create(fixture, { automatic });
|
|
1655
|
+
try {
|
|
1656
|
+
const initial = test.analyze();
|
|
1657
|
+
const evaluator = test.program.evaluator;
|
|
1658
|
+
const alias = test.name('alias');
|
|
1659
|
+
const canonical = evaluator.getType(alias);
|
|
1660
|
+
const expected = evaluator.getType(test.name('string'));
|
|
1661
|
+
const call = test.nodes.find((n) => n.nodeType === 9 /* ParseNodeType.Call */ &&
|
|
1662
|
+
n.d.leftExpr.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
1663
|
+
n.d.leftExpr.d.value === 'project' &&
|
|
1664
|
+
(0, parseTreeUtils_1.getEnclosingFunction)(n)?.d.name.d.value === 'positive');
|
|
1665
|
+
const stableCall = evaluator.getTypeOfExpression(call);
|
|
1666
|
+
const controller = test.program.experimentalOverloadResultController;
|
|
1667
|
+
const before = controller.getStats(test.module);
|
|
1668
|
+
for (let round = 0; round < 8; round++) {
|
|
1669
|
+
for (const flags of [0 /* EvalFlags.None */, 2 /* EvalFlags.NoSpecialize */]) {
|
|
1670
|
+
assert.strictEqual(evaluator.getTypeOfExpression(alias, flags, { expectedType: expected }).type, canonical);
|
|
1671
|
+
const result = evaluator.getTypeOfExpression(call, flags, {
|
|
1672
|
+
expectedType: expected,
|
|
1673
|
+
isTypeIncomplete: true,
|
|
1674
|
+
});
|
|
1675
|
+
assert.ok(!(0, types_1.isOverloadResult)(result.type));
|
|
1676
|
+
assert.strictEqual(evaluator.getTypeOfExpression(call).type, stableCall.type);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
const after = controller.getStats(test.module);
|
|
1680
|
+
assert.equal(after.outcomes, before.outcomes);
|
|
1681
|
+
assert.equal(after.slots, before.slots);
|
|
1682
|
+
assert.equal(after.candidateCalls, before.candidateCalls);
|
|
1683
|
+
assert.equal(after.contextualRequests - before.contextualRequests, 16);
|
|
1684
|
+
assert.deepStrictEqual(test.info.getFileInfo(test.module).diagnosticSink.fetchAndClear(), []);
|
|
1685
|
+
assert.deepStrictEqual(test.analyze(), initial);
|
|
1686
|
+
const contextFirst = create(fixture, { automatic });
|
|
1687
|
+
try {
|
|
1688
|
+
const coldCall = contextFirst.nodes.find((n) => n.nodeType === 9 /* ParseNodeType.Call */ && n.start === call.start);
|
|
1689
|
+
const cold = contextFirst.program.evaluator;
|
|
1690
|
+
const contextual = cold.getTypeOfExpression(coldCall, 2 /* EvalFlags.NoSpecialize */, {
|
|
1691
|
+
expectedType: types_1.AnyType.create(),
|
|
1692
|
+
isTypeIncomplete: true,
|
|
1693
|
+
});
|
|
1694
|
+
assert.ok(!(0, types_1.isOverloadResult)(contextual.type));
|
|
1695
|
+
assert.deepStrictEqual(contextFirst.info.getFileInfo(contextFirst.module).diagnosticSink.fetchAndClear(), []);
|
|
1696
|
+
assert.deepStrictEqual(contextFirst.analyze(), initial);
|
|
1697
|
+
assert.equal(cold.printType(cold.getTypeOfExpression(coldCall).type), evaluator.printType(stableCall.type));
|
|
1698
|
+
}
|
|
1699
|
+
finally {
|
|
1700
|
+
contextFirst.dispose();
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
finally {
|
|
1704
|
+
test.dispose();
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
test('OverloadResultController zero limits preserve ordinary errors and zero consumer budget preserves aliases', () => {
|
|
1708
|
+
const normal = create(fixture, false);
|
|
1709
|
+
const zero = create(fixture, { limits: { candidates: 0 } });
|
|
1710
|
+
const alias = create(fixture, { consumerCalls: 0 });
|
|
1711
|
+
try {
|
|
1712
|
+
assert.deepStrictEqual(zero.analyze(), normal.analyze());
|
|
1713
|
+
alias.analyze();
|
|
1714
|
+
const ev = alias.program.evaluator;
|
|
1715
|
+
assert.ok((0, types_1.isOverloadResult)(ev.getType(alias.name('ret'))));
|
|
1716
|
+
assert.strictEqual(ev.getType(alias.name('ret')), ev.getType(alias.name('alias')));
|
|
1717
|
+
const cutoffs = zero.events.filter((e) => e.kind === 'value' && e.cutoff);
|
|
1718
|
+
assert.ok(cutoffs.length);
|
|
1719
|
+
for (const event of cutoffs) {
|
|
1720
|
+
if (event.kind === 'value') {
|
|
1721
|
+
assert.strictEqual(event.canonical, event.baseline);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
finally {
|
|
1726
|
+
normal.dispose();
|
|
1727
|
+
zero.dispose();
|
|
1728
|
+
alias.dispose();
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1731
|
+
test('OverloadResultController unsupported flags and root alternative contexts use complete ordinary operations', () => {
|
|
1732
|
+
const enabled = create();
|
|
1733
|
+
try {
|
|
1734
|
+
const initial = enabled.analyze();
|
|
1735
|
+
const evaluator = enabled.program.evaluator;
|
|
1736
|
+
const canonical = evaluator.getType(enabled.name('alias'));
|
|
1737
|
+
assert.ok((0, types_1.isOverloadResult)(canonical));
|
|
1738
|
+
const findCall = (nodes) => nodes.find((n) => n.nodeType === 9 /* ParseNodeType.Call */ &&
|
|
1739
|
+
n.d.leftExpr.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
1740
|
+
n.d.leftExpr.d.value === 'project' &&
|
|
1741
|
+
(0, parseTreeUtils_1.getEnclosingFunction)(n)?.d.name.d.value === 'positive');
|
|
1742
|
+
const call = findCall(enabled.nodes);
|
|
1743
|
+
const controller = enabled.program.experimentalOverloadResultController;
|
|
1744
|
+
const before = controller.getStats(enabled.module);
|
|
1745
|
+
for (const flags of [256 /* EvalFlags.TypeExpression */, 1073741824 /* EvalFlags.TypeFormArg */, 4 /* EvalFlags.ForwardRefs */]) {
|
|
1746
|
+
for (const node of [enabled.name('alias'), call]) {
|
|
1747
|
+
controller.evict(enabled.module);
|
|
1748
|
+
const result = evaluator.getTypeOfExpression(node, flags, {
|
|
1749
|
+
expectedType: canonical,
|
|
1750
|
+
returnTypeOverride: canonical,
|
|
1751
|
+
isTypeIncomplete: true,
|
|
1752
|
+
});
|
|
1753
|
+
assert.ok(!(0, types_1.isOverloadResult)(result.type));
|
|
1754
|
+
// An owned contextual operation has fresh selected caches. Compare
|
|
1755
|
+
// with a cold ordinary request, not a cached context-free answer.
|
|
1756
|
+
const ordinary = create(fixture, false);
|
|
1757
|
+
try {
|
|
1758
|
+
const baselineEvaluator = ordinary.program.evaluator;
|
|
1759
|
+
const baseline = baselineEvaluator.getTypeOfExpression(node === call ? findCall(ordinary.nodes) : ordinary.name('alias'), flags, {
|
|
1760
|
+
expectedType: baselineEvaluator.getType(ordinary.name('ret')),
|
|
1761
|
+
returnTypeOverride: baselineEvaluator.getType(ordinary.name('ret')),
|
|
1762
|
+
isTypeIncomplete: true,
|
|
1763
|
+
});
|
|
1764
|
+
assert.deepStrictEqual(resultSummary(evaluator, result), resultSummary(baselineEvaluator, baseline));
|
|
1765
|
+
const trials = enabled.events.filter((e) => e.kind === 'trial' && e.mode === 'context');
|
|
1766
|
+
const trial = trials[trials.length - 1];
|
|
1767
|
+
assert.ok(trial.kind === 'trial');
|
|
1768
|
+
const diagnostic = (d) => ({
|
|
1769
|
+
category: d.category,
|
|
1770
|
+
message: d.message,
|
|
1771
|
+
range: d.range,
|
|
1772
|
+
rule: d.getRule(),
|
|
1773
|
+
});
|
|
1774
|
+
assert.deepStrictEqual(trial.diagnostics.map(diagnostic), ordinary.info.getFileInfo(ordinary.module).diagnosticSink.fetchAndClear().map(diagnostic));
|
|
1775
|
+
}
|
|
1776
|
+
finally {
|
|
1777
|
+
ordinary.dispose();
|
|
1778
|
+
}
|
|
1779
|
+
assert.strictEqual(evaluator.getType(enabled.name('alias')), canonical);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
const after = controller.getStats(enabled.module);
|
|
1783
|
+
assert.equal(after.outcomes, before.outcomes);
|
|
1784
|
+
assert.equal(after.candidateCalls, before.candidateCalls);
|
|
1785
|
+
assert.equal(after.contextualRequests - before.contextualRequests, 6);
|
|
1786
|
+
assert.deepStrictEqual(enabled.info.getFileInfo(enabled.module).diagnosticSink.fetchAndClear(), []);
|
|
1787
|
+
assert.deepStrictEqual(enabled.analyze(), initial);
|
|
1788
|
+
}
|
|
1789
|
+
finally {
|
|
1790
|
+
enabled.dispose();
|
|
1791
|
+
}
|
|
1792
|
+
});
|
|
1793
|
+
test('OverloadResultController cutoff after successful member lookup preserves the complete ordinary operation', () => {
|
|
1794
|
+
const content = fixture
|
|
1795
|
+
.replace('scalar.upper()', 'unwrap(project(ret)).upper()')
|
|
1796
|
+
.replace('scalar.bit_length()', 'unwrap(project(ret)).bit_length()');
|
|
1797
|
+
const test = create(content, { consumerCalls: 3 });
|
|
1798
|
+
try {
|
|
1799
|
+
test.analyze();
|
|
1800
|
+
const calls = test.events.filter((e) => e.kind === 'beforeCall' && e.mode === 'candidate');
|
|
1801
|
+
const resolved = calls.find((e) => e.kind === 'beforeCall' &&
|
|
1802
|
+
e.node.d.leftExpr.nodeType === 35 /* ParseNodeType.MemberAccess */ &&
|
|
1803
|
+
e.node.d.leftExpr.d.member.d.value === 'bit_length' &&
|
|
1804
|
+
(0, types_1.isFunction)(e.seen.get(e.node.d.leftExpr).type));
|
|
1805
|
+
assert.ok(resolved?.kind === 'beforeCall');
|
|
1806
|
+
assert.ok(test.events.some((e) => e.kind === 'trial' &&
|
|
1807
|
+
e.exception &&
|
|
1808
|
+
e.mode === 'candidate' &&
|
|
1809
|
+
e.root.start <= resolved.node.start &&
|
|
1810
|
+
e.root.start + e.root.length >= resolved.node.start + resolved.node.length));
|
|
1811
|
+
const cutoffs = test.events.filter((e) => e.kind === 'value' && e.cutoff);
|
|
1812
|
+
assert.ok(cutoffs.length);
|
|
1813
|
+
for (const e of cutoffs) {
|
|
1814
|
+
assert.ok(e.kind === 'value');
|
|
1815
|
+
assert.strictEqual(e.canonical, e.baseline);
|
|
1816
|
+
}
|
|
1817
|
+
assert.deepStrictEqual(test.info.getFileInfo(test.module).diagnosticSink.fetchAndClear(), []);
|
|
1818
|
+
}
|
|
1819
|
+
finally {
|
|
1820
|
+
test.dispose();
|
|
1821
|
+
}
|
|
1822
|
+
});
|
|
1823
|
+
test.each(['error', 'cancel'])('AutomaticOverloadResult interrupted activation probe restores state: %s', (kind) => {
|
|
1824
|
+
let fault = true;
|
|
1825
|
+
const reference = create(fixture, { automatic: true, checkerHandoff: false });
|
|
1826
|
+
const test = create(fixture, {
|
|
1827
|
+
automatic: true,
|
|
1828
|
+
checkerHandoff: false,
|
|
1829
|
+
observe(event) {
|
|
1830
|
+
if (fault && event.kind === 'activationProbe' && event.selection.reason === 'selected') {
|
|
1831
|
+
fault = false;
|
|
1832
|
+
if (kind === 'cancel') {
|
|
1833
|
+
throw new cancellationUtils_1.OperationCanceledException();
|
|
1834
|
+
}
|
|
1835
|
+
throw new Error('activation interruption');
|
|
1836
|
+
}
|
|
1837
|
+
},
|
|
1838
|
+
});
|
|
1839
|
+
try {
|
|
1840
|
+
const evaluator = test.program.evaluator;
|
|
1841
|
+
const controller = test.program.experimentalOverloadResultController;
|
|
1842
|
+
assert.throws(() => evaluator.getType(test.name('ret')), (error) => kind === 'cancel'
|
|
1843
|
+
? cancellationUtils_1.OperationCanceledException.is(error)
|
|
1844
|
+
: error instanceof Error && error.message === 'activation interruption');
|
|
1845
|
+
assert.equal(controller.hasPendingActivation, false);
|
|
1846
|
+
assert.equal(controller.getAutomaticStats().modules, 0);
|
|
1847
|
+
assert.equal(controller.roots.size, 0);
|
|
1848
|
+
assert.equal(evaluator.isSpeculativeModeInUse(undefined), false);
|
|
1849
|
+
assert.deepStrictEqual(test.info.getFileInfo(test.module).diagnosticSink.fetchAndClear(), []);
|
|
1850
|
+
assert.ok((0, types_1.isOverloadResult)(evaluator.getType(test.name('ret'))));
|
|
1851
|
+
assert.deepStrictEqual(test.analyze(), reference.analyze());
|
|
1852
|
+
}
|
|
1853
|
+
finally {
|
|
1854
|
+
reference.dispose();
|
|
1855
|
+
test.dispose();
|
|
1856
|
+
}
|
|
1857
|
+
});
|
|
1858
|
+
test.each([false, true])('OverloadResultController cancelled discovery restores state automatic=%s', (automatic) => {
|
|
1859
|
+
let fault = true;
|
|
1860
|
+
const test = create(fixture, {
|
|
1861
|
+
automatic,
|
|
1862
|
+
observe(event) {
|
|
1863
|
+
if (fault && event.kind === 'beforeCall' && event.mode === 'candidate') {
|
|
1864
|
+
fault = false;
|
|
1865
|
+
throw new cancellationUtils_1.OperationCanceledException();
|
|
1866
|
+
}
|
|
1867
|
+
},
|
|
1868
|
+
});
|
|
1869
|
+
try {
|
|
1870
|
+
const evaluator = test.program.evaluator;
|
|
1871
|
+
assert.throws(() => evaluator.getType(test.name('ret')), (error) => cancellationUtils_1.OperationCanceledException.is(error));
|
|
1872
|
+
assert.equal(evaluator.getCachedType(test.name('ret')), undefined);
|
|
1873
|
+
assert.deepStrictEqual(test.info.getFileInfo(test.module).diagnosticSink.fetchAndClear(), []);
|
|
1874
|
+
assert.ok((0, types_1.isOverloadResult)(evaluator.getType(test.name('ret'))));
|
|
1875
|
+
assert.ok(test.program.experimentalOverloadResultController.getStats(test.module).retryReservations > 0);
|
|
1876
|
+
test.analyze();
|
|
1877
|
+
const controller = test.program.experimentalOverloadResultController;
|
|
1878
|
+
test.dispose();
|
|
1879
|
+
assert.equal(controller.getStats(test.module), undefined);
|
|
1880
|
+
assert.throws(() => evaluator.getType(test.name('ret')), /Retired overload-result controller/);
|
|
1881
|
+
}
|
|
1882
|
+
finally {
|
|
1883
|
+
test.dispose();
|
|
1884
|
+
}
|
|
1885
|
+
});
|
|
1886
|
+
test.each([
|
|
1887
|
+
' ret += [1]',
|
|
1888
|
+
' while condition:\n ret = [b"changed"]\n ret.append("late")',
|
|
1889
|
+
' alias = ret\n print(ret, alias)',
|
|
1890
|
+
' second = choose(ret)\n second.append("late")',
|
|
1891
|
+
' storage.append(ret)',
|
|
1892
|
+
' box.field = ret',
|
|
1893
|
+
])('OverloadResultController unsupported local path declines before publication: %s', (suffix) => {
|
|
1894
|
+
const source = fixture +
|
|
1895
|
+
'\n\ndef tail(value: list[Any], condition: bool, storage: list[list[int]], box: Box):\n' +
|
|
1896
|
+
' ret = choose(value)\n ret.append("early")\n' +
|
|
1897
|
+
suffix +
|
|
1898
|
+
'\n';
|
|
1899
|
+
const ordinary = create(source, false);
|
|
1900
|
+
const enabled = create(source);
|
|
1901
|
+
try {
|
|
1902
|
+
const result = enabled.program.evaluator.getTypeOfExpression(enabled.name('ret', 'tail', 1));
|
|
1903
|
+
assert.ok(!(0, types_1.isOverloadResult)(result.type));
|
|
1904
|
+
const startLine = source.slice(0, source.indexOf('def tail')).split('\n').length - 1;
|
|
1905
|
+
const baseline = ordinary.analyze().filter((d) => d.range.start.line >= startLine);
|
|
1906
|
+
const actual = enabled.analyze().filter((d) => d.range.start.line >= startLine);
|
|
1907
|
+
assert.deepStrictEqual(actual, baseline);
|
|
1908
|
+
assert.ok((0, types_1.isOverloadResult)(enabled.program.evaluator.getType(enabled.name('independent', 'repeated'))));
|
|
1909
|
+
}
|
|
1910
|
+
finally {
|
|
1911
|
+
ordinary.dispose();
|
|
1912
|
+
enabled.dispose();
|
|
1913
|
+
}
|
|
1914
|
+
});
|
|
1915
|
+
test('OverloadResultController materialization faults restore selected caches and live diagnostic ownership', () => {
|
|
1916
|
+
let fault;
|
|
1917
|
+
const test = create(fixture, {
|
|
1918
|
+
observe(event) {
|
|
1919
|
+
if (fault && event.kind === 'project' && event.mode === 'materialize-witness') {
|
|
1920
|
+
const current = fault;
|
|
1921
|
+
fault = undefined;
|
|
1922
|
+
if (current === 'cancel') {
|
|
1923
|
+
throw new cancellationUtils_1.OperationCanceledException();
|
|
1924
|
+
}
|
|
1925
|
+
throw new Error('materialization interruption');
|
|
1926
|
+
}
|
|
1927
|
+
},
|
|
1928
|
+
});
|
|
1929
|
+
try {
|
|
1930
|
+
const original = test.analyze();
|
|
1931
|
+
const evaluator = test.program.evaluator;
|
|
1932
|
+
const target = test.name('string');
|
|
1933
|
+
assert.ok(target.parent?.nodeType === 54 /* ParseNodeType.TypeAnnotation */);
|
|
1934
|
+
const statement = target.parent.parent;
|
|
1935
|
+
assert.ok(statement?.nodeType === 3 /* ParseNodeType.Assignment */);
|
|
1936
|
+
const canonical = evaluator.getType(test.name('alias'));
|
|
1937
|
+
const sink = test.info.getFileInfo(test.module).diagnosticSink;
|
|
1938
|
+
for (const kind of ['error', 'cancel']) {
|
|
1939
|
+
test.program.experimentalOverloadResultController.evict(test.module);
|
|
1940
|
+
fault = kind;
|
|
1941
|
+
assert.throws(() => evaluator.evaluateTypesForStatement(statement), (error) => kind === 'cancel'
|
|
1942
|
+
? cancellationUtils_1.OperationCanceledException.is(error)
|
|
1943
|
+
: error instanceof Error && error.message === 'materialization interruption');
|
|
1944
|
+
assert.strictEqual(test.info.getFileInfo(test.module).diagnosticSink, sink);
|
|
1945
|
+
assert.equal(evaluator.getCachedType(target), undefined);
|
|
1946
|
+
assert.deepStrictEqual(sink.fetchAndClear(), []);
|
|
1947
|
+
assert.ok(!evaluator.isSpeculativeModeInUse(undefined));
|
|
1948
|
+
evaluator.evaluateTypesForStatement(statement);
|
|
1949
|
+
assert.equal(evaluator.printType(evaluator.getCachedType(target)), 'list[str]');
|
|
1950
|
+
assert.strictEqual(evaluator.getType(test.name('alias')), canonical);
|
|
1951
|
+
}
|
|
1952
|
+
assert.deepStrictEqual(test.analyze(), original);
|
|
1953
|
+
}
|
|
1954
|
+
finally {
|
|
1955
|
+
test.dispose();
|
|
1956
|
+
}
|
|
1957
|
+
});
|
|
1958
|
+
test.each([false, true])('OverloadResultController discovery limits automatic=%s', (automatic) => {
|
|
1959
|
+
const exact = create(fixture, { automatic, limits: { candidates: 2 } });
|
|
1960
|
+
const over = create(fixture, { automatic, limits: { candidates: 1 } });
|
|
1961
|
+
const zeroGraph = create(fixture, { automatic, limits: { graphUnits: 0 } });
|
|
1962
|
+
const ordinary = create(fixture, false);
|
|
1963
|
+
try {
|
|
1964
|
+
assert.ok((0, types_1.isOverloadResult)(exact.program.evaluator.getType(exact.name('ret'))));
|
|
1965
|
+
const baseline = ordinary.analyze();
|
|
1966
|
+
assert.deepStrictEqual(over.analyze(), baseline);
|
|
1967
|
+
assert.deepStrictEqual(zeroGraph.analyze(), baseline);
|
|
1968
|
+
assert.ok(!(0, types_1.isOverloadResult)(over.program.evaluator.getType(over.name('ret'))));
|
|
1969
|
+
assert.equal(zeroGraph.program.experimentalOverloadResultController.getStats(zeroGraph.module).candidateCalls, 0);
|
|
1970
|
+
}
|
|
1971
|
+
finally {
|
|
1972
|
+
exact.dispose();
|
|
1973
|
+
over.dispose();
|
|
1974
|
+
zeroGraph.dispose();
|
|
1975
|
+
ordinary.dispose();
|
|
1976
|
+
}
|
|
1977
|
+
});
|
|
1978
|
+
test.each([false, true])('OverloadResultController source replacement automatic=%s', (automatic) => {
|
|
1979
|
+
const test = create(fixture, { automatic });
|
|
1980
|
+
try {
|
|
1981
|
+
const diagnostics = test.analyze();
|
|
1982
|
+
const old = test.program.evaluator;
|
|
1983
|
+
const alias = test.name('alias');
|
|
1984
|
+
const value = old.getType(alias);
|
|
1985
|
+
test.program.setFileOpened(test.uri, 2, fixture.replace('ret = choose(value)', 'ret = [b"edited"]'));
|
|
1986
|
+
test.analyze();
|
|
1987
|
+
assert.ok(test.events.some((event) => event.kind === 'retire'));
|
|
1988
|
+
test.program.setFileOpened(test.uri, 3, fixture);
|
|
1989
|
+
assert.deepStrictEqual(test.analyze(), diagnostics);
|
|
1990
|
+
const nodes = new Nodes();
|
|
1991
|
+
nodes.walk(test.program.getParseResults(test.uri).parserOutput.parseTree);
|
|
1992
|
+
const restored = nodes.nodes.find((n) => n.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
1993
|
+
n.d.value === 'alias' &&
|
|
1994
|
+
(0, parseTreeUtils_1.getEnclosingFunction)(n)?.d.name.d.value === 'positive');
|
|
1995
|
+
const current = test.program.evaluator.getType(restored);
|
|
1996
|
+
assert.ok(current && (0, types_1.isOverloadResult)(current));
|
|
1997
|
+
assert.notStrictEqual(current, value);
|
|
1998
|
+
assert.throws(() => old.getType(alias), /Retired overload-result controller|Expired overload-result parse generation/);
|
|
1999
|
+
}
|
|
2000
|
+
finally {
|
|
2001
|
+
test.dispose();
|
|
2002
|
+
}
|
|
2003
|
+
});
|
|
2004
|
+
const readerFixture = (0, testUtils_1.readSampleFile)('overloadResultReader1.py');
|
|
2005
|
+
const readerOrders = ['check-first', 'value-first', 'reader-first'];
|
|
2006
|
+
test.each(readerOrders.flatMap((order) => [false, true].map((automatic) => ({ order, automatic }))))('OverloadResultController reader type argument preserves ordinary errors: %j', ({ order, automatic }) => {
|
|
2007
|
+
const ordinary = create(readerFixture, false);
|
|
2008
|
+
const enabled = create(readerFixture, { automatic });
|
|
2009
|
+
const expected = [
|
|
2010
|
+
{
|
|
2011
|
+
category: 0 /* DiagnosticCategory.Error */,
|
|
2012
|
+
rule: 'reportInvalidTypeForm',
|
|
2013
|
+
message: 'Variable not allowed in type expression',
|
|
2014
|
+
range: { start: { line: 13, character: 19 }, end: { line: 13, character: 22 } },
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
category: 0 /* DiagnosticCategory.Error */,
|
|
2018
|
+
rule: 'reportAssertTypeFailure',
|
|
2019
|
+
message: '"assert_type" mismatch: expected "Unknown" but received "Literal[1]"',
|
|
2020
|
+
range: { start: { line: 13, character: 16 }, end: { line: 13, character: 17 } },
|
|
2021
|
+
},
|
|
2022
|
+
];
|
|
2023
|
+
const reader = (test) => test.nodes.find((n) => n.nodeType === 9 /* ParseNodeType.Call */ && n.start === readerFixture.indexOf('assert_type(1, ret)'));
|
|
2024
|
+
try {
|
|
2025
|
+
for (const test of [ordinary, enabled]) {
|
|
2026
|
+
if (order === 'value-first') {
|
|
2027
|
+
test.program.evaluator.getType(test.name('ret', 'invalid_expected_type'));
|
|
2028
|
+
}
|
|
2029
|
+
else if (order === 'reader-first') {
|
|
2030
|
+
test.program.evaluator.getTypeOfExpression(reader(test));
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
assert.deepStrictEqual(ordinary.analyze(), expected);
|
|
2034
|
+
assert.deepStrictEqual(enabled.analyze(), expected);
|
|
2035
|
+
const evaluator = enabled.program.evaluator;
|
|
2036
|
+
const value = evaluator.getType(enabled.name('ret', 'invalid_expected_type'));
|
|
2037
|
+
assert.ok(value && (0, types_1.isOverloadResult)(value));
|
|
2038
|
+
assert.deepStrictEqual(resultSummary(evaluator, evaluator.getTypeOfExpression(reader(enabled))), resultSummary(ordinary.program.evaluator, ordinary.program.evaluator.getTypeOfExpression(reader(ordinary))));
|
|
2039
|
+
assert.ok(enabled.events.some((e) => e.kind === 'trial' &&
|
|
2040
|
+
e.mode === 'reader' &&
|
|
2041
|
+
e.diagnostics.some((d) => d.getRule() === 'reportInvalidTypeForm')));
|
|
2042
|
+
enabled.program.experimentalOverloadResultController.evict(enabled.module);
|
|
2043
|
+
evaluator.evaluateTypesForStatement(reader(enabled));
|
|
2044
|
+
assert.strictEqual(evaluator.getType(enabled.name('ret', 'invalid_expected_type')), value);
|
|
2045
|
+
assert.deepStrictEqual(enabled.info.getFileInfo(enabled.module).diagnosticSink.fetchAndClear(), []);
|
|
2046
|
+
assert.deepStrictEqual(enabled.analyze(), expected);
|
|
2047
|
+
}
|
|
2048
|
+
finally {
|
|
2049
|
+
ordinary.dispose();
|
|
2050
|
+
enabled.dispose();
|
|
2051
|
+
}
|
|
2052
|
+
});
|
|
2053
|
+
test.each([
|
|
2054
|
+
{ statement: 'check_type(1, ret)', declined: false },
|
|
2055
|
+
{ statement: 'show_type(1, expected_type=ret)', declined: false },
|
|
2056
|
+
{ statement: 'alias = ret\n check_type(1, alias)', declined: false },
|
|
2057
|
+
{ statement: 'check_type(1, "ret")', declined: false },
|
|
2058
|
+
{ statement: 'check_type(1, ret.__class__)', declined: true },
|
|
2059
|
+
{ statement: 'check_type(ret.copy(), list[int])', declined: true },
|
|
2060
|
+
{ statement: 'show_type(ret.copy())', declined: true },
|
|
2061
|
+
{ statement: 'check_type(1, list[ret])', declined: true },
|
|
2062
|
+
{ statement: 'check_type(ret, ret)', declined: true },
|
|
2063
|
+
{ statement: 'reader = check_type\n reader(1, ret)', declined: true },
|
|
2064
|
+
{ statement: 'reader = check_type\n reader(ret, list[int])', declined: true },
|
|
2065
|
+
{ statement: 'reader = show_type\n reader(ret)', declined: true },
|
|
2066
|
+
{ statement: 'print(check_type(ret, list[int]))', declined: true },
|
|
2067
|
+
{ statement: 'check_type(ret, list[int]).append(1)', declined: true },
|
|
2068
|
+
])('OverloadResultController reader roles and aliases: $statement', ({ statement, declined }) => {
|
|
2069
|
+
const content = readerFixture
|
|
2070
|
+
.replace('from typing import Any, assert_type, overload', 'from typing import Any, assert_type as check_type, reveal_type as show_type, overload')
|
|
2071
|
+
.replace('assert_type(1, ret)', statement);
|
|
2072
|
+
for (const order of readerOrders) {
|
|
2073
|
+
const ordinary = create(content, false);
|
|
2074
|
+
const enabled = create(content);
|
|
2075
|
+
const root = (test) => test.nodes.find((n) => n.nodeType === 9 /* ParseNodeType.Call */ &&
|
|
2076
|
+
n.start === content.lastIndexOf(statement.split('\n').pop().trim()));
|
|
2077
|
+
try {
|
|
2078
|
+
if (order === 'reader-first') {
|
|
2079
|
+
ordinary.program.evaluator.getTypeOfExpression(root(ordinary));
|
|
2080
|
+
enabled.program.evaluator.getTypeOfExpression(root(enabled));
|
|
2081
|
+
}
|
|
2082
|
+
else if (order === 'value-first') {
|
|
2083
|
+
ordinary.program.evaluator.getType(ordinary.name('ret', 'invalid_expected_type'));
|
|
2084
|
+
const early = enabled.program.evaluator.getType(enabled.name('ret', 'invalid_expected_type'));
|
|
2085
|
+
assert.ok(early);
|
|
2086
|
+
assert.equal((0, types_1.isOverloadResult)(early), !declined);
|
|
2087
|
+
}
|
|
2088
|
+
assert.deepStrictEqual(enabled.analyze(), ordinary.analyze());
|
|
2089
|
+
assert.deepStrictEqual(resultSummary(enabled.program.evaluator, enabled.program.evaluator.getTypeOfExpression(root(enabled))), resultSummary(ordinary.program.evaluator, ordinary.program.evaluator.getTypeOfExpression(root(ordinary))));
|
|
2090
|
+
const value = enabled.program.evaluator.getType(enabled.name('ret', 'invalid_expected_type'));
|
|
2091
|
+
assert.ok(value);
|
|
2092
|
+
assert.equal((0, types_1.isOverloadResult)(value), !declined);
|
|
2093
|
+
assert.deepStrictEqual(enabled.info.getFileInfo(enabled.module).diagnosticSink.fetchAndClear(), []);
|
|
2094
|
+
}
|
|
2095
|
+
finally {
|
|
2096
|
+
ordinary.dispose();
|
|
2097
|
+
enabled.dispose();
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
test.each([false, true])('OverloadResultController canonical reader value arguments remain exact, aliased=%s', (aliased) => {
|
|
2102
|
+
const assertName = aliased ? 'check_type' : 'assert_type';
|
|
2103
|
+
const revealName = aliased ? 'show_type' : 'reveal_type';
|
|
2104
|
+
const content = readerFixture
|
|
2105
|
+
.replace('from typing import Any, assert_type, overload', `from typing import Any, assert_type${aliased ? ' as check_type' : ''}, reveal_type${aliased ? ' as show_type' : ''}, overload`)
|
|
2106
|
+
.replace('assert_type(1, ret)', `${assertName}(ret, list[int])\n ${assertName}(ret, list[Any])\n ${revealName}(ret)`);
|
|
2107
|
+
for (const order of readerOrders) {
|
|
2108
|
+
const ordinary = create(content, false);
|
|
2109
|
+
const enabled = create(content);
|
|
2110
|
+
try {
|
|
2111
|
+
if (order === 'value-first') {
|
|
2112
|
+
enabled.program.evaluator.getType(enabled.name('ret', 'invalid_expected_type'));
|
|
2113
|
+
}
|
|
2114
|
+
else if (order === 'reader-first') {
|
|
2115
|
+
const calls = enabled.nodes.filter((n) => n.nodeType === 9 /* ParseNodeType.Call */ &&
|
|
2116
|
+
n.d.leftExpr.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
2117
|
+
[assertName, revealName].includes(n.d.leftExpr.d.value));
|
|
2118
|
+
for (const call of [...calls].reverse()) {
|
|
2119
|
+
enabled.program.evaluator.getTypeOfExpression(call);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
const baseline = ordinary.analyze();
|
|
2123
|
+
const diagnostics = enabled.analyze();
|
|
2124
|
+
const value = enabled.program.evaluator.getType(enabled.name('ret', 'invalid_expected_type'));
|
|
2125
|
+
assert.ok(value && (0, types_1.isOverloadResult)(value));
|
|
2126
|
+
assert.equal(diagnostics.length, 3);
|
|
2127
|
+
assert.equal(baseline.length, 2);
|
|
2128
|
+
assert.ok(diagnostics.every((d) => d.rule !== 'reportInvalidTypeForm'));
|
|
2129
|
+
assert.equal(diagnostics[0].message, '"assert_type" mismatch: expected "list[int]" but received "OverloadResult[list[int], list[str]]"');
|
|
2130
|
+
assert.equal(diagnostics[1].message, '"assert_type" mismatch: expected "list[Any]" but received "OverloadResult[list[int], list[str]]"');
|
|
2131
|
+
assert.equal(diagnostics[2].message, 'Type of "ret" is "OverloadResult[list[int], list[str]]"');
|
|
2132
|
+
const readers = enabled.nodes.filter((n) => n.nodeType === 9 /* ParseNodeType.Call */ &&
|
|
2133
|
+
n.d.leftExpr.nodeType === 38 /* ParseNodeType.Name */ &&
|
|
2134
|
+
[assertName, revealName].includes(n.d.leftExpr.d.value));
|
|
2135
|
+
for (const call of readers) {
|
|
2136
|
+
assert.strictEqual(enabled.program.evaluator.getTypeOfExpression(call, 2 /* EvalFlags.NoSpecialize */).type, value);
|
|
2137
|
+
}
|
|
2138
|
+
assert.deepStrictEqual(enabled.info.getFileInfo(enabled.module).diagnosticSink.fetchAndClear(), []);
|
|
2139
|
+
}
|
|
2140
|
+
finally {
|
|
2141
|
+
ordinary.dispose();
|
|
2142
|
+
enabled.dispose();
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
});
|
|
2146
|
+
//# sourceMappingURL=overloadResultController.test.js.map
|