numtypes 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +12 -0
  3. package/LICENSE-APACHE +201 -0
  4. package/LICENSE-MIT +21 -0
  5. package/README.md +652 -0
  6. package/dist/lib/index.d.ts +22 -0
  7. package/dist/lib/index.d.ts.map +1 -0
  8. package/dist/lib/index.js +2 -0
  9. package/dist/lib/index.js.map +1 -0
  10. package/dist/transformer/analyze/analyze-source-file.d.ts +15 -0
  11. package/dist/transformer/analyze/analyze-source-file.d.ts.map +1 -0
  12. package/dist/transformer/analyze/analyze-source-file.js +605 -0
  13. package/dist/transformer/analyze/analyze-source-file.js.map +1 -0
  14. package/dist/transformer/analyze/get-contextual-domain.d.ts +19 -0
  15. package/dist/transformer/analyze/get-contextual-domain.d.ts.map +1 -0
  16. package/dist/transformer/analyze/get-contextual-domain.js +197 -0
  17. package/dist/transformer/analyze/get-contextual-domain.js.map +1 -0
  18. package/dist/transformer/analyze/get-expression-domain.d.ts +26 -0
  19. package/dist/transformer/analyze/get-expression-domain.d.ts.map +1 -0
  20. package/dist/transformer/analyze/get-expression-domain.js +804 -0
  21. package/dist/transformer/analyze/get-expression-domain.js.map +1 -0
  22. package/dist/transformer/analyze/type-domain.d.ts +41 -0
  23. package/dist/transformer/analyze/type-domain.d.ts.map +1 -0
  24. package/dist/transformer/analyze/type-domain.js +260 -0
  25. package/dist/transformer/analyze/type-domain.js.map +1 -0
  26. package/dist/transformer/ast.d.ts +10 -0
  27. package/dist/transformer/ast.d.ts.map +1 -0
  28. package/dist/transformer/ast.js +115 -0
  29. package/dist/transformer/ast.js.map +1 -0
  30. package/dist/transformer/diagnostics.d.ts +17 -0
  31. package/dist/transformer/diagnostics.d.ts.map +1 -0
  32. package/dist/transformer/diagnostics.js +30 -0
  33. package/dist/transformer/diagnostics.js.map +1 -0
  34. package/dist/transformer/domains.d.ts +11 -0
  35. package/dist/transformer/domains.d.ts.map +1 -0
  36. package/dist/transformer/domains.js +32 -0
  37. package/dist/transformer/domains.js.map +1 -0
  38. package/dist/transformer/index.d.ts +10 -0
  39. package/dist/transformer/index.d.ts.map +1 -0
  40. package/dist/transformer/index.js +60 -0
  41. package/dist/transformer/index.js.map +1 -0
  42. package/dist/transformer/operators.d.ts +16 -0
  43. package/dist/transformer/operators.d.ts.map +1 -0
  44. package/dist/transformer/operators.js +44 -0
  45. package/dist/transformer/operators.js.map +1 -0
  46. package/dist/transformer/options.d.ts +19 -0
  47. package/dist/transformer/options.d.ts.map +1 -0
  48. package/dist/transformer/options.js +17 -0
  49. package/dist/transformer/options.js.map +1 -0
  50. package/dist/transformer/symbols.d.ts +56 -0
  51. package/dist/transformer/symbols.d.ts.map +1 -0
  52. package/dist/transformer/symbols.js +270 -0
  53. package/dist/transformer/symbols.js.map +1 -0
  54. package/dist/transformer/transform/erase-imports.d.ts +14 -0
  55. package/dist/transformer/transform/erase-imports.d.ts.map +1 -0
  56. package/dist/transformer/transform/erase-imports.js +174 -0
  57. package/dist/transformer/transform/erase-imports.js.map +1 -0
  58. package/dist/transformer/transform/generated-coercions.d.ts +9 -0
  59. package/dist/transformer/transform/generated-coercions.d.ts.map +1 -0
  60. package/dist/transformer/transform/generated-coercions.js +22 -0
  61. package/dist/transformer/transform/generated-coercions.js.map +1 -0
  62. package/dist/transformer/transform/optimize-coercions.d.ts +11 -0
  63. package/dist/transformer/transform/optimize-coercions.d.ts.map +1 -0
  64. package/dist/transformer/transform/optimize-coercions.js +1702 -0
  65. package/dist/transformer/transform/optimize-coercions.js.map +1 -0
  66. package/dist/transformer/transform/transform-declaration-file.d.ts +9 -0
  67. package/dist/transformer/transform/transform-declaration-file.d.ts.map +1 -0
  68. package/dist/transformer/transform/transform-declaration-file.js +376 -0
  69. package/dist/transformer/transform/transform-declaration-file.js.map +1 -0
  70. package/dist/transformer/transform/transform-expression.d.ts +24 -0
  71. package/dist/transformer/transform/transform-expression.d.ts.map +1 -0
  72. package/dist/transformer/transform/transform-expression.js +545 -0
  73. package/dist/transformer/transform/transform-expression.js.map +1 -0
  74. package/dist/transformer/transform/transform-source-file.d.ts +10 -0
  75. package/dist/transformer/transform/transform-source-file.d.ts.map +1 -0
  76. package/dist/transformer/transform/transform-source-file.js +52 -0
  77. package/dist/transformer/transform/transform-source-file.js.map +1 -0
  78. package/dist/transformer/ts-compat.d.ts +4 -0
  79. package/dist/transformer/ts-compat.d.ts.map +1 -0
  80. package/dist/transformer/ts-compat.js +24 -0
  81. package/dist/transformer/ts-compat.js.map +1 -0
  82. package/docs/implementation-plan.md +335 -0
  83. package/docs/lib-implementation.md +77 -0
  84. package/docs/lowering-optimization-spec.md +1020 -0
  85. package/docs/project-structure.md +52 -0
  86. package/docs/transform-spec.md +2114 -0
  87. package/package.json +83 -0
