semantifold 0.12.0 → 0.14.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.
- package/README.md +2 -0
- package/build/src/backends/effects.js +5 -2
- package/build/src/backends/java.js +33 -14
- package/build/src/backends/javascript.js +83 -18
- package/build/src/backends/php.d.ts.map +1 -1
- package/build/src/backends/php.js +88 -16
- package/build/src/backends/ruby.js +37 -12
- package/build/src/backends/scalars.js +2 -2
- package/build/src/backends/shared.d.ts +2 -1
- package/build/src/backends/shared.d.ts.map +1 -1
- package/build/src/backends/shared.js +299 -42
- package/build/src/backends/typescript.js +83 -18
- package/build/src/backends/writer.d.ts +18 -4
- package/build/src/backends/writer.d.ts.map +1 -1
- package/build/src/backends/writer.js +40 -4
- package/build/src/frontends/java.d.ts +9 -1
- package/build/src/frontends/java.d.ts.map +1 -1
- package/build/src/frontends/java.js +90 -43
- package/build/src/frontends/javascript-typescript.d.ts +3 -1
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
- package/build/src/frontends/javascript-typescript.js +133 -52
- package/build/src/frontends/php.d.ts +13 -1
- package/build/src/frontends/php.d.ts.map +1 -1
- package/build/src/frontends/php.js +226 -59
- package/build/src/frontends/ruby.d.ts +4 -1
- package/build/src/frontends/ruby.d.ts.map +1 -1
- package/build/src/frontends/ruby.js +113 -37
- package/build/src/frontends/types.d.ts +9 -2
- package/build/src/frontends/types.d.ts.map +1 -1
- package/build/src/frontends/types.js +26 -9
- package/build/src/language-registry.d.ts.map +1 -1
- package/build/src/language-registry.js +14 -6
- package/build/src/mapping.d.ts.map +1 -1
- package/build/src/mapping.js +4 -2
- package/build/src/semantic/effects.d.ts.map +1 -1
- package/build/src/semantic/effects.js +20 -6
- package/build/src/semantic/lifetime.js +28 -4
- package/build/src/semantic/provenance.d.ts.map +1 -1
- package/build/src/semantic/provenance.js +61 -9
- package/build/src/semantic/types.d.ts +214 -23
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +58 -11
- package/build/src/semantic/validate.d.ts +31 -2
- package/build/src/semantic/validate.d.ts.map +1 -1
- package/build/src/semantic/validate.js +450 -60
- package/docs/adr/20261026-typed-error-handling-extension.md +55 -0
- package/docs/adr-bounded-subtyping.md +180 -0
- package/docs/adr-numeric-tower.md +156 -0
- package/docs/architecture.md +1 -1
- package/docs/language-support.md +8 -8
- package/docs/testing.md +5 -3
- package/package.json +1 -1
- package/src/backends/effects.js +3 -1
- package/src/backends/java.js +33 -14
- package/src/backends/javascript.js +83 -18
- package/src/backends/php.js +89 -16
- package/src/backends/ruby.js +36 -12
- package/src/backends/scalars.js +1 -1
- package/src/backends/shared.js +298 -47
- package/src/backends/typescript.js +83 -18
- package/src/backends/writer.js +41 -3
- package/src/frontends/java.js +95 -43
- package/src/frontends/javascript-typescript.js +145 -53
- package/src/frontends/php.js +243 -58
- package/src/frontends/ruby.js +117 -35
- package/src/frontends/types.js +26 -8
- package/src/language-registry.js +13 -5
- package/src/mapping.js +3 -1
- package/src/semantic/effects.js +18 -5
- package/src/semantic/lifetime.js +29 -3
- package/src/semantic/provenance.js +60 -14
- package/src/semantic/types.js +62 -10
- package/src/semantic/validate.js +481 -61
package/src/frontends/php.js
CHANGED
|
@@ -6,13 +6,13 @@ import {missingType, parseFailure, unsupportedSyntax} from "../diagnostic.js"
|
|
|
6
6
|
import {blockReferencesProtectedEntry} from "../semantic/effects.js"
|
|
7
7
|
import {locationFromOffsets, moduleLocation} from "../semantic/location.js"
|
|
8
8
|
import {withAdaptedOperation} from "../semantic/operators.js"
|
|
9
|
-
import {withParserRanges} from "../semantic/provenance.js"
|
|
9
|
+
import {setParserRanges, withParserRanges} from "../semantic/provenance.js"
|
|
10
10
|
import {isTask034ResourceProbe} from "../semantic/capabilities.js"
|
|
11
11
|
import {hasExactEffectSupport} from "../backends/effects.js"
|
|
12
12
|
import {hasOnlyUnicodeScalars, scalarType} from "../semantic/scalars.js"
|
|
13
13
|
import {typeContainsAnyVariable} from "../semantic/generics.js"
|
|
14
14
|
import {requireSourceReturnType, requireSourceScalarType, sourceScalarType} from "./scalars.js"
|
|
15
|
-
import {capabilityFunctionSignatures, documentedValueType, instantiatedRecordFieldType, iterationBindingType, iterationOperandType, knownCallParameterTypes, knownCallReturnType, optionalType, orderedMapTypeFromMap, ownedResourceType, preservesGenericOptionalEvidence, recordType, referenceType, sameValueType} from "./types.js"
|
|
15
|
+
import {capabilityFunctionSignatures, documentedValueType, instantiatedRecordFieldType, interfaceType, iterationBindingType, iterationOperandType, knownCallParameterTypes, knownCallReturnType, optionalType, orderedMapTypeFromMap, ownedResourceType, preservesGenericOptionalEvidence, recordType, referenceType, sameValueType} from "./types.js"
|
|
16
16
|
const parser = new PhpParser.Engine({
|
|
17
17
|
ast: {withPositions: true},
|
|
18
18
|
parser: {extractDoc: true, suppressErrors: false}
|
|
@@ -39,13 +39,16 @@ const phpBinaryOperations = new Map([
|
|
|
39
39
|
/**
|
|
40
40
|
* @typedef PhpConversionContext
|
|
41
41
|
* @property {Map<string, import("../semantic/types.js").SemanticBindingType>} bindings - Explicitly typed visible bindings.
|
|
42
|
+
* @property {Set<string>} [catchBindingNames] - Lexically visible exact catch binding names.
|
|
43
|
+
* @property {Map<string, import("../semantic/types.js").CatchBinding>} [catchBindingNodes] - Exact catch binding nodes by visible name.
|
|
42
44
|
* @property {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Reference classes by identity.
|
|
43
45
|
* @property {import("../semantic/types.js").ClassDeclaration} [currentClass] - Enclosing reference class.
|
|
46
|
+
* @property {Map<string, import("../semantic/types.js").InterfaceDeclaration>} interfaces - Interface declarations by identity.
|
|
44
47
|
* @property {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Error declarations by source name.
|
|
45
48
|
* @property {Map<string, import("../semantic/types.js").ErrorDeclaration>} errors - Error declarations by identity.
|
|
46
49
|
* @property {Map<string, PhpFunctionSignature>} functions - Explicit module function signatures.
|
|
47
50
|
* @property {Set<object>} [orderedMapDeclarations] - Exact local declarations selected by parser-owned pair iteration.
|
|
48
|
-
* @property {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
|
|
51
|
+
* @property {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} recordNames - Nominal declarations by source name.
|
|
49
52
|
* @property {Map<string, import("../semantic/types.js").RecordDeclaration>} records - Record declarations by identity.
|
|
50
53
|
* @property {import("../semantic/types.js").SemanticFunctionReturnType | undefined} returnType - Enclosing return type.
|
|
51
54
|
* @property {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
|
|
@@ -323,6 +326,9 @@ function convertExpression(node, filename, source, context, expectedType, preser
|
|
|
323
326
|
const declaration = context.recordNames.get(name.name)
|
|
324
327
|
|
|
325
328
|
if (!declaration) return unsupportedSyntax("php", "construction of an unknown nominal class", nodeLocation(construction.what, filename, source))
|
|
329
|
+
if (declaration.kind == "InterfaceDeclaration") {
|
|
330
|
+
return unsupportedSyntax("php", "construction of an interface", nodeLocation(construction.what, filename, source))
|
|
331
|
+
}
|
|
326
332
|
if (declaration.kind == "ClassDeclaration") {
|
|
327
333
|
const unsupportedArgument = construction.arguments.find((argument) =>
|
|
328
334
|
argument.kind == "namedargument" || argument.kind == "variadic" || Reflect.get(argument, "byref") === true)
|
|
@@ -380,6 +386,9 @@ function convertExpression(node, filename, source, context, expectedType, preser
|
|
|
380
386
|
if (receiverType?.kind == "ReferenceType") {
|
|
381
387
|
return unsupportedSyntax("php", "private reference-class field access", location)
|
|
382
388
|
}
|
|
389
|
+
if (receiverType?.kind == "InterfaceType") {
|
|
390
|
+
return unsupportedSyntax("php", "interface receiver member access", location)
|
|
391
|
+
}
|
|
383
392
|
const receiverRecord = receiverType?.kind == "OptionalType" ? receiverType.valueType : receiverType
|
|
384
393
|
const declaration = receiverRecord?.kind == "RecordType" ? context.records.get(receiverRecord.declarationId) : undefined
|
|
385
394
|
|
|
@@ -508,8 +517,10 @@ function convertExpression(node, filename, source, context, expectedType, preser
|
|
|
508
517
|
const member = lookup.offset.kind == "identifier" ? /** @type {import("php-parser").Identifier} */ (lookup.offset) : undefined
|
|
509
518
|
const receiverType = knownExpressionType(lookup.what, context)
|
|
510
519
|
|
|
511
|
-
if (member && typeof member.name == "string" && receiverType?.kind == "ReferenceType") {
|
|
512
|
-
const method =
|
|
520
|
+
if (member && typeof member.name == "string" && (receiverType?.kind == "ReferenceType" || receiverType?.kind == "InterfaceType")) {
|
|
521
|
+
const method = receiverType.kind == "ReferenceType"
|
|
522
|
+
? context.classes.get(receiverType.declarationId)?.methods.find((candidate) => candidate.name == member.name)
|
|
523
|
+
: context.interfaces.get(receiverType.declarationId)?.methods.find((candidate) => candidate.name == member.name)
|
|
513
524
|
const unsupportedArgument = call.arguments.find((argument) =>
|
|
514
525
|
argument.kind == "namedargument" || argument.kind == "variadic" || Reflect.get(argument, "byref") === true)
|
|
515
526
|
|
|
@@ -630,6 +641,9 @@ function knownExpressionType(node, context) {
|
|
|
630
641
|
if (receiver?.kind == "ReferenceType" && member && typeof member.name == "string") {
|
|
631
642
|
return context.classes.get(receiver.declarationId)?.methods.find((method) => method.name == member.name)?.returnType
|
|
632
643
|
}
|
|
644
|
+
if (receiver?.kind == "InterfaceType" && member && typeof member.name == "string") {
|
|
645
|
+
return context.interfaces.get(receiver.declarationId)?.methods.find((method) => method.name == member.name)?.returnType
|
|
646
|
+
}
|
|
633
647
|
}
|
|
634
648
|
}
|
|
635
649
|
if (node.kind == "offsetlookup") {
|
|
@@ -646,7 +660,9 @@ function knownExpressionType(node, context) {
|
|
|
646
660
|
|
|
647
661
|
if (declaration?.id) return declaration.kind == "ClassDeclaration"
|
|
648
662
|
? {declarationId: declaration.id, kind: "ReferenceType"}
|
|
649
|
-
:
|
|
663
|
+
: declaration.kind == "InterfaceDeclaration"
|
|
664
|
+
? {declarationId: declaration.id, kind: "InterfaceType"}
|
|
665
|
+
: {declarationId: declaration.id, kind: "RecordType"}
|
|
650
666
|
}
|
|
651
667
|
if (node.kind == "propertylookup") {
|
|
652
668
|
const read = /** @type {import("php-parser").PropertyLookup} */ (node)
|
|
@@ -754,7 +770,7 @@ function convertReturn(node, filename, source, context) {
|
|
|
754
770
|
* @param {string} name - Local name.
|
|
755
771
|
* @param {string} filename - Source filename.
|
|
756
772
|
* @param {string} source - Complete source.
|
|
757
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
|
|
773
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} recordNames - Nominal declarations by source name.
|
|
758
774
|
* @param {Map<string, import("../semantic/types.js").TypeParameter>} [typeParameters] - Declaration-scoped parameters.
|
|
759
775
|
* @returns {{immutable: boolean, type: import("../semantic/types.js").SemanticValueType} | undefined} Metadata when present.
|
|
760
776
|
*/
|
|
@@ -874,7 +890,7 @@ function convertLocalStatement(node, filename, source, context) {
|
|
|
874
890
|
const inferredType = knownExpressionType(assignment.right, context)
|
|
875
891
|
|
|
876
892
|
if (inferredType?.kind == "RecordType" || inferredType?.kind == "ReferenceType" ||
|
|
877
|
-
inferredType?.kind == "OwnedResourceType") {
|
|
893
|
+
inferredType?.kind == "InterfaceType" || inferredType?.kind == "OwnedResourceType") {
|
|
878
894
|
const initializer = convertExpression(assignment.right, filename, source, context, inferredType)
|
|
879
895
|
|
|
880
896
|
context.bindings.set(variable.name, inferredType)
|
|
@@ -963,12 +979,24 @@ function convertStatement(node, filename, source, context) {
|
|
|
963
979
|
* @param {string} filename - Source filename.
|
|
964
980
|
* @param {string} source - Complete source.
|
|
965
981
|
* @param {PhpConversionContext} context - Typed lexical context.
|
|
966
|
-
* @returns {import("../semantic/types.js").RaiseStatement} Semantic raise.
|
|
982
|
+
* @returns {import("../semantic/types.js").RaiseStatement | import("../semantic/types.js").RethrowStatement} Semantic raise or rethrow.
|
|
967
983
|
*/
|
|
968
984
|
function convertPhpRaise(node, filename, source, context) {
|
|
969
985
|
const location = nodeLocation(node, filename, source)
|
|
970
986
|
const construction = Reflect.get(node, "what")
|
|
971
987
|
|
|
988
|
+
if (construction?.kind == "variable" && typeof construction.name == "string" && !construction.curly &&
|
|
989
|
+
context.bindings.get(construction.name)?.kind == "ErrorType" && context.catchBindingNames?.has(construction.name)) {
|
|
990
|
+
const bindingNode = context.catchBindingNodes?.get(construction.name)
|
|
991
|
+
|
|
992
|
+
if (!bindingNode) return unsupportedSyntax("php", "throw of a catch binding without an exact handler binding", location)
|
|
993
|
+
return withParserRanges({
|
|
994
|
+
binding: construction.name,
|
|
995
|
+
bindingNode,
|
|
996
|
+
kind: /** @type {const} */ ("RethrowStatement"),
|
|
997
|
+
location
|
|
998
|
+
}, {keyword: tokenLocation("throw", node.loc?.start.offset ?? 0, construction.loc?.start.offset ?? source.length, filename, source)})
|
|
999
|
+
}
|
|
972
1000
|
if (!construction || construction.kind != "new" || construction.what?.kind != "name" ||
|
|
973
1001
|
construction.what.resolution != "uqn" || !context.errorNames.has(construction.what.name) ||
|
|
974
1002
|
!Array.isArray(construction.arguments) || construction.arguments.length != 1 ||
|
|
@@ -1002,42 +1030,69 @@ function convertPhpTry(node, filename, source, context) {
|
|
|
1002
1030
|
const location = nodeLocation(node, filename, source)
|
|
1003
1031
|
const catches = Reflect.get(node, "catches")
|
|
1004
1032
|
const body = Reflect.get(node, "body")
|
|
1033
|
+
const always = Reflect.get(node, "always")
|
|
1005
1034
|
|
|
1006
|
-
if (
|
|
1007
|
-
return unsupportedSyntax("php", "try without
|
|
1035
|
+
if (!Array.isArray(catches) || (catches.length == 0 && !always) || body?.kind != "block") {
|
|
1036
|
+
return unsupportedSyntax("php", "try without typed catches or finally", location)
|
|
1008
1037
|
}
|
|
1009
|
-
|
|
1010
|
-
const
|
|
1011
|
-
const
|
|
1038
|
+
/** @type {import("../semantic/types.js").CatchHandler[]} */
|
|
1039
|
+
const handlers = []
|
|
1040
|
+
const seenHandlerNames = new Set(context.catchBindingNames ?? [])
|
|
1012
1041
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
binding
|
|
1016
|
-
return unsupportedSyntax("php", "broad, multiple, or malformed catch", nodeLocation(handler, filename, source))
|
|
1017
|
-
}
|
|
1018
|
-
const declaration = /** @type {import("../semantic/types.js").ErrorDeclaration} */ (context.errorNames.get(caught.name))
|
|
1019
|
-
const caughtLocation = nodeLocation(caught, filename, source)
|
|
1020
|
-
const catchType = withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {
|
|
1021
|
-
type: caughtLocation
|
|
1022
|
-
})
|
|
1023
|
-
const bodyContext = {...context, bindings: new Map(context.bindings)}
|
|
1024
|
-
const catchContext = {...context, bindings: new Map(context.bindings)}
|
|
1042
|
+
for (const handler of catches) {
|
|
1043
|
+
const caught = handler.what?.[0]
|
|
1044
|
+
const binding = handler.variable
|
|
1025
1045
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1046
|
+
if (handler.what?.length != 1 || caught?.kind != "name" || caught.resolution != "uqn" ||
|
|
1047
|
+
!context.errorNames.has(caught.name) || binding?.kind != "variable" || typeof binding.name != "string" ||
|
|
1048
|
+
binding.curly || handler.body?.kind != "block") {
|
|
1049
|
+
return unsupportedSyntax("php", "broad, multiple, or malformed catch", nodeLocation(handler, filename, source))
|
|
1050
|
+
}
|
|
1051
|
+
if (seenHandlerNames.has(binding.name)) {
|
|
1052
|
+
return unsupportedSyntax("php", `catch binding '${binding.name}' shadows an outer handler binding`, nodeLocation(binding, filename, source))
|
|
1053
|
+
}
|
|
1054
|
+
seenHandlerNames.add(binding.name)
|
|
1055
|
+
const declaration = /** @type {import("../semantic/types.js").ErrorDeclaration} */ (context.errorNames.get(caught.name))
|
|
1056
|
+
const caughtLocation = nodeLocation(caught, filename, source)
|
|
1057
|
+
const catchType = withParserRanges({declarationId: /** @type {string} */ (declaration.id), kind: /** @type {const} */ ("ErrorType")}, {
|
|
1058
|
+
type: caughtLocation
|
|
1059
|
+
})
|
|
1060
|
+
const catchContext = {...context, bindings: new Map(context.bindings), catchBindingNames: new Set(seenHandlerNames),
|
|
1061
|
+
catchBindingNodes: new Map(context.catchBindingNodes)}
|
|
1062
|
+
|
|
1063
|
+
catchContext.bindings.set(binding.name, catchType)
|
|
1064
|
+
const catchBindingNode = withParserRanges({
|
|
1030
1065
|
kind: /** @type {const} */ ("CatchBinding"),
|
|
1031
1066
|
location: nodeLocation(binding, filename, source),
|
|
1032
1067
|
mutable: /** @type {const} */ (false),
|
|
1033
1068
|
name: binding.name,
|
|
1034
1069
|
type: catchType
|
|
1035
|
-
}, {name: nodeLocation(binding, filename, source)})
|
|
1036
|
-
|
|
1037
|
-
|
|
1070
|
+
}, {name: nodeLocation(binding, filename, source)})
|
|
1071
|
+
|
|
1072
|
+
catchContext.catchBindingNodes.set(binding.name, catchBindingNode)
|
|
1073
|
+
handlers.push(withParserRanges({
|
|
1074
|
+
catchBinding: catchBindingNode,
|
|
1075
|
+
catchBody: convertBlock(handler.body, filename, source, catchContext),
|
|
1076
|
+
catchType,
|
|
1077
|
+
kind: /** @type {const} */ ("CatchHandler"),
|
|
1078
|
+
location: nodeLocation(handler, filename, source)
|
|
1079
|
+
}, {catch: nodeLocation(handler, filename, source)}))
|
|
1080
|
+
}
|
|
1081
|
+
const bodyContext = {...context, bindings: new Map(context.bindings)}
|
|
1082
|
+
const semantic = /** @type {import("../semantic/types.js").TryStatement} */ (withParserRanges({
|
|
1083
|
+
body: convertBlock(body, filename, source, bodyContext),
|
|
1084
|
+
handlers,
|
|
1038
1085
|
kind: /** @type {const} */ ("TryStatement"),
|
|
1039
1086
|
location
|
|
1040
|
-
}, {
|
|
1087
|
+
}, {try: tokenLocation("try", node.loc?.start.offset ?? 0, body.loc?.start.offset ?? source.length, filename, source)}))
|
|
1088
|
+
|
|
1089
|
+
if (always) {
|
|
1090
|
+
if (always.kind != "block") return unsupportedSyntax("php", "malformed finally block", nodeLocation(always, filename, source))
|
|
1091
|
+
semantic.finallyBody = convertBlock(always, filename, source, bodyContext)
|
|
1092
|
+
setParserRanges(semantic, {finally: nodeLocation(always, filename, source),
|
|
1093
|
+
try: tokenLocation("try", node.loc?.start.offset ?? 0, body.loc?.start.offset ?? source.length, filename, source)})
|
|
1094
|
+
}
|
|
1095
|
+
return semantic
|
|
1041
1096
|
}
|
|
1042
1097
|
|
|
1043
1098
|
/**
|
|
@@ -1154,7 +1209,7 @@ function convertBlock(node, filename, source, context) {
|
|
|
1154
1209
|
* @param {import("../semantic/types.js").SourceLocation} location - Source location.
|
|
1155
1210
|
* @param {string} filename - Source filename.
|
|
1156
1211
|
* @param {string} source - Complete source.
|
|
1157
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [recordNames] - Nominal declarations by source name.
|
|
1212
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} [recordNames] - Nominal declarations by source name.
|
|
1158
1213
|
* @returns {import("../semantic/types.js").SemanticValueType} Semantic type.
|
|
1159
1214
|
*/
|
|
1160
1215
|
function convertType(sourceType, subject, location, filename, source, recordNames = new Map()) {
|
|
@@ -1170,9 +1225,11 @@ function convertType(sourceType, subject, location, filename, source, recordName
|
|
|
1170
1225
|
|
|
1171
1226
|
if (declaration?.id) return declaration.kind == "ClassDeclaration"
|
|
1172
1227
|
? referenceType(declaration.id, typeLocation)
|
|
1173
|
-
: declaration.kind == "
|
|
1174
|
-
?
|
|
1175
|
-
:
|
|
1228
|
+
: declaration.kind == "InterfaceDeclaration"
|
|
1229
|
+
? interfaceType(declaration.id, typeLocation)
|
|
1230
|
+
: declaration.kind == "EffectResourceDeclaration"
|
|
1231
|
+
? ownedResourceType(declaration.id, typeLocation)
|
|
1232
|
+
: recordType(declaration.id, typeLocation)
|
|
1176
1233
|
|
|
1177
1234
|
return requireSourceScalarType("php", typeName, subject, location, typeLocation)
|
|
1178
1235
|
}
|
|
@@ -1290,7 +1347,7 @@ function nullableType(valueType, sourceType, ownerStart, filename, source) {
|
|
|
1290
1347
|
* @param {import("../semantic/types.js").SourceLocation} location - Function location.
|
|
1291
1348
|
* @param {string} filename - Source filename.
|
|
1292
1349
|
* @param {string} source - Complete source.
|
|
1293
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} [recordNames] - Nominal declarations by source name.
|
|
1350
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} [recordNames] - Nominal declarations by source name.
|
|
1294
1351
|
* @returns {import("../semantic/types.js").SemanticFunctionReturnType} Semantic return type.
|
|
1295
1352
|
*/
|
|
1296
1353
|
function convertReturnType(sourceType, subject, location, filename, source, recordNames = new Map()) {
|
|
@@ -1303,9 +1360,11 @@ function convertReturnType(sourceType, subject, location, filename, source, reco
|
|
|
1303
1360
|
|
|
1304
1361
|
if (declaration?.id) return declaration.kind == "ClassDeclaration"
|
|
1305
1362
|
? referenceType(declaration.id, nodeLocation(sourceType, filename, source))
|
|
1306
|
-
: declaration.kind == "
|
|
1307
|
-
?
|
|
1308
|
-
:
|
|
1363
|
+
: declaration.kind == "InterfaceDeclaration"
|
|
1364
|
+
? interfaceType(declaration.id, nodeLocation(sourceType, filename, source))
|
|
1365
|
+
: declaration.kind == "EffectResourceDeclaration"
|
|
1366
|
+
? ownedResourceType(declaration.id, nodeLocation(sourceType, filename, source))
|
|
1367
|
+
: recordType(declaration.id, nodeLocation(sourceType, filename, source))
|
|
1309
1368
|
|
|
1310
1369
|
return requireSourceReturnType("php", typeName, subject, location, nodeLocation(sourceType, filename, source))
|
|
1311
1370
|
}
|
|
@@ -1315,7 +1374,7 @@ function convertReturnType(sourceType, subject, location, filename, source, reco
|
|
|
1315
1374
|
* @param {import("php-parser").Function} node - PHP function node.
|
|
1316
1375
|
* @param {string} filename - Source filename.
|
|
1317
1376
|
* @param {string} source - Complete source.
|
|
1318
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
|
|
1377
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} recordNames - Nominal declarations by source name.
|
|
1319
1378
|
* @param {string} ownerId - Stable function identity.
|
|
1320
1379
|
* @returns {PhpFunctionSignature} Typed function signature.
|
|
1321
1380
|
*/
|
|
@@ -1416,7 +1475,7 @@ function convertFunctionSignature(node, filename, source, recordNames, ownerId)
|
|
|
1416
1475
|
* @param {string | undefined} nativeType - Native PHP type name.
|
|
1417
1476
|
* @param {boolean} nativeNullable - Whether the native carrier has PHP's nullable marker.
|
|
1418
1477
|
* @param {import("../semantic/types.js").SemanticValueType} documentedType - Exact semantic document type.
|
|
1419
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} records - Visible nominal declarations by name.
|
|
1478
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} records - Visible nominal declarations by name.
|
|
1420
1479
|
* @returns {boolean} Whether the native carrier agrees without changing meaning.
|
|
1421
1480
|
*/
|
|
1422
1481
|
function phpDocumentedNativeTypeMatches(nativeType, nativeNullable, documentedType, records) {
|
|
@@ -1441,6 +1500,9 @@ function phpDocumentedNativeTypeMatches(nativeType, nativeNullable, documentedTy
|
|
|
1441
1500
|
if (documentedType.kind == "ReferenceType") {
|
|
1442
1501
|
return nativeType !== undefined && records.get(nativeType)?.id == documentedType.declarationId
|
|
1443
1502
|
}
|
|
1503
|
+
if (documentedType.kind == "InterfaceType") {
|
|
1504
|
+
return nativeType !== undefined && records.get(nativeType)?.id == documentedType.declarationId
|
|
1505
|
+
}
|
|
1444
1506
|
|
|
1445
1507
|
return false
|
|
1446
1508
|
}
|
|
@@ -1450,16 +1512,17 @@ function phpDocumentedNativeTypeMatches(nativeType, nativeNullable, documentedTy
|
|
|
1450
1512
|
* @param {import("php-parser").Function} node - PHP function node.
|
|
1451
1513
|
* @param {PhpFunctionSignature} signature - Preconverted explicit signature.
|
|
1452
1514
|
* @param {Map<string, PhpFunctionSignature>} functions - Module function signatures.
|
|
1453
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} recordNames - Nominal declarations by source name.
|
|
1515
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} recordNames - Nominal declarations by source name.
|
|
1454
1516
|
* @param {Map<string, import("../semantic/types.js").RecordDeclaration>} records - Record declarations by identity.
|
|
1455
1517
|
* @param {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Reference classes by identity.
|
|
1518
|
+
* @param {Map<string, import("../semantic/types.js").InterfaceDeclaration>} interfaces - Interface declarations by identity.
|
|
1456
1519
|
* @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Error declarations by source name.
|
|
1457
1520
|
* @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errors - Error declarations by identity.
|
|
1458
1521
|
* @param {string} filename - Source filename.
|
|
1459
1522
|
* @param {string} source - Complete source.
|
|
1460
1523
|
* @returns {import("../semantic/types.js").FunctionDeclaration} Semantic function.
|
|
1461
1524
|
*/
|
|
1462
|
-
function convertFunction(node, signature, functions, recordNames, records, classes, errorNames, errors, filename, source) {
|
|
1525
|
+
function convertFunction(node, signature, functions, recordNames, records, classes, interfaces, errorNames, errors, filename, source) {
|
|
1463
1526
|
if (!node.body) return unsupportedSyntax("php", "function body", signature.location)
|
|
1464
1527
|
const context = {
|
|
1465
1528
|
bindings: new Map(signature.parameters.map((parameter) => [parameter.name, parameter.type])),
|
|
@@ -1475,7 +1538,7 @@ function convertFunction(node, signature, functions, recordNames, records, class
|
|
|
1475
1538
|
}
|
|
1476
1539
|
|
|
1477
1540
|
return withParserRanges({
|
|
1478
|
-
body: convertBlock(node.body, filename, source, context),
|
|
1541
|
+
body: convertBlock(node.body, filename, source, {...context, interfaces}),
|
|
1479
1542
|
kind: "FunctionDeclaration",
|
|
1480
1543
|
location: signature.location,
|
|
1481
1544
|
name: signature.name,
|
|
@@ -1572,7 +1635,7 @@ function convertPhpError(node, declaration, filename, source) {
|
|
|
1572
1635
|
/**
|
|
1573
1636
|
* Reads one exact public PHP constructor or instance-method signature.
|
|
1574
1637
|
* @param {import("php-parser").Method} node - Parser-owned method.
|
|
1575
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} nominalNames - Visible nominal declarations.
|
|
1638
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} nominalNames - Visible nominal declarations.
|
|
1576
1639
|
* @param {string} ownerId - Stable callable identity.
|
|
1577
1640
|
* @param {boolean} constructor - Whether this is the sole constructor.
|
|
1578
1641
|
* @param {string} filename - Source filename.
|
|
@@ -1655,13 +1718,106 @@ function convertPhpClassCallableSignature(node, nominalNames, ownerId, construct
|
|
|
1655
1718
|
return {location, name, nameLocation: nodeLocation(nameNode, filename, source), parameters, returnType}
|
|
1656
1719
|
}
|
|
1657
1720
|
|
|
1721
|
+
/**
|
|
1722
|
+
* Converts the signature-only PHP interface profile.
|
|
1723
|
+
* @param {import("php-parser").Class} node - Parser interface.
|
|
1724
|
+
* @param {import("../semantic/types.js").InterfaceDeclaration} declaration - Predeclared interface identity.
|
|
1725
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} nominalNames - Visible nominal declarations.
|
|
1726
|
+
* @param {string} filename - Source filename.
|
|
1727
|
+
* @param {string} source - Complete source.
|
|
1728
|
+
* @returns {import("../semantic/types.js").InterfaceDeclaration} Semantic interface.
|
|
1729
|
+
*/
|
|
1730
|
+
function convertPhpInterface(node, declaration, nominalNames, filename, source) {
|
|
1731
|
+
const location = nodeLocation(node, filename, source)
|
|
1732
|
+
const parents = /** @type {readonly import("php-parser").Name[]} */ (/** @type {unknown} */ (node.extends ?? []))
|
|
1733
|
+
|
|
1734
|
+
if (node.isAnonymous || node.attrGroups.length) return unsupportedSyntax("php", "malformed interface declaration", location)
|
|
1735
|
+
const parentReferences = parents.map((parent) => {
|
|
1736
|
+
if (parent.kind != "name" || parent.resolution != "uqn" || typeof parent.name != "string") {
|
|
1737
|
+
return unsupportedSyntax("php", "qualified or dynamic interface parent", nodeLocation(parent, filename, source))
|
|
1738
|
+
}
|
|
1739
|
+
const parentDeclaration = nominalNames.get(parent.name)
|
|
1740
|
+
|
|
1741
|
+
if (!parentDeclaration?.id || parentDeclaration.kind != "InterfaceDeclaration") {
|
|
1742
|
+
return unsupportedSyntax("php", `interface parent '${parent.name}' is not a declared interface`, nodeLocation(parent, filename, source))
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
return interfaceType(/** @type {string} */ (parentDeclaration.id), nodeLocation(parent, filename, source))
|
|
1746
|
+
})
|
|
1747
|
+
const methodNodes = /** @type {import("php-parser").Method[]} */ (node.body.filter((member) => member.kind == "method"))
|
|
1748
|
+
|
|
1749
|
+
if (methodNodes.length != node.body.length) {
|
|
1750
|
+
return unsupportedSyntax("php", "interface member outside one signature-only public method",
|
|
1751
|
+
nodeLocation(node.body.find((member) => member.kind != "method") ?? node, filename, source))
|
|
1752
|
+
}
|
|
1753
|
+
declaration.methods = methodNodes.map((method, index) => convertPhpInterfaceMethodSignature(method, nominalNames,
|
|
1754
|
+
`${declaration.id}:method:${index}`, filename, source))
|
|
1755
|
+
const nameNode = typeof node.name == "string" ? node : node.name
|
|
1756
|
+
|
|
1757
|
+
return withParserRanges({
|
|
1758
|
+
...declaration,
|
|
1759
|
+
...(parentReferences.length > 0 ? {extends: parentReferences} : {})
|
|
1760
|
+
}, {name: nodeLocation(nameNode, filename, source)})
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
/**
|
|
1764
|
+
* Reads one exact signature-only PHP interface method.
|
|
1765
|
+
* @param {import("php-parser").Method} node - Parser interface method.
|
|
1766
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} nominalNames - Visible nominal declarations.
|
|
1767
|
+
* @param {string} ownerId - Stable interface method identity.
|
|
1768
|
+
* @param {string} filename - Source filename.
|
|
1769
|
+
* @param {string} source - Complete source.
|
|
1770
|
+
* @returns {import("../semantic/types.js").InterfaceMethodDeclaration} Semantic interface method.
|
|
1771
|
+
*/
|
|
1772
|
+
function convertPhpInterfaceMethodSignature(node, nominalNames, ownerId, filename, source) {
|
|
1773
|
+
const location = nodeLocation(node, filename, source)
|
|
1774
|
+
const name = typeof node.name == "string" ? node.name : node.name.name
|
|
1775
|
+
|
|
1776
|
+
if (node.visibility != "public" || node.isStatic || node.isAbstract || node.isFinal || Reflect.get(node, "isReadonly") ||
|
|
1777
|
+
node.byref || node.nullable || node.attrGroups.length || node.body !== null) {
|
|
1778
|
+
return unsupportedSyntax("php", "interface method outside one public signature-only declaration", location)
|
|
1779
|
+
}
|
|
1780
|
+
const documented = functionDocumentedTypes(/** @type {import("php-parser").Function} */ (/** @type {unknown} */ (node)),
|
|
1781
|
+
filename, source, ownerId)
|
|
1782
|
+
|
|
1783
|
+
if (documented.parameters.size > 0 || documented.returnType || documented.typeParameters?.length) {
|
|
1784
|
+
return unsupportedSyntax("php", "documented interface method type annotation", location)
|
|
1785
|
+
}
|
|
1786
|
+
if (!node.type) return missingType("php", `Interface method '${name}' return`, location)
|
|
1787
|
+
const parameters = node.arguments.map((parameter) => {
|
|
1788
|
+
const parameterLocation = nodeLocation(parameter, filename, source)
|
|
1789
|
+
const parameterName = typeof parameter.name == "string" ? parameter.name : parameter.name.name
|
|
1790
|
+
|
|
1791
|
+
if (parameter.byref || parameter.variadic || parameter.value || parameter.flags || parameter.readonly || parameter.nullable ||
|
|
1792
|
+
parameter.hooks.length || parameter.attrGroups.length) {
|
|
1793
|
+
return unsupportedSyntax("php", "noncanonical interface method parameter", parameterLocation)
|
|
1794
|
+
}
|
|
1795
|
+
if (!parameter.type) return missingType("php", `Parameter '${parameterName}'`, parameterLocation)
|
|
1796
|
+
const type = convertType(parameter.type, `Parameter '${parameterName}'`, parameterLocation, filename, source, nominalNames)
|
|
1797
|
+
|
|
1798
|
+
if (type.kind == "OptionalType") return unsupportedSyntax("php", "optional interface method parameter", parameterLocation)
|
|
1799
|
+
const nameNode = typeof parameter.name == "string" ? parameter : parameter.name
|
|
1800
|
+
|
|
1801
|
+
return withParserRanges({kind: /** @type {const} */ ("Parameter"), location: parameterLocation,
|
|
1802
|
+
name: parameterName, type}, {name: nodeLocation(nameNode, filename, source)})
|
|
1803
|
+
})
|
|
1804
|
+
const returnType = convertReturnType(node.type, `Interface method '${name}' return`, location, filename, source, nominalNames)
|
|
1805
|
+
|
|
1806
|
+
if (returnType.kind == "OptionalType") return unsupportedSyntax("php", "optional interface method return", location)
|
|
1807
|
+
const nameNode = typeof node.name == "string" ? node : node.name
|
|
1808
|
+
|
|
1809
|
+
return withParserRanges({kind: /** @type {const} */ ("InterfaceMethodDeclaration"), location, name, parameters, returnType},
|
|
1810
|
+
{name: nodeLocation(nameNode, filename, source)})
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1658
1813
|
/**
|
|
1659
1814
|
* Converts the bounded final/private-field PHP reference-class profile.
|
|
1660
1815
|
* @param {import("php-parser").Class} node - Parser class.
|
|
1661
1816
|
* @param {import("../semantic/types.js").ClassDeclaration} declaration - Predeclared class identity.
|
|
1662
|
-
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration>} nominalNames - Visible nominal declarations.
|
|
1817
|
+
* @param {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} nominalNames - Visible nominal declarations.
|
|
1663
1818
|
* @param {Map<string, import("../semantic/types.js").RecordDeclaration>} records - Records by identity.
|
|
1664
1819
|
* @param {Map<string, import("../semantic/types.js").ClassDeclaration>} classes - Classes by identity.
|
|
1820
|
+
* @param {Map<string, import("../semantic/types.js").InterfaceDeclaration>} interfaces - Interfaces by identity.
|
|
1665
1821
|
* @param {Map<string, PhpFunctionSignature>} functions - Top-level signatures.
|
|
1666
1822
|
* @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errorNames - Errors by name.
|
|
1667
1823
|
* @param {Map<string, import("../semantic/types.js").ErrorDeclaration>} errors - Errors by identity.
|
|
@@ -1669,12 +1825,28 @@ function convertPhpClassCallableSignature(node, nominalNames, ownerId, construct
|
|
|
1669
1825
|
* @param {string} source - Complete source.
|
|
1670
1826
|
* @returns {import("../semantic/types.js").ClassDeclaration} Semantic class.
|
|
1671
1827
|
*/
|
|
1672
|
-
function convertPhpReferenceClass(node, declaration, nominalNames, records, classes, functions, errorNames, errors,
|
|
1828
|
+
function convertPhpReferenceClass(node, declaration, nominalNames, records, classes, interfaces, functions, errorNames, errors,
|
|
1673
1829
|
filename, source) {
|
|
1674
1830
|
const location = nodeLocation(node, filename, source)
|
|
1675
1831
|
|
|
1676
|
-
if (node.isAnonymous || !node.isFinal || node.isReadonly || node.isAbstract || node.extends || node.
|
|
1677
|
-
|
|
1832
|
+
if (node.isAnonymous || !node.isFinal || node.isReadonly || node.isAbstract || node.extends || node.attrGroups.length) {
|
|
1833
|
+
return unsupportedSyntax("php", "reference class inheritance or noncanonical modifier", location)
|
|
1834
|
+
}
|
|
1835
|
+
const implementedReferences = /** @type {readonly import("php-parser").Name[]} */ (/** @type {unknown} */ (node.implements ?? []))
|
|
1836
|
+
.map((implemented) => {
|
|
1837
|
+
const implementedLocation = nodeLocation(implemented, filename, source)
|
|
1838
|
+
|
|
1839
|
+
if (implemented.kind != "name" || implemented.resolution != "uqn" || typeof implemented.name != "string") {
|
|
1840
|
+
return unsupportedSyntax("php", "qualified or dynamic interface implementation", implementedLocation)
|
|
1841
|
+
}
|
|
1842
|
+
const implementedDeclaration = nominalNames.get(implemented.name)
|
|
1843
|
+
|
|
1844
|
+
if (!implementedDeclaration?.id || implementedDeclaration.kind != "InterfaceDeclaration") {
|
|
1845
|
+
return unsupportedSyntax("php", `implemented interface '${implemented.name}' is not a declared interface`, implementedLocation)
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
return interfaceType(/** @type {string} */ (implementedDeclaration.id), implementedLocation)
|
|
1849
|
+
})
|
|
1678
1850
|
const fieldStatements = node.body.filter((member) => member.kind == "propertystatement")
|
|
1679
1851
|
const methodNodes = /** @type {import("php-parser").Method[]} */ (node.body.filter((member) => member.kind == "method"))
|
|
1680
1852
|
const constructors = methodNodes.filter((method) => (typeof method.name == "string" ? method.name : method.name.name) == "__construct")
|
|
@@ -1715,8 +1887,9 @@ function convertPhpReferenceClass(node, declaration, nominalNames, records, clas
|
|
|
1715
1887
|
parameters: signature.parameters,
|
|
1716
1888
|
returnType: /** @type {import("../semantic/types.js").SemanticFunctionReturnType} */ (signature.returnType)
|
|
1717
1889
|
}, {name: signature.nameLocation}))
|
|
1890
|
+
if (implementedReferences.length > 0) declaration.implements = implementedReferences
|
|
1718
1891
|
const base = {bindings: new Map(), classes, currentClass: declaration, errorNames, errors, functions,
|
|
1719
|
-
recordNames: nominalNames, records}
|
|
1892
|
+
interfaces, recordNames: nominalNames, records}
|
|
1720
1893
|
|
|
1721
1894
|
declaration.constructor.body = convertBlock(/** @type {import("php-parser").Block} */ (constructorNode.body), filename, source,
|
|
1722
1895
|
{...base, bindings: new Map(declaration.constructor.parameters.map((parameter) => [parameter.name, parameter.type])),
|
|
@@ -1966,6 +2139,7 @@ export function parsePhp({capabilities = [], filename, source, program: programC
|
|
|
1966
2139
|
|
|
1967
2140
|
body = namespace.children.filter((node) => node.kind != "usegroup" && !isPhpRequireOnce(node))
|
|
1968
2141
|
}
|
|
2142
|
+
const interfaceNodes = /** @type {import("php-parser").Class[]} */ (body.filter((node) => node.kind == "interface"))
|
|
1969
2143
|
const classNodes = /** @type {import("php-parser").Class[]} */ (body.filter((node) => node.kind == "class"))
|
|
1970
2144
|
const errorNodes = classNodes.filter((node) => node.extends?.kind == "name" &&
|
|
1971
2145
|
["RuntimeException", "\\RuntimeException"].includes(node.extends.name))
|
|
@@ -1997,7 +2171,12 @@ export function parsePhp({capabilities = [], filename, source, program: programC
|
|
|
1997
2171
|
location: nodeLocation(node, filename, source), methods: [],
|
|
1998
2172
|
name: typeof node.name == "string" ? node.name : node.name.name
|
|
1999
2173
|
}))
|
|
2000
|
-
const
|
|
2174
|
+
const interfaceDeclarations = interfaceNodes.map((node, index) => ({
|
|
2175
|
+
id: `interface:${index}`, kind: /** @type {const} */ ("InterfaceDeclaration"),
|
|
2176
|
+
location: nodeLocation(node, filename, source), methods: [],
|
|
2177
|
+
name: typeof node.name == "string" ? node.name : node.name.name
|
|
2178
|
+
}))
|
|
2179
|
+
const recordNames = /** @type {Map<string, import("../semantic/types.js").RecordDeclaration | import("../semantic/types.js").ClassDeclaration | import("../semantic/types.js").EffectResourceDeclaration | import("../semantic/types.js").InterfaceDeclaration>} */ (
|
|
2001
2180
|
new Map(programContext?.records ?? []))
|
|
2002
2181
|
for (const resource of capabilities.flatMap(({resources}) => resources)) recordNames.set(resource.name, resource)
|
|
2003
2182
|
const errorNames = new Map(programContext?.errors ?? [])
|
|
@@ -2009,10 +2188,12 @@ export function parsePhp({capabilities = [], filename, source, program: programC
|
|
|
2009
2188
|
const errors = errorNodes.map((node, index) => convertPhpError(node, errorDeclarations[index], filename, source))
|
|
2010
2189
|
for (const declaration of recordDeclarations) recordNames.set(declaration.name, declaration)
|
|
2011
2190
|
for (const declaration of classDeclarations) recordNames.set(declaration.name, declaration)
|
|
2191
|
+
for (const declaration of interfaceDeclarations) recordNames.set(declaration.name, declaration)
|
|
2012
2192
|
const recordsById = new Map([...recordNames.values()]
|
|
2013
2193
|
.filter((declaration) => declaration.kind == "RecordDeclaration")
|
|
2014
2194
|
.map((declaration) => [/** @type {string} */ (declaration.id), declaration]))
|
|
2015
2195
|
const classesById = new Map(classDeclarations.map((declaration) => [/** @type {string} */ (declaration.id), declaration]))
|
|
2196
|
+
const interfacesById = new Map(interfaceDeclarations.map((declaration) => [/** @type {string} */ (declaration.id), declaration]))
|
|
2016
2197
|
const records = recordNodes.map((node, index) => convertPhpRecord(node, recordDeclarations[index],
|
|
2017
2198
|
/** @type {Map<string, import("../semantic/types.js").RecordDeclaration>} */ (/** @type {unknown} */ (recordNames)), filename, source))
|
|
2018
2199
|
const functionNodes = /** @type {import("php-parser").Function[]} */ (body.filter((node) => node.kind == "function"))
|
|
@@ -2027,12 +2208,14 @@ export function parsePhp({capabilities = [], filename, source, program: programC
|
|
|
2027
2208
|
...(declaration.typeParameters ? {typeParameters: declaration.typeParameters} : {})
|
|
2028
2209
|
}]))
|
|
2029
2210
|
for (const signature of signatures) functionSignatures.set(signature.name, signature)
|
|
2211
|
+
const interfaces = interfaceNodes.map((node, index) => convertPhpInterface(node, interfaceDeclarations[index],
|
|
2212
|
+
recordNames, filename, source))
|
|
2030
2213
|
const classes = referenceClassNodes.map((node, index) => convertPhpReferenceClass(node, classDeclarations[index],
|
|
2031
|
-
recordNames, recordsById, classesById, functionSignatures, errorNames, errorsById, filename, source))
|
|
2214
|
+
recordNames, recordsById, classesById, interfacesById, functionSignatures, errorNames, errorsById, filename, source))
|
|
2032
2215
|
const functions = functionNodes.map((node, index) =>
|
|
2033
|
-
convertFunction(node, signatures[index], functionSignatures, recordNames, recordsById, classesById,
|
|
2034
|
-
filename, source))
|
|
2035
|
-
const executableNodes = body.filter((node) => !["class", "function", "declare", "noop"].includes(node.kind))
|
|
2216
|
+
convertFunction(node, signatures[index], functionSignatures, recordNames, recordsById, classesById, interfacesById,
|
|
2217
|
+
errorNames, errorsById, filename, source))
|
|
2218
|
+
const executableNodes = body.filter((node) => !["class", "function", "interface", "declare", "noop"].includes(node.kind))
|
|
2036
2219
|
const location = moduleLocation(filename, source)
|
|
2037
2220
|
|
|
2038
2221
|
if (programContext && !programContext.isEntry && executableNodes.length > 0) {
|
|
@@ -2049,6 +2232,7 @@ export function parsePhp({capabilities = [], filename, source, program: programC
|
|
|
2049
2232
|
errorNames,
|
|
2050
2233
|
errors: errorsById,
|
|
2051
2234
|
functions: functionSignatures,
|
|
2235
|
+
interfaces: interfacesById,
|
|
2052
2236
|
orderedMapDeclarations: orderedMapDeclarationsForPhp(executableNodes),
|
|
2053
2237
|
recordNames,
|
|
2054
2238
|
records: recordsById,
|
|
@@ -2074,6 +2258,7 @@ export function parsePhp({capabilities = [], filename, source, program: programC
|
|
|
2074
2258
|
location,
|
|
2075
2259
|
...(errors.length > 0 ? {errors} : {}),
|
|
2076
2260
|
...(classes.length > 0 ? {classes} : {}),
|
|
2261
|
+
...(interfaces.length > 0 ? {interfaces} : {}),
|
|
2077
2262
|
...(records.length > 0 ? {records} : {})
|
|
2078
2263
|
}
|
|
2079
2264
|
}
|