fhirpath-ts 0.1.0 → 0.2.1
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/README.md +50 -18
- package/dist/analyzer/analyze-dto.d.ts +4 -0
- package/dist/analyzer/analyze-dto.d.ts.map +1 -1
- package/dist/analyzer/analyze-dto.js +12 -14
- package/dist/analyzer/analyze-dto.js.map +1 -1
- package/dist/analyzer/analyze.d.ts +22 -6
- package/dist/analyzer/analyze.d.ts.map +1 -1
- package/dist/analyzer/analyze.js +242 -98
- package/dist/analyzer/analyze.js.map +1 -1
- package/dist/analyzer/declarations.d.ts +26 -0
- package/dist/analyzer/declarations.d.ts.map +1 -0
- package/dist/analyzer/declarations.js +68 -0
- package/dist/analyzer/declarations.js.map +1 -0
- package/dist/analyzer/operator-rules.d.ts +127 -0
- package/dist/analyzer/operator-rules.d.ts.map +1 -0
- package/dist/analyzer/operator-rules.js +70 -0
- package/dist/analyzer/operator-rules.js.map +1 -0
- package/dist/analyzer/signatures.d.ts +1097 -10
- package/dist/analyzer/signatures.d.ts.map +1 -1
- package/dist/analyzer/signatures.js +70 -29
- package/dist/analyzer/signatures.js.map +1 -1
- package/dist/api/compile.d.ts +32 -8
- package/dist/api/compile.d.ts.map +1 -1
- package/dist/api/compile.js +2 -0
- package/dist/api/compile.js.map +1 -1
- package/dist/api/engine.d.ts +42 -16
- package/dist/api/engine.d.ts.map +1 -1
- package/dist/api/engine.js +2 -1
- package/dist/api/engine.js.map +1 -1
- package/dist/api/project.d.ts +8 -8
- package/dist/api/project.d.ts.map +1 -1
- package/dist/api/project.js +31 -19
- package/dist/api/project.js.map +1 -1
- package/dist/api/strict.d.ts +6 -0
- package/dist/api/strict.d.ts.map +1 -0
- package/dist/api/strict.js +71 -0
- package/dist/api/strict.js.map +1 -0
- package/dist/api/tagged.d.ts +4 -4
- package/dist/api/tagged.d.ts.map +1 -1
- package/dist/api/tagged.js.map +1 -1
- package/dist/cli/dto-check.js +7 -3
- package/dist/cli/dto-check.js.map +1 -1
- package/dist/engine/context.d.ts +3 -1
- package/dist/engine/context.d.ts.map +1 -1
- package/dist/engine/context.js +5 -1
- package/dist/engine/context.js.map +1 -1
- package/dist/engine/navigation.d.ts.map +1 -1
- package/dist/engine/navigation.js +7 -48
- package/dist/engine/navigation.js.map +1 -1
- package/dist/engine/type-matching.d.ts.map +1 -1
- package/dist/engine/type-matching.js +6 -16
- package/dist/engine/type-matching.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/parser.js +102 -73
- package/dist/parser/parser.js.map +1 -1
- package/dist/parser/precedence.d.ts +368 -4
- package/dist/parser/precedence.d.ts.map +1 -1
- package/dist/parser/precedence.js +146 -32
- package/dist/parser/precedence.js.map +1 -1
- package/dist/r4/generated/resources-data.d.ts +6 -2
- package/dist/r4/generated/resources-data.d.ts.map +1 -1
- package/dist/r4/generated/resources-data.js +626 -28627
- package/dist/r4/generated/resources-data.js.map +1 -1
- package/dist/r4/generated/type-maps.d.ts +1367 -0
- package/dist/r4/generated/type-maps.d.ts.map +1 -1
- package/dist/r4/generated/types-data.d.ts +6 -2
- package/dist/r4/generated/types-data.d.ts.map +1 -1
- package/dist/r4/generated/types-data.js +84 -2204
- package/dist/r4/generated/types-data.js.map +1 -1
- package/dist/r4/index.d.ts +3 -1
- package/dist/r4/index.d.ts.map +1 -1
- package/dist/r4/index.js +10 -4
- package/dist/r4/index.js.map +1 -1
- package/dist/r4/model-data.d.ts +17 -0
- package/dist/r4/model-data.d.ts.map +1 -1
- package/dist/r4/model-data.js +118 -1
- package/dist/r4/model-data.js.map +1 -1
- package/dist/typed/context-maps.d.ts +23 -0
- package/dist/typed/context-maps.d.ts.map +1 -0
- package/dist/typed/context-maps.js +2 -0
- package/dist/typed/context-maps.js.map +1 -0
- package/dist/typed/infer.d.ts +99 -229
- package/dist/typed/infer.d.ts.map +1 -1
- package/dist/typed/infer.js +1 -84
- package/dist/typed/infer.js.map +1 -1
- package/dist/typed/inference-limits.d.ts +7 -0
- package/dist/typed/inference-limits.d.ts.map +1 -0
- package/dist/typed/inference-limits.js +6 -0
- package/dist/typed/inference-limits.js.map +1 -0
- package/dist/typed/metadata-compact.d.ts +189 -0
- package/dist/typed/metadata-compact.d.ts.map +1 -0
- package/dist/typed/metadata-compact.js +3 -0
- package/dist/typed/metadata-compact.js.map +1 -0
- package/dist/typed/parser.d.ts +515 -0
- package/dist/typed/parser.d.ts.map +1 -0
- package/dist/typed/parser.js +2 -0
- package/dist/typed/parser.js.map +1 -0
- package/dist/values/type-compat.d.ts +8 -0
- package/dist/values/type-compat.d.ts.map +1 -1
- package/dist/values/type-compat.js +44 -1
- package/dist/values/type-compat.js.map +1 -1
- package/package.json +20 -1
- package/src/analyzer/analyze-dto.ts +21 -17
- package/src/analyzer/analyze.ts +335 -109
- package/src/analyzer/declarations.ts +120 -0
- package/src/analyzer/operator-rules.ts +88 -0
- package/src/analyzer/signatures.ts +99 -41
- package/src/api/compile.ts +66 -11
- package/src/api/engine.ts +97 -33
- package/src/api/project.ts +54 -30
- package/src/api/strict.ts +112 -0
- package/src/api/tagged.ts +5 -5
- package/src/cli/dto-check.ts +7 -3
- package/src/engine/context.ts +7 -2
- package/src/engine/navigation.ts +7 -52
- package/src/engine/type-matching.ts +11 -17
- package/src/index.ts +25 -2
- package/src/parser/parser.ts +106 -74
- package/src/parser/precedence.ts +250 -33
- package/src/r4/generated/resources-data.ts +624 -28628
- package/src/r4/generated/type-maps.ts +1557 -0
- package/src/r4/generated/types-data.ts +82 -2205
- package/src/r4/index.ts +11 -5
- package/src/r4/model-data.ts +127 -0
- package/src/typed/context-maps.ts +41 -0
- package/src/typed/infer.ts +157 -425
- package/src/typed/inference-limits.ts +9 -0
- package/src/typed/metadata-compact.ts +307 -0
- package/src/typed/parser.ts +2277 -0
- package/src/values/type-compat.ts +48 -1
- package/dist/typed/perf-fixture.types.d.ts +0 -136
- package/dist/typed/perf-fixture.types.d.ts.map +0 -1
- package/dist/typed/perf-fixture.types.js +0 -2
- package/dist/typed/perf-fixture.types.js.map +0 -1
- package/src/typed/perf-fixture.types.ts +0 -143
package/src/analyzer/analyze.ts
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
import '../functions/install.ts'
|
|
2
2
|
|
|
3
|
-
import { BUILTIN_ENV_VARIABLE_NAMES } from '../engine/context.ts'
|
|
3
|
+
import { bareEnvironmentName, BUILTIN_ENV_VARIABLE_NAMES, normalizeEnvKeys } from '../engine/context.ts'
|
|
4
4
|
import { FhirPathSyntaxError, type SourceSpan } from '../errors.ts'
|
|
5
5
|
import { describeArity, functions } from '../functions/registry.ts'
|
|
6
6
|
import type { ElementInfo, ModelProvider } from '../model/provider.ts'
|
|
7
7
|
import type { AstNode } from '../parser/ast.ts'
|
|
8
8
|
import { parse } from '../parser/parser.ts'
|
|
9
|
-
import
|
|
9
|
+
import type { FhirpathTypeDeclarations } from '../typed/infer.ts'
|
|
10
|
+
import {
|
|
11
|
+
canonicalFocusType,
|
|
12
|
+
commonValueKind,
|
|
13
|
+
resolveByInput,
|
|
14
|
+
resolveSystemTypeName,
|
|
15
|
+
rootTypeMatches,
|
|
16
|
+
SYSTEM_TYPE_LOCAL_NAMES,
|
|
17
|
+
unsatisfiedInput,
|
|
18
|
+
type ValueKind,
|
|
19
|
+
} from '../values/type-compat.ts'
|
|
10
20
|
import { FHIR_PRIMITIVE_TO_SYSTEM, typeLocalName } from '../values/typed-value.ts'
|
|
21
|
+
import {
|
|
22
|
+
analyzerEnvironmentVariables,
|
|
23
|
+
type AnalyzerVariable,
|
|
24
|
+
type AnalyzerVariableState,
|
|
25
|
+
isRuntimeAnalyzerVariable,
|
|
26
|
+
runtimeAnalyzerEnvironmentVariables,
|
|
27
|
+
} from './declarations.ts'
|
|
28
|
+
import { applyOperatorResultRule, applyTypeOperatorResultRule } from './operator-rules.ts'
|
|
11
29
|
import { hasNestedUnboundedQuantifier } from './regex-safety.ts'
|
|
12
30
|
import {
|
|
31
|
+
applyResultRule,
|
|
13
32
|
type CustomFunctionSignature,
|
|
14
33
|
FUNCTION_SIGNATURES,
|
|
15
34
|
type FunctionSignature,
|
|
@@ -30,22 +49,27 @@ export interface AnalyzerDiagnostic {
|
|
|
30
49
|
}
|
|
31
50
|
|
|
32
51
|
/** A host variable known to the analyzer. Omit its types to keep the value unknown. */
|
|
33
|
-
export
|
|
34
|
-
/** Candidate type names ('Patient', 'System.String'); omit to leave the type unknown. */
|
|
35
|
-
types?: string[]
|
|
36
|
-
/** True when the variable always holds at most one item. */
|
|
37
|
-
single?: boolean
|
|
38
|
-
}
|
|
52
|
+
export type DeclaredVariable = AnalyzerVariable
|
|
39
53
|
|
|
40
54
|
/** A host function declaration. Arity resolves the call; an optional signature checks it. */
|
|
41
55
|
export type SingleDeclaredFunction =
|
|
42
|
-
| {
|
|
56
|
+
| {
|
|
57
|
+
minArity?: number
|
|
58
|
+
maxArity?: number
|
|
59
|
+
expression?: never
|
|
60
|
+
signature?: CustomFunctionSignature
|
|
61
|
+
criteria?: never
|
|
62
|
+
envTypes?: never
|
|
63
|
+
}
|
|
43
64
|
| {
|
|
44
65
|
/** The body of an expression-defined CustomFunction; its presence pins the arity to 0. */
|
|
45
66
|
expression: unknown
|
|
46
67
|
minArity?: never
|
|
47
68
|
maxArity?: never
|
|
48
69
|
signature?: CustomFunctionSignature
|
|
70
|
+
criteria?: boolean
|
|
71
|
+
env?: Record<string, unknown>
|
|
72
|
+
envTypes?: FhirpathTypeDeclarations
|
|
49
73
|
}
|
|
50
74
|
|
|
51
75
|
/** Same-name functions selected by the call focus. Unknown focus keeps only their shared claims. */
|
|
@@ -69,6 +93,11 @@ export interface AnalyzeOptions {
|
|
|
69
93
|
variables?: Record<string, DeclaredVariable>
|
|
70
94
|
}
|
|
71
95
|
|
|
96
|
+
/** Internal strict-analysis options whose variables may retain exact runtime focus types. */
|
|
97
|
+
export interface RuntimeAnalyzeOptions extends Omit<AnalyzeOptions, 'variables'> {
|
|
98
|
+
variables?: Record<string, AnalyzerVariableState>
|
|
99
|
+
}
|
|
100
|
+
|
|
72
101
|
/**
|
|
73
102
|
* Candidate type names and cardinality for one sub-expression. `undefined`
|
|
74
103
|
* means unknown and pauses checks that need that fact. `single` is true for at
|
|
@@ -77,6 +106,8 @@ export interface AnalyzeOptions {
|
|
|
77
106
|
interface StaticState {
|
|
78
107
|
types: string[] | undefined
|
|
79
108
|
single: boolean | undefined
|
|
109
|
+
/** Exact runtime focus types, present only while strict analysis can still prove them. */
|
|
110
|
+
exactTypes?: string[]
|
|
80
111
|
/**
|
|
81
112
|
* Canonical resource types a Reference-valued state may point to (from
|
|
82
113
|
* `Reference.targetProfile`) — what resolve() yields. Carried by element
|
|
@@ -89,6 +120,17 @@ interface StaticState {
|
|
|
89
120
|
|
|
90
121
|
const UNKNOWN: StaticState = { types: undefined, single: undefined }
|
|
91
122
|
|
|
123
|
+
/** A runtime-analyzed function result no longer has exact facts about its input focus. */
|
|
124
|
+
function callResult(state: Pick<StaticState, 'types' | 'single' | 'targets'>, runtime: boolean): StaticState {
|
|
125
|
+
return runtime
|
|
126
|
+
? {
|
|
127
|
+
types: state.types,
|
|
128
|
+
single: state.single,
|
|
129
|
+
...(state.targets !== undefined && { targets: state.targets }),
|
|
130
|
+
}
|
|
131
|
+
: state
|
|
132
|
+
}
|
|
133
|
+
|
|
92
134
|
/**
|
|
93
135
|
* Variables visible at one point in analysis. Operators and arguments receive a
|
|
94
136
|
* copy. A dynamic variable name prevents later unknown-variable diagnostics in
|
|
@@ -126,6 +168,14 @@ export interface AnalysisDetails {
|
|
|
126
168
|
elementDependencies: string[]
|
|
127
169
|
}
|
|
128
170
|
|
|
171
|
+
/** Runtime-known input state used by strict evaluation. Not part of the public analyzer API. */
|
|
172
|
+
export interface AnalyzerRoot {
|
|
173
|
+
types: string[] | undefined
|
|
174
|
+
single: boolean | undefined
|
|
175
|
+
/** Canonical runtime types in focus order; an empty array means an exactly empty focus. */
|
|
176
|
+
exactTypes?: string[]
|
|
177
|
+
}
|
|
178
|
+
|
|
129
179
|
/**
|
|
130
180
|
* Statically check one expression against the model: spec §11's strict-mode rules
|
|
131
181
|
* (singleton misuse, wrong operand and argument types, incomparable equality)
|
|
@@ -199,7 +249,16 @@ export function analyzeExpressionDetailed(expression: string, options?: AnalyzeO
|
|
|
199
249
|
result: { types: undefined, single: undefined },
|
|
200
250
|
}
|
|
201
251
|
}
|
|
202
|
-
|
|
252
|
+
return analyzeAstDetailed(ast, options)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Analyze an already parsed expression, optionally with a runtime-derived input state. */
|
|
256
|
+
function analyzeAstDetailed(
|
|
257
|
+
ast: AstNode,
|
|
258
|
+
options?: AnalyzeOptions | RuntimeAnalyzeOptions,
|
|
259
|
+
root?: AnalyzerRoot
|
|
260
|
+
): AnalysisDetails {
|
|
261
|
+
const analyzer = new Analyzer(options, root)
|
|
203
262
|
const state = analyzer.walk(ast, analyzer.rootState(), emptyScope())
|
|
204
263
|
return {
|
|
205
264
|
diagnostics: analyzer.diagnostics,
|
|
@@ -208,36 +267,54 @@ export function analyzeExpressionDetailed(expression: string, options?: AnalyzeO
|
|
|
208
267
|
}
|
|
209
268
|
}
|
|
210
269
|
|
|
270
|
+
/** Analyze a parsed expression using facts from the actual evaluation focus. */
|
|
271
|
+
export function analyzeRuntimeAstDetailed(
|
|
272
|
+
ast: AstNode,
|
|
273
|
+
options: RuntimeAnalyzeOptions,
|
|
274
|
+
root: AnalyzerRoot
|
|
275
|
+
): AnalysisDetails {
|
|
276
|
+
return analyzeAstDetailed(ast, options, root)
|
|
277
|
+
}
|
|
278
|
+
|
|
211
279
|
class Analyzer {
|
|
212
280
|
readonly diagnostics: AnalyzerDiagnostic[] = []
|
|
213
281
|
readonly dependencies = new Set<string>()
|
|
214
282
|
private readonly model: ModelProvider | undefined
|
|
215
|
-
private readonly
|
|
283
|
+
private readonly root: AnalyzerRoot
|
|
284
|
+
private readonly runtime: boolean
|
|
216
285
|
private readonly frames: StaticState[] = []
|
|
217
286
|
/** Every declaration of each host-supplied name; one entry unless the name is overloaded. */
|
|
218
287
|
private readonly customFunctions: ReadonlyMap<string, readonly ResolvedDeclaration[]>
|
|
219
|
-
private readonly declaredVariables: ReadonlyMap<string,
|
|
288
|
+
private readonly declaredVariables: ReadonlyMap<string, AnalyzerVariableState>
|
|
289
|
+
private readonly activeExpressionFunctions = new Set<string>()
|
|
220
290
|
|
|
221
|
-
constructor(options: AnalyzeOptions | undefined) {
|
|
291
|
+
constructor(options: AnalyzeOptions | RuntimeAnalyzeOptions | undefined, root: AnalyzerRoot | undefined) {
|
|
222
292
|
this.model = options?.model
|
|
293
|
+
this.runtime = root !== undefined
|
|
223
294
|
const inputType = options?.inputType
|
|
224
|
-
this.
|
|
295
|
+
this.root =
|
|
296
|
+
root ??
|
|
297
|
+
(inputType === undefined
|
|
298
|
+
? { types: undefined, single: undefined }
|
|
299
|
+
: { types: [this.model?.resolveType(inputType) ?? inputType], single: true })
|
|
225
300
|
this.customFunctions = new Map(
|
|
226
301
|
Object.entries(options?.functions ?? {}).map(([name, declared]) => [
|
|
227
302
|
name,
|
|
228
|
-
('overloads' in declared ? declared.overloads : [declared]).map(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
this.declaredVariables = new Map(
|
|
232
|
-
Object.entries(options?.variables ?? {}).map(([name, variable]) => [
|
|
233
|
-
name.startsWith('%') ? name.slice(1) : name,
|
|
234
|
-
variable,
|
|
303
|
+
('overloads' in declared ? declared.overloads : [declared]).map(declaration =>
|
|
304
|
+
resolvedDeclaration(declaration, this.model, this.runtime)
|
|
305
|
+
),
|
|
235
306
|
])
|
|
236
307
|
)
|
|
308
|
+
this.declaredVariables = new Map(Object.entries(normalizeEnvKeys(options?.variables)))
|
|
237
309
|
}
|
|
238
310
|
|
|
239
311
|
rootState(): StaticState {
|
|
240
|
-
return
|
|
312
|
+
return {
|
|
313
|
+
types: this.root.types,
|
|
314
|
+
single: this.root.single,
|
|
315
|
+
rawInput: true,
|
|
316
|
+
...(this.root.exactTypes !== undefined && { exactTypes: this.root.exactTypes }),
|
|
317
|
+
}
|
|
241
318
|
}
|
|
242
319
|
|
|
243
320
|
walk(node: AstNode, input: StaticState, scope: VariableScope): StaticState {
|
|
@@ -312,7 +389,7 @@ class Analyzer {
|
|
|
312
389
|
}
|
|
313
390
|
const declared = this.declaredVariables.get(node.name)
|
|
314
391
|
if (declared !== undefined) {
|
|
315
|
-
return
|
|
392
|
+
return this.declaredVariableState(declared)
|
|
316
393
|
}
|
|
317
394
|
switch (node.name) {
|
|
318
395
|
case 'context':
|
|
@@ -362,30 +439,54 @@ class Analyzer {
|
|
|
362
439
|
if (input.types !== undefined && input.types.length === 0) {
|
|
363
440
|
return { types: [], single: true }
|
|
364
441
|
}
|
|
442
|
+
const exactTypes = input.exactTypes?.filter(type => rootTypeMatches(this.model, type, node.name))
|
|
443
|
+
if (exactTypes !== undefined && exactTypes.length > 0) {
|
|
444
|
+
const types = input.types?.filter(type => rootTypeMatches(this.model, type, node.name))
|
|
445
|
+
return {
|
|
446
|
+
types: types === undefined ? undefined : [...new Set(types)],
|
|
447
|
+
single: input.single,
|
|
448
|
+
exactTypes,
|
|
449
|
+
...(input.rawInput === true && { rawInput: true }),
|
|
450
|
+
}
|
|
451
|
+
}
|
|
365
452
|
if (input.types === undefined) {
|
|
366
453
|
// Even with an unknown input, a root identifier naming a model type anchors
|
|
367
454
|
// the state — this is what checks `Patient.nope` without an inputType option.
|
|
368
|
-
const asType = this.model?.resolveType(node.name)
|
|
455
|
+
const asType = this.model?.resolveType(node.name) ?? resolveSystemTypeName(node.name)
|
|
369
456
|
if (asType !== undefined) {
|
|
370
|
-
return {
|
|
457
|
+
return {
|
|
458
|
+
types: [asType],
|
|
459
|
+
single: true,
|
|
460
|
+
...(input.exactTypes !== undefined && {
|
|
461
|
+
exactTypes: input.exactTypes.filter(type => this.model?.isSubtypeOf(type, asType) === true),
|
|
462
|
+
}),
|
|
463
|
+
}
|
|
371
464
|
}
|
|
372
465
|
return UNKNOWN
|
|
373
466
|
}
|
|
374
467
|
// Root rule: an identifier naming the (super)type of the context is the context.
|
|
375
|
-
|
|
376
|
-
const asType = this.model
|
|
377
|
-
|
|
468
|
+
{
|
|
469
|
+
const asType = this.model?.resolveType(node.name) ?? resolveSystemTypeName(node.name)
|
|
470
|
+
let matchingTypes: string[] = []
|
|
471
|
+
if (asType !== undefined) {
|
|
472
|
+
matchingTypes = input.types.filter(type => rootTypeMatches(this.model, type, node.name))
|
|
473
|
+
}
|
|
474
|
+
if (asType !== undefined && matchingTypes.length > 0) {
|
|
378
475
|
// The runtime matches a type name against the raw input only through the
|
|
379
476
|
// resourceType discriminator (values/typed-value.ts), so a non-resource
|
|
380
477
|
// name never matches there and the whole path navigates to empty.
|
|
381
|
-
if (input.rawInput === true && !this.isResourceType(asType)) {
|
|
478
|
+
if (input.rawInput === true && !asType.startsWith('System.') && !this.isResourceType(asType)) {
|
|
382
479
|
this.report(
|
|
383
480
|
'datatype-root',
|
|
384
481
|
`'${node.name}' is not a resource type, and a type-name root matches only a resource's resourceType, so this always evaluates to empty — navigate from the input with a relative path`,
|
|
385
482
|
node.span
|
|
386
483
|
)
|
|
387
484
|
}
|
|
388
|
-
return
|
|
485
|
+
return {
|
|
486
|
+
...input,
|
|
487
|
+
types: matchingTypes,
|
|
488
|
+
...(input.exactTypes !== undefined && { exactTypes: input.exactTypes }),
|
|
489
|
+
}
|
|
389
490
|
}
|
|
390
491
|
}
|
|
391
492
|
const found: string[] = []
|
|
@@ -528,7 +629,8 @@ class Analyzer {
|
|
|
528
629
|
const signature = registered !== undefined ? FUNCTION_SIGNATURES[node.name] : this.toSignature(custom?.signature)
|
|
529
630
|
if (!signature) {
|
|
530
631
|
this.walkUncheckedArguments(node, input, scope)
|
|
531
|
-
|
|
632
|
+
const expressionResult = this.expressionFunctionResult(node.name, custom, input, scope)
|
|
633
|
+
return expressionResult === undefined ? UNKNOWN : callResult(expressionResult, this.runtime)
|
|
532
634
|
}
|
|
533
635
|
this.checkCallInput(node, signature, input)
|
|
534
636
|
const { argStates, typeTarget } = this.walkArguments(node, signature, input, scope)
|
|
@@ -541,7 +643,125 @@ class Analyzer {
|
|
|
541
643
|
if ((node.name === 'ofType' || node.name === 'as') && typeTarget !== undefined) {
|
|
542
644
|
return { types: this.narrowTypes(input, typeTarget, node.span), single: input.single }
|
|
543
645
|
}
|
|
544
|
-
|
|
646
|
+
const expressionResult = this.expressionFunctionResult(node.name, custom, input, scope)
|
|
647
|
+
if (expressionResult !== undefined) {
|
|
648
|
+
return callResult(expressionResult, this.runtime)
|
|
649
|
+
}
|
|
650
|
+
return callResult(applyResultRule(signature.result, input, argStates), this.runtime)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/** Apply the expression-body and criteria rules in one place for signed and unsigned declarations. */
|
|
654
|
+
private expressionFunctionResult(
|
|
655
|
+
name: string,
|
|
656
|
+
declaration: ResolvedDeclaration | undefined,
|
|
657
|
+
input: StaticState,
|
|
658
|
+
scope: VariableScope
|
|
659
|
+
): StaticState | undefined {
|
|
660
|
+
if (declaration?.expression === undefined) {
|
|
661
|
+
for (const possible of declaration?.possibleBodies ?? []) {
|
|
662
|
+
if (possible.expression !== undefined) {
|
|
663
|
+
this.walkExpressionFunction(
|
|
664
|
+
name,
|
|
665
|
+
possible.expression,
|
|
666
|
+
possible.variables,
|
|
667
|
+
this.expressionBodyInput(possible, input),
|
|
668
|
+
scope
|
|
669
|
+
)
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return undefined
|
|
673
|
+
}
|
|
674
|
+
const expression = declaration.expression
|
|
675
|
+
const bodyInput = this.expressionBodyInput(declaration, input)
|
|
676
|
+
if (declaration.criteria === true) {
|
|
677
|
+
this.walkExpressionFunction(name, expression, declaration.variables, bodyInput, scope)
|
|
678
|
+
return { types: ['System.Boolean'], single: true }
|
|
679
|
+
}
|
|
680
|
+
if (declaration.signature?.result === undefined) {
|
|
681
|
+
return this.walkExpressionFunction(name, expression, declaration.variables, bodyInput, scope)
|
|
682
|
+
}
|
|
683
|
+
if (this.runtime) {
|
|
684
|
+
this.walkExpressionFunction(name, expression, declaration.variables, bodyInput, scope)
|
|
685
|
+
}
|
|
686
|
+
return undefined
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/** A declared input type is the contract an expression body must satisfy. */
|
|
690
|
+
private expressionBodyInput(declaration: ResolvedDeclaration, callInput: StaticState): StaticState {
|
|
691
|
+
const declared = declaration.signature?.input?.types
|
|
692
|
+
if (declared === undefined) {
|
|
693
|
+
return callInput
|
|
694
|
+
}
|
|
695
|
+
const types = declared
|
|
696
|
+
.map(type =>
|
|
697
|
+
this.model === undefined ? resolveSystemTypeName(typeLocalName(type)) : canonicalFocusType(this.model, type)
|
|
698
|
+
)
|
|
699
|
+
.filter((type): type is string => type !== undefined)
|
|
700
|
+
if (types.length === 0) {
|
|
701
|
+
return callInput
|
|
702
|
+
}
|
|
703
|
+
return {
|
|
704
|
+
types: [...new Set(types)],
|
|
705
|
+
single: callInput.single,
|
|
706
|
+
...(callInput.targets !== undefined && { targets: callInput.targets }),
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** Infer a literal expression-function body under its call focus and temporary local environment declarations. */
|
|
711
|
+
private walkExpressionFunction(
|
|
712
|
+
name: string,
|
|
713
|
+
expression: NonNullable<ResolvedDeclaration['expression']>,
|
|
714
|
+
variables: ResolvedDeclaration['variables'],
|
|
715
|
+
input: StaticState,
|
|
716
|
+
callerScope: VariableScope
|
|
717
|
+
): StaticState {
|
|
718
|
+
if (this.activeExpressionFunctions.has(name)) {
|
|
719
|
+
return UNKNOWN
|
|
720
|
+
}
|
|
721
|
+
let ast = expression.ast
|
|
722
|
+
if (ast === undefined) {
|
|
723
|
+
try {
|
|
724
|
+
ast = parse(expression.source)
|
|
725
|
+
} catch {
|
|
726
|
+
return UNKNOWN
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
const functionScope = forkScope(callerScope)
|
|
730
|
+
for (const [declaredName, variable] of Object.entries(variables ?? {})) {
|
|
731
|
+
const bare = bareEnvironmentName(declaredName)
|
|
732
|
+
// defineVariable() values have priority over environment overlays at runtime.
|
|
733
|
+
if (!functionScope.vars.has(bare)) {
|
|
734
|
+
functionScope.vars.set(bare, this.declaredVariableState(variable))
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const diagnosticCount = this.diagnostics.length
|
|
738
|
+
this.activeExpressionFunctions.add(name)
|
|
739
|
+
this.frames.push(input)
|
|
740
|
+
try {
|
|
741
|
+
return this.walk(ast, input, functionScope)
|
|
742
|
+
} finally {
|
|
743
|
+
if (this.runtime) {
|
|
744
|
+
for (const diagnostic of this.diagnostics.slice(diagnosticCount)) {
|
|
745
|
+
diagnostic.message = `Custom function '${name}': ${diagnostic.message}`
|
|
746
|
+
}
|
|
747
|
+
} else {
|
|
748
|
+
// Body spans index another source string, so callers cannot display these diagnostics correctly.
|
|
749
|
+
this.diagnostics.length = diagnosticCount
|
|
750
|
+
}
|
|
751
|
+
this.frames.pop()
|
|
752
|
+
this.activeExpressionFunctions.delete(name)
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/** Convert one declared environment value into the same state used for scoped variables. */
|
|
757
|
+
private declaredVariableState(declared: AnalyzerVariableState): StaticState {
|
|
758
|
+
const exactTypes = this.runtime && isRuntimeAnalyzerVariable(declared) ? declared.exactTypes : undefined
|
|
759
|
+
return {
|
|
760
|
+
types: declared.types?.map(type => this.canonicalize(type)),
|
|
761
|
+
single: declared.single,
|
|
762
|
+
...(declared.targets !== undefined && { targets: declared.targets.map(type => this.canonicalize(type)) }),
|
|
763
|
+
...(exactTypes !== undefined && { exactTypes }),
|
|
764
|
+
}
|
|
545
765
|
}
|
|
546
766
|
|
|
547
767
|
/**
|
|
@@ -554,11 +774,25 @@ class Analyzer {
|
|
|
554
774
|
if (candidates === undefined || candidates.length <= 1) {
|
|
555
775
|
return candidates?.[0]
|
|
556
776
|
}
|
|
777
|
+
if (this.runtime && (input.exactTypes !== undefined || this.model === undefined)) {
|
|
778
|
+
const resolution = resolveByInput(
|
|
779
|
+
this.model,
|
|
780
|
+
candidates,
|
|
781
|
+
candidate => candidate.signature?.input?.types,
|
|
782
|
+
input.exactTypes ?? []
|
|
783
|
+
)
|
|
784
|
+
return 'resolved' in resolution ? resolution.resolved : mergedDeclaration(candidates)
|
|
785
|
+
}
|
|
557
786
|
const focus = input.types ?? []
|
|
558
787
|
const fitting = candidates.filter(
|
|
559
788
|
candidate => unsatisfiedInput(this.model, candidate.signature?.input?.types, focus) === undefined
|
|
560
789
|
)
|
|
561
|
-
|
|
790
|
+
if (fitting.length === 1) {
|
|
791
|
+
return fitting[0]
|
|
792
|
+
}
|
|
793
|
+
const possible = fitting.length === 0 ? candidates : fitting
|
|
794
|
+
const merged = mergedDeclaration(possible)
|
|
795
|
+
return this.runtime ? { ...merged, possibleBodies: possible } : merged
|
|
562
796
|
}
|
|
563
797
|
|
|
564
798
|
/** Without a signature the arguments still walk (for their own diagnostics), each in a scope fork. */
|
|
@@ -740,7 +974,11 @@ class Analyzer {
|
|
|
740
974
|
return {
|
|
741
975
|
...(declared.input !== undefined && { input: declared.input }),
|
|
742
976
|
...(declared.args !== undefined && { args: declared.args }),
|
|
743
|
-
result:
|
|
977
|
+
result: {
|
|
978
|
+
kind: 'fixed',
|
|
979
|
+
...(types !== undefined && { types }),
|
|
980
|
+
...(single !== undefined && { single }),
|
|
981
|
+
},
|
|
744
982
|
}
|
|
745
983
|
}
|
|
746
984
|
|
|
@@ -793,7 +1031,7 @@ class Analyzer {
|
|
|
793
1031
|
/** Check a type name; returns its canonical form, or undefined when it was reported unknown. */
|
|
794
1032
|
private checkTypeName(parts: string[], span: SourceSpan): string | undefined {
|
|
795
1033
|
if (parts.length === 2 && parts[0] === 'System') {
|
|
796
|
-
if (!
|
|
1034
|
+
if (!SYSTEM_TYPE_LOCAL_NAMES.has(parts[1] as string)) {
|
|
797
1035
|
this.report('unknown-type', `Unknown type 'System.${parts[1]}'`, span)
|
|
798
1036
|
return undefined
|
|
799
1037
|
}
|
|
@@ -805,11 +1043,11 @@ class Analyzer {
|
|
|
805
1043
|
return undefined
|
|
806
1044
|
}
|
|
807
1045
|
const resolved = this.model?.resolveType(name)
|
|
808
|
-
if (this.model && resolved === undefined && !
|
|
1046
|
+
if (this.model && resolved === undefined && !SYSTEM_TYPE_LOCAL_NAMES.has(name)) {
|
|
809
1047
|
this.report('unknown-type', `Unknown type '${parts.join('.')}'`, span)
|
|
810
1048
|
return undefined
|
|
811
1049
|
}
|
|
812
|
-
return resolved ?? (
|
|
1050
|
+
return resolved ?? (SYSTEM_TYPE_LOCAL_NAMES.has(name) ? `System.${name}` : name)
|
|
813
1051
|
}
|
|
814
1052
|
|
|
815
1053
|
private walkBinary(node: AstNode & { kind: 'binary' }, input: StaticState, scope: VariableScope): StaticState {
|
|
@@ -826,24 +1064,14 @@ class Analyzer {
|
|
|
826
1064
|
case 'div':
|
|
827
1065
|
case 'mod': {
|
|
828
1066
|
this.checkArithmetic(node.operator, left, right, node.span)
|
|
829
|
-
|
|
830
|
-
// plain division yields Decimal; everything else keeps the operand type.
|
|
831
|
-
const quantity =
|
|
832
|
-
(node.operator === '*' || node.operator === '/') &&
|
|
833
|
-
(kindOf(left) === 'Quantity' || kindOf(right) === 'Quantity')
|
|
834
|
-
const types = quantity
|
|
835
|
-
? ['System.Quantity']
|
|
836
|
-
: node.operator === '/'
|
|
837
|
-
? ['System.Decimal']
|
|
838
|
-
: (left.types ?? right.types)
|
|
839
|
-
return { types, single: true }
|
|
1067
|
+
return applyOperatorResultRule(node.operator, left, right)
|
|
840
1068
|
}
|
|
841
1069
|
case '&':
|
|
842
1070
|
this.requireSingle(left, node.left.span, "'&' expects single-item operands")
|
|
843
1071
|
this.requireSingle(right, node.right.span, "'&' expects single-item operands")
|
|
844
1072
|
this.requireKind(left, 'String', node.left.span, "'&' expects String operands")
|
|
845
1073
|
this.requireKind(right, 'String', node.right.span, "'&' expects String operands")
|
|
846
|
-
return
|
|
1074
|
+
return applyOperatorResultRule(node.operator, left, right)
|
|
847
1075
|
case '<':
|
|
848
1076
|
case '>':
|
|
849
1077
|
case '<=':
|
|
@@ -851,13 +1079,13 @@ class Analyzer {
|
|
|
851
1079
|
this.requireSingle(left, node.left.span, `Operator '${node.operator}' expects single-item operands`)
|
|
852
1080
|
this.requireSingle(right, node.right.span, `Operator '${node.operator}' expects single-item operands`)
|
|
853
1081
|
this.checkComparable(left, right, node.span)
|
|
854
|
-
return
|
|
1082
|
+
return applyOperatorResultRule(node.operator, left, right)
|
|
855
1083
|
case '=':
|
|
856
1084
|
case '!=':
|
|
857
1085
|
case '~':
|
|
858
1086
|
case '!~':
|
|
859
1087
|
this.checkEquality(left, right, node.span)
|
|
860
|
-
return
|
|
1088
|
+
return applyOperatorResultRule(node.operator, left, right)
|
|
861
1089
|
case 'and':
|
|
862
1090
|
case 'or':
|
|
863
1091
|
case 'xor':
|
|
@@ -866,17 +1094,10 @@ class Analyzer {
|
|
|
866
1094
|
// rule, so only cardinality is checkable here.
|
|
867
1095
|
this.requireSingle(left, node.left.span, `'${node.operator}' expects single-item operands`)
|
|
868
1096
|
this.requireSingle(right, node.right.span, `'${node.operator}' expects single-item operands`)
|
|
869
|
-
return
|
|
1097
|
+
return applyOperatorResultRule(node.operator, left, right)
|
|
870
1098
|
}
|
|
871
1099
|
case '|': {
|
|
872
|
-
|
|
873
|
-
if (left.types?.length === 0) {
|
|
874
|
-
return right
|
|
875
|
-
}
|
|
876
|
-
if (right.types?.length === 0) {
|
|
877
|
-
return left
|
|
878
|
-
}
|
|
879
|
-
return withSingle(unionStates([left, right]), false)
|
|
1100
|
+
return applyOperatorResultRule(node.operator, left, right)
|
|
880
1101
|
}
|
|
881
1102
|
case 'in':
|
|
882
1103
|
case 'contains': {
|
|
@@ -887,7 +1108,7 @@ class Analyzer {
|
|
|
887
1108
|
singletonSpan,
|
|
888
1109
|
`The ${node.operator === 'in' ? 'left' : 'right'} operand of '${node.operator}' must be a single item`
|
|
889
1110
|
)
|
|
890
|
-
return
|
|
1111
|
+
return applyOperatorResultRule(node.operator, left, right)
|
|
891
1112
|
}
|
|
892
1113
|
/* v8 ignore start -- the parser produces no other binary operators */
|
|
893
1114
|
default:
|
|
@@ -907,13 +1128,9 @@ class Analyzer {
|
|
|
907
1128
|
const operand = this.walk(node.operand, input, scope)
|
|
908
1129
|
this.requireSingle(operand, node.operand.span, `'${node.operator}' expects a single item operand`)
|
|
909
1130
|
const resolved = this.checkTypeName(node.type.parts, node.type.span)
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
if (resolved === undefined) {
|
|
914
|
-
return UNKNOWN
|
|
915
|
-
}
|
|
916
|
-
return { types: this.narrowTypes(operand, resolved, node.span), single: true }
|
|
1131
|
+
const narrowed =
|
|
1132
|
+
node.operator === 'as' && resolved !== undefined ? this.narrowTypes(operand, resolved, node.span) : undefined
|
|
1133
|
+
return applyTypeOperatorResultRule(node.operator, narrowed)
|
|
917
1134
|
}
|
|
918
1135
|
|
|
919
1136
|
private checkArithmetic(operator: string, left: StaticState, right: StaticState, span: SourceSpan): void {
|
|
@@ -921,8 +1138,8 @@ class Analyzer {
|
|
|
921
1138
|
this.report('singleton-required', `Operator '${operator}' expects single-item operands${NARROW_HINT}`, span)
|
|
922
1139
|
return
|
|
923
1140
|
}
|
|
924
|
-
const leftKind =
|
|
925
|
-
const rightKind =
|
|
1141
|
+
const leftKind = commonValueKind(left.types)
|
|
1142
|
+
const rightKind = commonValueKind(right.types)
|
|
926
1143
|
if (leftKind === undefined || rightKind === undefined || leftKind === 'Complex' || rightKind === 'Complex') {
|
|
927
1144
|
return
|
|
928
1145
|
}
|
|
@@ -941,8 +1158,8 @@ class Analyzer {
|
|
|
941
1158
|
}
|
|
942
1159
|
|
|
943
1160
|
private checkComparable(left: StaticState, right: StaticState, span: SourceSpan): void {
|
|
944
|
-
const leftKind =
|
|
945
|
-
const rightKind =
|
|
1161
|
+
const leftKind = commonValueKind(left.types)
|
|
1162
|
+
const rightKind = commonValueKind(right.types)
|
|
946
1163
|
if (leftKind === undefined || rightKind === undefined) {
|
|
947
1164
|
return
|
|
948
1165
|
}
|
|
@@ -952,8 +1169,8 @@ class Analyzer {
|
|
|
952
1169
|
}
|
|
953
1170
|
|
|
954
1171
|
private checkEquality(left: StaticState, right: StaticState, span: SourceSpan): void {
|
|
955
|
-
const leftKind =
|
|
956
|
-
const rightKind =
|
|
1172
|
+
const leftKind = commonValueKind(left.types)
|
|
1173
|
+
const rightKind = commonValueKind(right.types)
|
|
957
1174
|
if (leftKind === undefined || rightKind === undefined || leftKind === 'Complex' || rightKind === 'Complex') {
|
|
958
1175
|
return
|
|
959
1176
|
}
|
|
@@ -963,7 +1180,7 @@ class Analyzer {
|
|
|
963
1180
|
}
|
|
964
1181
|
|
|
965
1182
|
private requireKind(state: StaticState, kind: ValueKind, span: SourceSpan, message: string): void {
|
|
966
|
-
const actual =
|
|
1183
|
+
const actual = commonValueKind(state.types)
|
|
967
1184
|
if (actual === undefined) {
|
|
968
1185
|
return
|
|
969
1186
|
}
|
|
@@ -987,19 +1204,6 @@ class Analyzer {
|
|
|
987
1204
|
/** Functions whose first argument is a regular expression pattern. */
|
|
988
1205
|
const REGEX_PATTERN_FUNCTIONS = new Set(['matches', 'matchesFull', 'replaceMatches'])
|
|
989
1206
|
|
|
990
|
-
const SYSTEM_TYPE_NAMES = new Set([
|
|
991
|
-
'Any',
|
|
992
|
-
'Boolean',
|
|
993
|
-
'String',
|
|
994
|
-
'Integer',
|
|
995
|
-
'Long',
|
|
996
|
-
'Decimal',
|
|
997
|
-
'Date',
|
|
998
|
-
'DateTime',
|
|
999
|
-
'Time',
|
|
1000
|
-
'Quantity',
|
|
1001
|
-
])
|
|
1002
|
-
|
|
1003
1207
|
/** Appended to singleton-misuse messages so the fix is spelled out, not just the rule. */
|
|
1004
1208
|
const NARROW_HINT = ' — narrow it to one item with first(), last(), or single()'
|
|
1005
1209
|
|
|
@@ -1016,15 +1220,54 @@ interface ResolvedDeclaration {
|
|
|
1016
1220
|
minArity?: number
|
|
1017
1221
|
maxArity?: number
|
|
1018
1222
|
signature?: CustomFunctionSignature
|
|
1223
|
+
expression?: { source: string; ast?: AstNode }
|
|
1224
|
+
criteria?: boolean
|
|
1225
|
+
variables?: Readonly<Record<string, AnalyzerVariableState>>
|
|
1226
|
+
/** Expression declarations runtime dispatch may select when static focus is not exact. */
|
|
1227
|
+
possibleBodies?: readonly ResolvedDeclaration[]
|
|
1019
1228
|
}
|
|
1020
1229
|
|
|
1021
|
-
function resolvedDeclaration(
|
|
1230
|
+
function resolvedDeclaration(
|
|
1231
|
+
declared: SingleDeclaredFunction,
|
|
1232
|
+
model: ModelProvider | undefined,
|
|
1233
|
+
runtime: boolean
|
|
1234
|
+
): ResolvedDeclaration {
|
|
1022
1235
|
if (declared.expression === undefined) {
|
|
1023
|
-
return
|
|
1236
|
+
return {
|
|
1237
|
+
...(declared.minArity !== undefined && { minArity: declared.minArity }),
|
|
1238
|
+
...(declared.maxArity !== undefined && { maxArity: declared.maxArity }),
|
|
1239
|
+
...(declared.signature !== undefined && { signature: declared.signature }),
|
|
1240
|
+
}
|
|
1024
1241
|
}
|
|
1025
1242
|
// An expression-defined function takes no arguments, which is how the runtime
|
|
1026
1243
|
// calls it (`evaluateHostFunction`).
|
|
1027
|
-
|
|
1244
|
+
const expression = resolvedExpression(declared.expression)
|
|
1245
|
+
return {
|
|
1246
|
+
minArity: 0,
|
|
1247
|
+
maxArity: 0,
|
|
1248
|
+
...(declared.signature !== undefined && { signature: declared.signature }),
|
|
1249
|
+
...(expression !== undefined && { expression }),
|
|
1250
|
+
...(declared.criteria !== undefined && { criteria: declared.criteria }),
|
|
1251
|
+
...((declared.env !== undefined || declared.envTypes !== undefined) && {
|
|
1252
|
+
variables: runtime
|
|
1253
|
+
? runtimeAnalyzerEnvironmentVariables(declared.env, declared.envTypes, model)
|
|
1254
|
+
: analyzerEnvironmentVariables(declared.env, declared.envTypes, model),
|
|
1255
|
+
}),
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
function resolvedExpression(expression: unknown): { source: string; ast?: AstNode } | undefined {
|
|
1260
|
+
if (typeof expression === 'string') {
|
|
1261
|
+
return { source: expression }
|
|
1262
|
+
}
|
|
1263
|
+
const compiled = expression as { source?: unknown; ast?: unknown } | undefined
|
|
1264
|
+
if (typeof compiled?.source !== 'string') {
|
|
1265
|
+
return undefined
|
|
1266
|
+
}
|
|
1267
|
+
return {
|
|
1268
|
+
source: compiled.source,
|
|
1269
|
+
...(compiled.ast !== undefined && { ast: compiled.ast as AstNode }),
|
|
1270
|
+
}
|
|
1028
1271
|
}
|
|
1029
1272
|
|
|
1030
1273
|
/**
|
|
@@ -1061,7 +1304,7 @@ function mergedInput(candidates: readonly ResolvedDeclaration[]): InputSpec | un
|
|
|
1061
1304
|
function mergedResult(candidates: readonly ResolvedDeclaration[]): { types?: string[]; single?: boolean } | undefined {
|
|
1062
1305
|
const union = unionStates(
|
|
1063
1306
|
candidates.map(candidate => ({
|
|
1064
|
-
types: candidate.signature?.result?.types,
|
|
1307
|
+
types: candidate.signature?.result?.types === undefined ? undefined : [...candidate.signature.result.types],
|
|
1065
1308
|
single: candidate.signature?.result?.single,
|
|
1066
1309
|
}))
|
|
1067
1310
|
)
|
|
@@ -1074,23 +1317,6 @@ function mergedResult(candidates: readonly ResolvedDeclaration[]): { types?: str
|
|
|
1074
1317
|
}
|
|
1075
1318
|
}
|
|
1076
1319
|
|
|
1077
|
-
/** The behavior kind shared by every candidate type, or undefined when mixed/unknown. */
|
|
1078
|
-
function kindOf(state: StaticState): ValueKind | undefined {
|
|
1079
|
-
if (state.types === undefined || state.types.length === 0) {
|
|
1080
|
-
return undefined
|
|
1081
|
-
}
|
|
1082
|
-
let kind: ValueKind | undefined
|
|
1083
|
-
for (const type of state.types) {
|
|
1084
|
-
const typeKind = valueKindOfTypeName(type)
|
|
1085
|
-
if (kind === undefined) {
|
|
1086
|
-
kind = typeKind
|
|
1087
|
-
} else if (kind !== typeKind) {
|
|
1088
|
-
return undefined
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
return kind
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
1320
|
function typeSpecifierParts(node: AstNode): string[] | undefined {
|
|
1095
1321
|
if (node.kind === 'identifier') {
|
|
1096
1322
|
return [node.name]
|