@@ -0,0 +1,804 @@
1
+ import * as ts from "typescript";
2
+ import { isNumericTypeDomain } from "./type-domain.js";
3
+ import { createDiagnostic } from "../diagnostics.js";
4
+ import { getTransparentDomainWrapperExpression, isTransparentDomainWrapper } from "../ast.js";
5
+ import { sortNumericDomains } from "../domains.js";
6
+ import { supportsBitwiseOperator } from "../operators.js";
7
+ import { getTypeDomain } from "./type-domain.js";
8
+ import { getNumtypeBindingName } from "../symbols.js";
9
+ const unaryArithmeticOperatorBySyntaxKind = new Map([
10
+ [ts.SyntaxKind.PlusToken, "+"],
11
+ [ts.SyntaxKind.MinusToken, "-"]
12
+ ]);
13
+ const binaryArithmeticOperatorBySyntaxKind = new Map([
14
+ [ts.SyntaxKind.PlusToken, "+"],
15
+ [ts.SyntaxKind.MinusToken, "-"],
16
+ [ts.SyntaxKind.AsteriskToken, "*"],
17
+ [ts.SyntaxKind.SlashToken, "/"],
18
+ [ts.SyntaxKind.PercentToken, "%"],
19
+ [ts.SyntaxKind.AsteriskAsteriskToken, "**"]
20
+ ]);
21
+ const integerBitwiseOperatorBySyntaxKind = new Map([
22
+ [ts.SyntaxKind.AmpersandToken, "&"],
23
+ [ts.SyntaxKind.BarToken, "|"],
24
+ [ts.SyntaxKind.CaretToken, "^"],
25
+ [ts.SyntaxKind.LessThanLessThanToken, "<<"],
26
+ [ts.SyntaxKind.GreaterThanGreaterThanToken, ">>"],
27
+ [ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken, ">>>"]
28
+ ]);
29
+ const compoundArithmeticOperatorBySyntaxKind = new Map([
30
+ [ts.SyntaxKind.PlusEqualsToken, "+"],
31
+ [ts.SyntaxKind.MinusEqualsToken, "-"],
32
+ [ts.SyntaxKind.AsteriskEqualsToken, "*"],
33
+ [ts.SyntaxKind.SlashEqualsToken, "/"],
34
+ [ts.SyntaxKind.PercentEqualsToken, "%"],
35
+ [ts.SyntaxKind.AsteriskAsteriskEqualsToken, "**"]
36
+ ]);
37
+ const compoundBitwiseOperatorBySyntaxKind = new Map([
38
+ [ts.SyntaxKind.AmpersandEqualsToken, "&"],
39
+ [ts.SyntaxKind.BarEqualsToken, "|"],
40
+ [ts.SyntaxKind.CaretEqualsToken, "^"],
41
+ [ts.SyntaxKind.LessThanLessThanEqualsToken, "<<"],
42
+ [ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, ">>"],
43
+ [ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, ">>>"]
44
+ ]);
45
+ export function createExpressionDomainCache() {
46
+ return new WeakMap();
47
+ }
48
+ export function getExpressionDomain(input) {
49
+ return analyzeExpression({
50
+ ...input,
51
+ cache: input.cache ?? createExpressionDomainCache()
52
+ });
53
+ }
54
+ function analyzeExpression(input) {
55
+ const expression = skipParentheses(input.expression);
56
+ const cachedResult = getCachedResult(input.cache, expression, input.contextualDomain);
57
+ if (cachedResult !== undefined) {
58
+ return cachedResult;
59
+ }
60
+ const result = analyzeExpressionUncached({
61
+ ...input,
62
+ expression
63
+ });
64
+ setCachedResult(input.cache, expression, input.contextualDomain, result);
65
+ return result;
66
+ }
67
+ function analyzeExpressionUncached(input) {
68
+ const expression = input.expression;
69
+ if (isContextualNumericLiteral(expression, input.contextualDomain)) {
70
+ return createExpressionResult({
71
+ diagnostics: [],
72
+ domain: input.contextualDomain,
73
+ reason: "contextual-literal",
74
+ requiresClosure: true
75
+ });
76
+ }
77
+ if (isNumericLiteralLike(expression)) {
78
+ return createExpressionResult({
79
+ diagnostics: [],
80
+ domain: "plain-number",
81
+ reason: "literal",
82
+ requiresClosure: false
83
+ });
84
+ }
85
+ if (isTransparentDomainWrapper(expression)) {
86
+ return analyzeExpression({
87
+ cache: input.cache,
88
+ checker: input.checker,
89
+ contextualDomain: input.contextualDomain,
90
+ expression: getTransparentDomainWrapperExpression(expression),
91
+ symbols: input.symbols
92
+ });
93
+ }
94
+ if (ts.isCallExpression(expression)) {
95
+ return analyzeCallExpression(input, expression);
96
+ }
97
+ if (ts.isPrefixUnaryExpression(expression)) {
98
+ const updateResult = analyzeUpdateExpression(input, expression);
99
+ if (updateResult !== undefined) {
100
+ return updateResult;
101
+ }
102
+ return analyzePrefixUnaryExpression(input, expression);
103
+ }
104
+ if (ts.isPostfixUnaryExpression(expression)) {
105
+ const updateResult = analyzeUpdateExpression(input, expression);
106
+ if (updateResult !== undefined) {
107
+ return updateResult;
108
+ }
109
+ }
110
+ if (ts.isBinaryExpression(expression)) {
111
+ return analyzeBinaryExpression(input, expression);
112
+ }
113
+ if (ts.isConditionalExpression(expression)) {
114
+ return analyzeConditionalExpression(input, expression);
115
+ }
116
+ return analyzeExpressionType(input, expression);
117
+ }
118
+ function analyzeUpdateExpression(input, expression) {
119
+ const operator = expression.operator;
120
+ if (!isUpdateOperator(operator)) {
121
+ return undefined;
122
+ }
123
+ const operand = analyzeExpression({
124
+ cache: input.cache,
125
+ checker: input.checker,
126
+ expression: expression.operand,
127
+ symbols: input.symbols
128
+ });
129
+ const unionOperationDiagnostic = getUnsupportedUnionOperationDiagnostic(expression, getUpdateOperatorVerb(operator), operand);
130
+ if (unionOperationDiagnostic !== undefined) {
131
+ return createExpressionResult({
132
+ diagnostics: [...operand.diagnostics, unionOperationDiagnostic],
133
+ domain: "unknown",
134
+ hasNonNumericUnionMembers: operand.hasNonNumericUnionMembers,
135
+ reason: "unknown",
136
+ requiresClosure: false,
137
+ unionDomains: operand.unionDomains
138
+ });
139
+ }
140
+ if (!isNumericTypeDomain(operand.domain)) {
141
+ return createExpressionResult({
142
+ diagnostics: operand.diagnostics,
143
+ domain: operand.domain,
144
+ hasNonNumericUnionMembers: operand.hasNonNumericUnionMembers,
145
+ reason: operand.reason,
146
+ requiresClosure: false,
147
+ unionDomains: operand.unionDomains
148
+ });
149
+ }
150
+ return createExpressionResult({
151
+ diagnostics: operand.diagnostics,
152
+ domain: operand.domain,
153
+ reason: "operator",
154
+ requiresClosure: true
155
+ });
156
+ }
157
+ function analyzeCallExpression(input, expression) {
158
+ const binding = input.symbols.getBindingForCastCallee(expression.expression);
159
+ if (binding === undefined) {
160
+ return analyzeExpressionType(input, expression);
161
+ }
162
+ if (binding.isTypeOnly) {
163
+ return createExpressionResult({
164
+ diagnostics: [
165
+ createDiagnostic("NUMTYPES_UNKNOWN_SYMBOL", `cannot use type-only import '${getNumtypeBindingName(binding)}' as a numtypes cast function`, getDiagnosticLocation(expression.expression))
166
+ ],
167
+ domain: "unknown",
168
+ reason: "unknown",
169
+ requiresClosure: false
170
+ });
171
+ }
172
+ const argumentDiagnostics = expression.arguments.flatMap((argument) => analyzeExpression({
173
+ cache: input.cache,
174
+ checker: input.checker,
175
+ expression: argument,
176
+ symbols: input.symbols
177
+ }).diagnostics);
178
+ const castCallDiagnostic = getCastCallMisuseDiagnostic(expression, binding);
179
+ if (castCallDiagnostic !== undefined) {
180
+ return createExpressionResult({
181
+ diagnostics: [...argumentDiagnostics, castCallDiagnostic],
182
+ domain: "unknown",
183
+ reason: "unknown",
184
+ requiresClosure: false
185
+ });
186
+ }
187
+ return createExpressionResult({
188
+ diagnostics: argumentDiagnostics,
189
+ domain: binding.domain,
190
+ reason: "cast-call",
191
+ requiresClosure: true
192
+ });
193
+ }
194
+ function getCastCallMisuseDiagnostic(expression, binding) {
195
+ const bindingName = getNumtypeBindingName(binding);
196
+ if (expression.questionDotToken !== undefined ||
197
+ hasOptionalPropertyAccessCallee(expression.expression) ||
198
+ expression.typeArguments !== undefined) {
199
+ return createDiagnostic("NUMTYPES_ERASED_CAST_MISUSE", `numtypes cast function '${bindingName}' must be called directly as ${bindingName}(value)`, getDiagnosticLocation(expression.expression));
200
+ }
201
+ if (expression.arguments.length !== 1) {
202
+ return createDiagnostic("NUMTYPES_ERASED_CAST_MISUSE", `numtypes cast function '${bindingName}' must be called with exactly one argument`, getDiagnosticLocation(expression.expression));
203
+ }
204
+ const argument = expression.arguments[0];
205
+ if (argument !== undefined && ts.isSpreadElement(argument)) {
206
+ return createDiagnostic("NUMTYPES_ERASED_CAST_MISUSE", `numtypes cast function '${bindingName}' must be called directly as ${bindingName}(value)`, getDiagnosticLocation(expression.expression));
207
+ }
208
+ return undefined;
209
+ }
210
+ function hasOptionalPropertyAccessCallee(expression) {
211
+ return (ts.isPropertyAccessExpression(expression) &&
212
+ expression.questionDotToken !== undefined);
213
+ }
214
+ function analyzePrefixUnaryExpression(input, expression) {
215
+ const operator = getUnaryArithmeticOperator(expression.operator);
216
+ if (operator === undefined) {
217
+ return analyzeExpressionType(input, expression);
218
+ }
219
+ const operand = analyzeExpression({
220
+ cache: input.cache,
221
+ checker: input.checker,
222
+ expression: expression.operand,
223
+ symbols: input.symbols
224
+ });
225
+ const unionOperationDiagnostic = getUnsupportedUnionOperationDiagnostic(expression, getUnaryOperatorVerb(operator), operand);
226
+ if (unionOperationDiagnostic !== undefined) {
227
+ return createExpressionResult({
228
+ diagnostics: [...operand.diagnostics, unionOperationDiagnostic],
229
+ domain: "unknown",
230
+ hasNonNumericUnionMembers: operand.hasNonNumericUnionMembers,
231
+ reason: "unknown",
232
+ requiresClosure: false,
233
+ unionDomains: operand.unionDomains
234
+ });
235
+ }
236
+ if (!isNumericTypeDomain(operand.domain)) {
237
+ return createExpressionResult({
238
+ diagnostics: operand.diagnostics,
239
+ domain: operand.domain,
240
+ hasNonNumericUnionMembers: operand.hasNonNumericUnionMembers,
241
+ reason: operand.reason,
242
+ requiresClosure: false,
243
+ unionDomains: operand.unionDomains
244
+ });
245
+ }
246
+ return createExpressionResult({
247
+ diagnostics: operand.diagnostics,
248
+ domain: operand.domain,
249
+ reason: "operator",
250
+ requiresClosure: true
251
+ });
252
+ }
253
+ function analyzeBinaryExpression(input, expression) {
254
+ const arithmeticOperator = getBinaryArithmeticOperator(expression.operatorToken.kind);
255
+ if (arithmeticOperator !== undefined) {
256
+ return analyzeBinaryOperation(input, expression, {
257
+ kind: "binary-arithmetic",
258
+ operator: arithmeticOperator
259
+ });
260
+ }
261
+ const bitwiseOperator = getIntegerBitwiseOperator(expression.operatorToken.kind);
262
+ if (bitwiseOperator !== undefined) {
263
+ return analyzeBinaryOperation(input, expression, {
264
+ kind: "bitwise",
265
+ operator: bitwiseOperator
266
+ });
267
+ }
268
+ const compoundArithmeticOperator = getCompoundArithmeticOperator(expression.operatorToken.kind);
269
+ if (compoundArithmeticOperator !== undefined) {
270
+ return analyzeCompoundAssignmentExpression(input, expression);
271
+ }
272
+ const compoundBitwiseOperator = getCompoundBitwiseOperator(expression.operatorToken.kind);
273
+ if (compoundBitwiseOperator !== undefined) {
274
+ return analyzeCompoundAssignmentExpression(input, expression);
275
+ }
276
+ return analyzeExpressionType(input, expression);
277
+ }
278
+ function analyzeCompoundAssignmentExpression(input, expression) {
279
+ const left = analyzeExpression({
280
+ cache: input.cache,
281
+ checker: input.checker,
282
+ expression: expression.left,
283
+ symbols: input.symbols
284
+ });
285
+ const right = analyzeExpression({
286
+ cache: input.cache,
287
+ checker: input.checker,
288
+ expression: expression.right,
289
+ symbols: input.symbols
290
+ });
291
+ const unionDomains = getMergedExpressionResultDomains([left, right]);
292
+ const hasNonNumericUnionMembers = left.hasNonNumericUnionMembers === true ||
293
+ right.hasNonNumericUnionMembers === true;
294
+ if (!isNumericTypeDomain(left.domain) &&
295
+ !isNumericTypeDomain(right.domain) &&
296
+ unionDomains === undefined) {
297
+ return analyzeExpressionType(input, expression);
298
+ }
299
+ return createExpressionResult({
300
+ diagnostics: [
301
+ ...left.diagnostics,
302
+ ...right.diagnostics,
303
+ createDiagnostic("NUMTYPES_UNSUPPORTED_COMPOUND_ASSIGNMENT", `cannot use compound assignment '${getCompoundAssignmentText(expression.operatorToken.kind)}' with numtypes values; use an explicit checked assignment`, getDiagnosticLocation(expression))
304
+ ],
305
+ domain: "unknown",
306
+ hasNonNumericUnionMembers,
307
+ reason: "unknown",
308
+ requiresClosure: false,
309
+ unionDomains
310
+ });
311
+ }
312
+ function analyzeConditionalExpression(input, expression) {
313
+ const condition = analyzeExpression({
314
+ cache: input.cache,
315
+ checker: input.checker,
316
+ expression: expression.condition,
317
+ symbols: input.symbols
318
+ });
319
+ const trueWithoutContext = analyzeExpression({
320
+ cache: input.cache,
321
+ checker: input.checker,
322
+ expression: expression.whenTrue,
323
+ symbols: input.symbols
324
+ });
325
+ const falseWithoutContext = analyzeExpression({
326
+ cache: input.cache,
327
+ checker: input.checker,
328
+ expression: expression.whenFalse,
329
+ symbols: input.symbols
330
+ });
331
+ const contextualDomain = input.contextualDomain ??
332
+ getConditionalBranchContextualDomain(trueWithoutContext.domain, falseWithoutContext.domain);
333
+ const whenTrue = analyzeExpression({
334
+ cache: input.cache,
335
+ checker: input.checker,
336
+ contextualDomain,
337
+ expression: expression.whenTrue,
338
+ symbols: input.symbols
339
+ });
340
+ const whenFalse = analyzeExpression({
341
+ cache: input.cache,
342
+ checker: input.checker,
343
+ contextualDomain,
344
+ expression: expression.whenFalse,
345
+ symbols: input.symbols
346
+ });
347
+ const numericUnion = getConditionalResultUnion(whenTrue, whenFalse);
348
+ const diagnostics = [
349
+ ...condition.diagnostics,
350
+ ...whenTrue.diagnostics,
351
+ ...whenFalse.diagnostics
352
+ ];
353
+ const domainError = input.contextualDomain === undefined
354
+ ? getConditionalDomainErrorDiagnostic(expression, whenTrue, whenFalse)
355
+ : undefined;
356
+ if (domainError !== undefined) {
357
+ return createExpressionResult({
358
+ diagnostics: [...diagnostics, domainError],
359
+ domain: "unknown",
360
+ reason: "unknown",
361
+ requiresClosure: false,
362
+ hasNonNumericUnionMembers: numericUnion?.hasNonNumericMembers,
363
+ unionDomains: numericUnion?.domains
364
+ });
365
+ }
366
+ const domain = getConditionalResultDomain(whenTrue.domain, whenFalse.domain, contextualDomain);
367
+ return createExpressionResult({
368
+ diagnostics,
369
+ domain,
370
+ reason: domain === "unknown" ? "unknown" : "operator",
371
+ requiresClosure: isNumericTypeDomain(domain) || numericUnion !== undefined,
372
+ hasNonNumericUnionMembers: numericUnion?.hasNonNumericMembers,
373
+ unionDomains: numericUnion?.domains
374
+ });
375
+ }
376
+ function getConditionalDomainErrorDiagnostic(expression, whenTrue, whenFalse) {
377
+ if (isNumericTypeDomain(whenTrue.domain) && isNumericTypeDomain(whenFalse.domain)) {
378
+ if (whenTrue.domain !== whenFalse.domain) {
379
+ return undefined;
380
+ }
381
+ return undefined;
382
+ }
383
+ if (isNumericTypeDomain(whenTrue.domain) && whenFalse.domain === "plain-number") {
384
+ return createDiagnostic("NUMTYPES_IMPLICIT_NUMBER", `cannot select ${whenTrue.domain} and number without explicit cast`, getDiagnosticLocation(expression));
385
+ }
386
+ if (whenTrue.domain === "plain-number" && isNumericTypeDomain(whenFalse.domain)) {
387
+ return createDiagnostic("NUMTYPES_IMPLICIT_NUMBER", `cannot select number and ${whenFalse.domain} without explicit cast`, getDiagnosticLocation(expression));
388
+ }
389
+ const trueUnionCandidate = getExpressionNumericUnionCandidate(whenTrue);
390
+ const falseUnionCandidate = getExpressionNumericUnionCandidate(whenFalse);
391
+ if (trueUnionCandidate?.isUnion === true &&
392
+ whenFalse.domain === "plain-number") {
393
+ return createDiagnostic("NUMTYPES_IMPLICIT_NUMBER", `cannot select union containing ${trueUnionCandidate.domains.join(" | ")} and number without explicit cast`, getDiagnosticLocation(expression));
394
+ }
395
+ if (whenTrue.domain === "plain-number" &&
396
+ falseUnionCandidate?.isUnion === true) {
397
+ return createDiagnostic("NUMTYPES_IMPLICIT_NUMBER", `cannot select number and union containing ${falseUnionCandidate.domains.join(" | ")} without explicit cast`, getDiagnosticLocation(expression));
398
+ }
399
+ return undefined;
400
+ }
401
+ function getConditionalResultUnion(whenTrue, whenFalse) {
402
+ const trueCandidate = getExpressionNumericUnionCandidate(whenTrue);
403
+ const falseCandidate = getExpressionNumericUnionCandidate(whenFalse);
404
+ if (trueCandidate === undefined && falseCandidate === undefined) {
405
+ return undefined;
406
+ }
407
+ if (trueCandidate === undefined) {
408
+ if (whenTrue.domain === "plain-number") {
409
+ return undefined;
410
+ }
411
+ return {
412
+ domains: falseCandidate?.domains ?? [],
413
+ hasNonNumericMembers: true,
414
+ isUnion: true
415
+ };
416
+ }
417
+ if (falseCandidate === undefined) {
418
+ if (whenFalse.domain === "plain-number") {
419
+ return undefined;
420
+ }
421
+ return {
422
+ domains: trueCandidate.domains,
423
+ hasNonNumericMembers: true,
424
+ isUnion: true
425
+ };
426
+ }
427
+ const domains = new Set([
428
+ ...trueCandidate.domains,
429
+ ...falseCandidate.domains
430
+ ]);
431
+ const hasNonNumericMembers = trueCandidate.hasNonNumericMembers ||
432
+ falseCandidate.hasNonNumericMembers;
433
+ const isUnion = trueCandidate.isUnion ||
434
+ falseCandidate.isUnion ||
435
+ domains.size > 1 ||
436
+ hasNonNumericMembers;
437
+ if (!isUnion) {
438
+ return undefined;
439
+ }
440
+ return {
441
+ domains: sortNumericDomains(domains),
442
+ hasNonNumericMembers,
443
+ isUnion: true
444
+ };
445
+ }
446
+ function getExpressionNumericUnionCandidate(result) {
447
+ if (isNumericTypeDomain(result.domain)) {
448
+ return {
449
+ domains: [result.domain],
450
+ hasNonNumericMembers: false,
451
+ isUnion: false
452
+ };
453
+ }
454
+ if (result.unionDomains !== undefined && result.unionDomains.length > 0) {
455
+ return {
456
+ domains: result.unionDomains,
457
+ hasNonNumericMembers: result.hasNonNumericUnionMembers === true,
458
+ isUnion: true
459
+ };
460
+ }
461
+ return undefined;
462
+ }
463
+ function analyzeBinaryOperation(input, expression, operation) {
464
+ const leftWithoutContext = analyzeExpression({
465
+ cache: input.cache,
466
+ checker: input.checker,
467
+ expression: expression.left,
468
+ symbols: input.symbols
469
+ });
470
+ const rightWithoutContext = analyzeExpression({
471
+ cache: input.cache,
472
+ checker: input.checker,
473
+ expression: expression.right,
474
+ symbols: input.symbols
475
+ });
476
+ const contextualDomain = getBinaryContextualDomain(leftWithoutContext.domain, rightWithoutContext.domain);
477
+ const left = analyzeBinaryOperand(input, expression.left, contextualDomain);
478
+ const right = analyzeBinaryOperand(input, expression.right, contextualDomain);
479
+ const diagnostics = [
480
+ ...left.diagnostics,
481
+ ...right.diagnostics
482
+ ];
483
+ const unionDomains = getMergedAnalyzedOperandDomains([left, right]);
484
+ const hasNonNumericUnionMembers = [left, right].some((operand) => operand.hasNonNumericUnionMembers === true);
485
+ const resultDomain = getBinaryResultDomain(left.domain, right.domain);
486
+ const domainError = getDomainErrorDiagnostic(expression, operation.operator, left, right);
487
+ if (domainError !== undefined) {
488
+ diagnostics.push(domainError);
489
+ return createExpressionResult({
490
+ diagnostics,
491
+ domain: "unknown",
492
+ hasNonNumericUnionMembers,
493
+ reason: "unknown",
494
+ requiresClosure: false,
495
+ unionDomains
496
+ });
497
+ }
498
+ if (resultDomain === "plain-number" || resultDomain === "unknown") {
499
+ return createExpressionResult({
500
+ diagnostics,
501
+ domain: resultDomain,
502
+ hasNonNumericUnionMembers,
503
+ reason: resultDomain === "plain-number" ? "type" : "unknown",
504
+ requiresClosure: false,
505
+ unionDomains
506
+ });
507
+ }
508
+ if (operation.kind === "bitwise" &&
509
+ !supportsBitwiseOperator(resultDomain, operation.operator)) {
510
+ diagnostics.push(createDiagnostic("NUMTYPES_BITWISE_FLOAT", `cannot apply bitwise operator '${operation.operator}' to ${resultDomain}`, getDiagnosticLocation(expression)));
511
+ return createExpressionResult({
512
+ diagnostics,
513
+ domain: "unknown",
514
+ hasNonNumericUnionMembers,
515
+ reason: "unknown",
516
+ requiresClosure: false,
517
+ unionDomains
518
+ });
519
+ }
520
+ return createExpressionResult({
521
+ diagnostics,
522
+ domain: resultDomain,
523
+ reason: "operator",
524
+ requiresClosure: true
525
+ });
526
+ }
527
+ function analyzeBinaryOperand(input, expression, contextualDomain) {
528
+ const result = analyzeExpression({
529
+ cache: input.cache,
530
+ checker: input.checker,
531
+ contextualDomain,
532
+ expression,
533
+ symbols: input.symbols
534
+ });
535
+ return {
536
+ diagnostics: result.diagnostics,
537
+ domain: result.domain,
538
+ expression,
539
+ hasNonNumericUnionMembers: result.hasNonNumericUnionMembers,
540
+ isContextualLiteral: result.reason === "contextual-literal",
541
+ unionDomains: result.unionDomains
542
+ };
543
+ }
544
+ function analyzeExpressionType(input, expression) {
545
+ const typeResult = getTypeDomain({
546
+ checker: input.checker,
547
+ node: expression,
548
+ symbols: input.symbols,
549
+ type: input.checker.getTypeAtLocation(expression)
550
+ });
551
+ return createExpressionResult({
552
+ diagnostics: typeResult.diagnostics,
553
+ domain: typeResult.domain,
554
+ hasNonNumericUnionMembers: typeResult.hasNonNumericUnionMembers,
555
+ reason: typeResult.domain === "unknown" ? "unknown" : "type",
556
+ requiresClosure: isNumericTypeDomain(typeResult.domain),
557
+ unionDomains: typeResult.unionDomains
558
+ });
559
+ }
560
+ function createExpressionResult(input) {
561
+ const result = {
562
+ diagnostics: input.diagnostics,
563
+ domain: input.domain,
564
+ isDomainBound: isNumericTypeDomain(input.domain) ||
565
+ (input.unionDomains !== undefined && input.unionDomains.length > 0),
566
+ reason: input.reason,
567
+ requiresClosure: input.requiresClosure
568
+ };
569
+ if (input.unionDomains !== undefined && input.unionDomains.length > 0) {
570
+ return {
571
+ ...result,
572
+ hasNonNumericUnionMembers: input.hasNonNumericUnionMembers === true,
573
+ unionDomains: input.unionDomains
574
+ };
575
+ }
576
+ return result;
577
+ }
578
+ function getMergedAnalyzedOperandDomains(operands) {
579
+ const domains = new Set();
580
+ for (const operand of operands) {
581
+ for (const domain of operand.unionDomains ?? []) {
582
+ domains.add(domain);
583
+ }
584
+ }
585
+ return domains.size === 0 ? undefined : sortNumericDomains(domains);
586
+ }
587
+ function getMergedExpressionResultDomains(results) {
588
+ const domains = new Set();
589
+ for (const result of results) {
590
+ if (isNumericTypeDomain(result.domain)) {
591
+ domains.add(result.domain);
592
+ }
593
+ for (const domain of result.unionDomains ?? []) {
594
+ domains.add(domain);
595
+ }
596
+ }
597
+ return domains.size === 0 ? undefined : sortNumericDomains(domains);
598
+ }
599
+ function getBinaryContextualDomain(leftDomain, rightDomain) {
600
+ if (isNumericTypeDomain(leftDomain) && rightDomain === "plain-number") {
601
+ return leftDomain;
602
+ }
603
+ if (isNumericTypeDomain(rightDomain) && leftDomain === "plain-number") {
604
+ return rightDomain;
605
+ }
606
+ return undefined;
607
+ }
608
+ function getConditionalBranchContextualDomain(trueDomain, falseDomain) {
609
+ if (isNumericTypeDomain(trueDomain) && falseDomain === "plain-number") {
610
+ return trueDomain;
611
+ }
612
+ if (isNumericTypeDomain(falseDomain) && trueDomain === "plain-number") {
613
+ return falseDomain;
614
+ }
615
+ return undefined;
616
+ }
617
+ function getBinaryResultDomain(leftDomain, rightDomain) {
618
+ if (isNumericTypeDomain(leftDomain) && isNumericTypeDomain(rightDomain)) {
619
+ return leftDomain === rightDomain ? leftDomain : "unknown";
620
+ }
621
+ if (leftDomain === "plain-number" && rightDomain === "plain-number") {
622
+ return "plain-number";
623
+ }
624
+ if (isNumericTypeDomain(leftDomain) && rightDomain === "plain-number") {
625
+ return leftDomain;
626
+ }
627
+ if (leftDomain === "plain-number" && isNumericTypeDomain(rightDomain)) {
628
+ return rightDomain;
629
+ }
630
+ return "unknown";
631
+ }
632
+ function getConditionalResultDomain(trueDomain, falseDomain, contextualDomain) {
633
+ if (contextualDomain !== undefined &&
634
+ trueDomain === contextualDomain &&
635
+ falseDomain === contextualDomain) {
636
+ return contextualDomain;
637
+ }
638
+ if (isNumericTypeDomain(trueDomain) && isNumericTypeDomain(falseDomain)) {
639
+ return trueDomain === falseDomain ? trueDomain : "unknown";
640
+ }
641
+ if (trueDomain === "plain-number" && falseDomain === "plain-number") {
642
+ return "plain-number";
643
+ }
644
+ return "unknown";
645
+ }
646
+ function getCachedResult(cache, expression, contextualDomain) {
647
+ return cache.get(expression)?.get(getCacheKey(contextualDomain));
648
+ }
649
+ function setCachedResult(cache, expression, contextualDomain, result) {
650
+ const cacheKey = getCacheKey(contextualDomain);
651
+ const expressionCache = cache.get(expression) ?? new Map();
652
+ expressionCache.set(cacheKey, result);
653
+ cache.set(expression, expressionCache);
654
+ }
655
+ function getCacheKey(contextualDomain) {
656
+ return contextualDomain ?? "";
657
+ }
658
+ function getDomainErrorDiagnostic(expression, operator, left, right) {
659
+ const unionDomains = getMergedAnalyzedOperandDomains([left, right]);
660
+ if (unionDomains !== undefined) {
661
+ return createDiagnostic("NUMTYPES_UNSUPPORTED_UNION", `cannot ${getOperatorVerb(operator)} union containing ${unionDomains.join(" | ")}; narrow or explicitly cast before the operation`, getDiagnosticLocation(expression));
662
+ }
663
+ const leftDomain = left.domain;
664
+ const rightDomain = right.domain;
665
+ if (isNumericTypeDomain(leftDomain) && isNumericTypeDomain(rightDomain)) {
666
+ if (leftDomain !== rightDomain) {
667
+ return createDiagnostic("NUMTYPES_MIXED_DOMAIN", `cannot ${getOperatorVerb(operator)} ${leftDomain} and ${rightDomain} without explicit cast`, getDiagnosticLocation(expression));
668
+ }
669
+ return undefined;
670
+ }
671
+ if (isNumericTypeDomain(leftDomain) &&
672
+ rightDomain === "plain-number" &&
673
+ !right.isContextualLiteral) {
674
+ return createDiagnostic("NUMTYPES_IMPLICIT_NUMBER", `cannot ${getOperatorVerb(operator)} ${leftDomain} and number without explicit cast`, getDiagnosticLocation(expression));
675
+ }
676
+ if (leftDomain === "plain-number" &&
677
+ isNumericTypeDomain(rightDomain) &&
678
+ !left.isContextualLiteral) {
679
+ return createDiagnostic("NUMTYPES_IMPLICIT_NUMBER", `cannot ${getOperatorVerb(operator)} number and ${rightDomain} without explicit cast`, getDiagnosticLocation(expression));
680
+ }
681
+ return undefined;
682
+ }
683
+ function isContextualNumericLiteral(expression, contextualDomain) {
684
+ if (contextualDomain === undefined || !isNumericLiteralLike(expression)) {
685
+ return false;
686
+ }
687
+ const numericValue = getNumericLiteralValue(expression);
688
+ if (contextualDomain === "f32" || contextualDomain === "f64") {
689
+ return true;
690
+ }
691
+ if (!Number.isInteger(numericValue)) {
692
+ return false;
693
+ }
694
+ if (contextualDomain === "u32" && numericValue < 0) {
695
+ return false;
696
+ }
697
+ return true;
698
+ }
699
+ function isNumericLiteralLike(expression) {
700
+ if (ts.isNumericLiteral(expression)) {
701
+ return true;
702
+ }
703
+ return (ts.isPrefixUnaryExpression(expression) &&
704
+ (expression.operator === ts.SyntaxKind.MinusToken ||
705
+ expression.operator === ts.SyntaxKind.PlusToken) &&
706
+ ts.isNumericLiteral(expression.operand));
707
+ }
708
+ function getNumericLiteralValue(expression) {
709
+ if (ts.isNumericLiteral(expression)) {
710
+ return Number(expression.text);
711
+ }
712
+ if (ts.isPrefixUnaryExpression(expression) &&
713
+ ts.isNumericLiteral(expression.operand)) {
714
+ const value = Number(expression.operand.text);
715
+ return expression.operator === ts.SyntaxKind.MinusToken ? -value : value;
716
+ }
717
+ return Number.NaN;
718
+ }
719
+ function getUnaryArithmeticOperator(operator) {
720
+ return unaryArithmeticOperatorBySyntaxKind.get(operator);
721
+ }
722
+ function getBinaryArithmeticOperator(operator) {
723
+ return binaryArithmeticOperatorBySyntaxKind.get(operator);
724
+ }
725
+ function getIntegerBitwiseOperator(operator) {
726
+ return integerBitwiseOperatorBySyntaxKind.get(operator);
727
+ }
728
+ function getCompoundArithmeticOperator(operator) {
729
+ return compoundArithmeticOperatorBySyntaxKind.get(operator);
730
+ }
731
+ function getCompoundBitwiseOperator(operator) {
732
+ return compoundBitwiseOperatorBySyntaxKind.get(operator);
733
+ }
734
+ function getCompoundAssignmentText(operator) {
735
+ return ts.tokenToString(operator) ?? "<unknown>";
736
+ }
737
+ function getOperatorVerb(operator) {
738
+ switch (operator) {
739
+ case "+":
740
+ return "add";
741
+ case "-":
742
+ return "subtract";
743
+ case "*":
744
+ return "multiply";
745
+ case "/":
746
+ return "divide";
747
+ case "%":
748
+ return "remainder";
749
+ case "**":
750
+ return "exponentiate";
751
+ case "&":
752
+ case "|":
753
+ case "^":
754
+ case "<<":
755
+ case ">>":
756
+ case ">>>":
757
+ return `apply operator '${operator}' to`;
758
+ }
759
+ }
760
+ function getUnaryOperatorVerb(operator) {
761
+ switch (operator) {
762
+ case "+":
763
+ return "apply unary '+' to";
764
+ case "-":
765
+ return "negate";
766
+ }
767
+ }
768
+ function getUpdateOperatorVerb(operator) {
769
+ switch (operator) {
770
+ case ts.SyntaxKind.PlusPlusToken:
771
+ return "increment";
772
+ case ts.SyntaxKind.MinusMinusToken:
773
+ return "decrement";
774
+ }
775
+ }
776
+ function isUpdateOperator(operator) {
777
+ return (operator === ts.SyntaxKind.PlusPlusToken ||
778
+ operator === ts.SyntaxKind.MinusMinusToken);
779
+ }
780
+ function getUnsupportedUnionOperationDiagnostic(expression, operationVerb, operand) {
781
+ if (operand.unionDomains !== undefined && operand.unionDomains.length > 0) {
782
+ return createDiagnostic("NUMTYPES_UNSUPPORTED_UNION", `cannot ${operationVerb} union containing ${operand.unionDomains.join(" | ")}; narrow or explicitly cast before the operation`, getDiagnosticLocation(expression));
783
+ }
784
+ return undefined;
785
+ }
786
+ function skipParentheses(expression) {
787
+ let current = expression;
788
+ while (ts.isParenthesizedExpression(current)) {
789
+ current = current.expression;
790
+ }
791
+ return current;
792
+ }
793
+ function getDiagnosticLocation(node) {
794
+ if (node === undefined) {
795
+ return undefined;
796
+ }
797
+ const sourceFile = node.getSourceFile();
798
+ return {
799
+ fileName: sourceFile.fileName,
800
+ length: node.getWidth(sourceFile),
801
+ start: node.getStart(sourceFile)
802
+ };
803
+ }
804
+ //# sourceMappingURL=get-expression-domain.js.map