semantifold 0.10.0 → 0.12.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 -2
- package/build/src/backends/c.d.ts +2 -1
- package/build/src/backends/c.d.ts.map +1 -1
- package/build/src/backends/c.js +26 -5
- package/build/src/backends/cpp.js +3 -1
- package/build/src/backends/kotlin.d.ts +1 -1
- package/build/src/backends/kotlin.js +2 -2
- package/build/src/backends/ordered-expressions.d.ts +19 -7
- package/build/src/backends/ordered-expressions.d.ts.map +1 -1
- package/build/src/backends/ordered-expressions.js +46 -11
- package/build/src/backends/shared.d.ts.map +1 -1
- package/build/src/backends/shared.js +30 -2
- package/build/src/frontends/c.d.ts +34 -1
- package/build/src/frontends/c.d.ts.map +1 -1
- package/build/src/frontends/c.js +128 -21
- package/build/src/frontends/java.d.ts.map +1 -1
- package/build/src/frontends/java.js +23 -1
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
- package/build/src/frontends/javascript-typescript.js +45 -4
- package/build/src/frontends/php.d.ts.map +1 -1
- package/build/src/frontends/php.js +54 -3
- package/build/src/frontends/ruby.d.ts.map +1 -1
- package/build/src/frontends/ruby.js +55 -5
- package/build/src/language-registry.d.ts.map +1 -1
- package/build/src/language-registry.js +33 -6
- package/build/src/semantic/effects.js +3 -1
- package/build/src/semantic/provenance.d.ts.map +1 -1
- package/build/src/semantic/provenance.js +11 -1
- package/build/src/semantic/types.d.ts +52 -2
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +14 -2
- package/build/src/semantic/validate.js +19 -2
- package/build/src/toolchains.js +3 -3
- package/docs/adr-expression-breadth.md +186 -0
- package/docs/c.md +1 -1
- package/docs/kotlin.md +2 -2
- package/docs/language-support.md +14 -5
- package/docs/parser-qualification.md +1 -1
- package/docs/testing.md +5 -4
- package/package.json +1 -1
- package/src/backends/c.js +23 -4
- package/src/backends/cpp.js +1 -0
- package/src/backends/kotlin.js +1 -1
- package/src/backends/ordered-expressions.js +46 -10
- package/src/backends/shared.js +31 -1
- package/src/frontends/c.js +135 -19
- package/src/frontends/java.js +24 -0
- package/src/frontends/javascript-typescript.js +49 -2
- package/src/frontends/php.js +61 -2
- package/src/frontends/ruby.js +62 -2
- package/src/language-registry.js +32 -5
- package/src/semantic/effects.js +1 -0
- package/src/semantic/provenance.js +8 -0
- package/src/semantic/types.js +14 -1
- package/src/semantic/validate.js +27 -1
- package/src/toolchains.js +2 -2
|
@@ -7,7 +7,7 @@ import {blockReferencesProtectedEntry} from "../semantic/effects.js"
|
|
|
7
7
|
import {locationFromOffsets, moduleLocation} from "../semantic/location.js"
|
|
8
8
|
import {withAdaptedOperation} from "../semantic/operators.js"
|
|
9
9
|
import {withParserRanges} from "../semantic/provenance.js"
|
|
10
|
-
import {hasOnlyUnicodeScalars} from "../semantic/scalars.js"
|
|
10
|
+
import {hasOnlyUnicodeScalars, scalarType} from "../semantic/scalars.js"
|
|
11
11
|
import {isTask034ResourceProbe} from "../semantic/capabilities.js"
|
|
12
12
|
import {hasExactEffectSupport} from "../backends/effects.js"
|
|
13
13
|
import {requireSourceReturnType, requireSourceScalarType} from "./scalars.js"
|
|
@@ -431,7 +431,38 @@ function convertExpression(node, language, filename, source, context = {bindings
|
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
if (node.type == "TemplateLiteral") {
|
|
434
|
-
if (node.expressions.length > 0)
|
|
434
|
+
if (node.expressions.length > 0) {
|
|
435
|
+
/** @type {import("../semantic/types.js").Expression[]} */
|
|
436
|
+
const ordered = []
|
|
437
|
+
|
|
438
|
+
node.quasis.forEach((quasi, index) => {
|
|
439
|
+
if (typeof quasi.value.cooked != "string" || !hasOnlyUnicodeScalars(quasi.value.cooked)) {
|
|
440
|
+
return unsupportedSyntax(language, "invalid Unicode string literal", nodeLocation(quasi, filename, source))
|
|
441
|
+
}
|
|
442
|
+
const quasiLocation = nodeLocation(quasi, filename, source)
|
|
443
|
+
|
|
444
|
+
if (quasi.value.cooked.length > 0) ordered.push(withParserRanges({kind: /** @type {const} */ ("StringLiteral"), location: quasiLocation, value: quasi.value.cooked}, {literal: quasiLocation}))
|
|
445
|
+
if (index < node.expressions.length) {
|
|
446
|
+
ordered.push(convertExpression(/** @type {import("@babel/types").Expression} */ (node.expressions[index]), language, filename, source, context, scalarType("string")))
|
|
447
|
+
}
|
|
448
|
+
})
|
|
449
|
+
if (ordered.length == 0) return unsupportedSyntax(language, "interpolated string", location)
|
|
450
|
+
|
|
451
|
+
let lowered = ordered[0]
|
|
452
|
+
|
|
453
|
+
for (const part of ordered.slice(1)) {
|
|
454
|
+
const semantic = withAdaptedOperation({
|
|
455
|
+
kind: /** @type {const} */ ("BinaryExpression"),
|
|
456
|
+
left: lowered,
|
|
457
|
+
location,
|
|
458
|
+
right: part
|
|
459
|
+
}, /** @type {import("../semantic/operators.js").AdaptedOperation} */ ("StringConcat"))
|
|
460
|
+
|
|
461
|
+
lowered = /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return lowered
|
|
465
|
+
}
|
|
435
466
|
|
|
436
467
|
const quasi = node.quasis[0]
|
|
437
468
|
|
|
@@ -442,6 +473,22 @@ function convertExpression(node, language, filename, source, context = {bindings
|
|
|
442
473
|
return withParserRanges({kind: /** @type {const} */ ("StringLiteral"), location, value: quasi.value.cooked}, {literal: location})
|
|
443
474
|
}
|
|
444
475
|
|
|
476
|
+
if (node.type == "ConditionalExpression") {
|
|
477
|
+
return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (withParserRanges({
|
|
478
|
+
alternate: convertExpression(node.alternate, language, filename, source, context),
|
|
479
|
+
condition: convertExpression(node.test, language, filename, source, context),
|
|
480
|
+
consequent: convertExpression(node.consequent, language, filename, source, context),
|
|
481
|
+
kind: /** @type {const} */ ("ConditionalExpression"),
|
|
482
|
+
location
|
|
483
|
+
}, {
|
|
484
|
+
alternate: nodeLocation(node.alternate, filename, source),
|
|
485
|
+
colon: tokenLocation(node, ":", node.consequent.end ?? node.test.end ?? node.start ?? 0, node.alternate.start ?? node.end ?? source.length, filename, source),
|
|
486
|
+
condition: nodeLocation(node.test, filename, source),
|
|
487
|
+
consequent: nodeLocation(node.consequent, filename, source),
|
|
488
|
+
operator: tokenLocation(node, "?", node.test.end ?? node.start ?? 0, node.consequent.start ?? node.end ?? source.length, filename, source)
|
|
489
|
+
})))
|
|
490
|
+
}
|
|
491
|
+
|
|
445
492
|
if (node.type == "UnaryExpression" && ["!", "-"].includes(node.operator)) {
|
|
446
493
|
const operand = convertExpression(node.argument, language, filename, source, context)
|
|
447
494
|
const semantic = withAdaptedOperation(withParserRanges({
|
package/src/frontends/php.js
CHANGED
|
@@ -222,9 +222,37 @@ function convertExpression(node, filename, source, context, expectedType, preser
|
|
|
222
222
|
|
|
223
223
|
if (node.kind == "encapsed") {
|
|
224
224
|
const literal = /** @type {import("php-parser").Encapsed} */ (node)
|
|
225
|
-
const detail = literal.type == "string" ? "interpolated string" : `${literal.type} string`
|
|
226
225
|
|
|
227
|
-
return unsupportedSyntax("php",
|
|
226
|
+
if (literal.type != "string") return unsupportedSyntax("php", `${literal.type} string`, location)
|
|
227
|
+
if (!Array.isArray(literal.value)) return unsupportedSyntax("php", "interpolated string", location)
|
|
228
|
+
|
|
229
|
+
const parts = literal.value.map((part) => {
|
|
230
|
+
if (part.kind != "encapsedpart") return unsupportedSyntax("php", `interpolated string part ${part.kind}`, nodeLocation(part, filename, source))
|
|
231
|
+
const encapsedPart = /** @type {import("php-parser").EncapsedPart} */ (part)
|
|
232
|
+
|
|
233
|
+
if (!encapsedPart.expression) return unsupportedSyntax("php", "interpolated string part without expression", nodeLocation(encapsedPart, filename, source))
|
|
234
|
+
|
|
235
|
+
return convertExpression(encapsedPart.expression, filename, source, context, scalarType("string"))
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
if (parts.length == 0) {
|
|
239
|
+
return withParserRanges({kind: /** @type {const} */ ("StringLiteral"), location, value: ""}, {literal: location})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let lowered = parts[0]
|
|
243
|
+
|
|
244
|
+
for (const part of parts.slice(1)) {
|
|
245
|
+
const semantic = withAdaptedOperation({
|
|
246
|
+
kind: /** @type {const} */ ("BinaryExpression"),
|
|
247
|
+
left: lowered,
|
|
248
|
+
location,
|
|
249
|
+
right: part
|
|
250
|
+
}, /** @type {import("../semantic/operators.js").AdaptedOperation} */ ("StringConcat"))
|
|
251
|
+
|
|
252
|
+
lowered = /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return withParserRanges(lowered, {literal: location})
|
|
228
256
|
}
|
|
229
257
|
|
|
230
258
|
if (node.kind == "nowdoc") return unsupportedSyntax("php", "nowdoc string", location)
|
|
@@ -414,6 +442,37 @@ function convertExpression(node, filename, source, context, expectedType, preser
|
|
|
414
442
|
return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
|
|
415
443
|
}
|
|
416
444
|
|
|
445
|
+
if (node.kind == "retif") {
|
|
446
|
+
const ternary = /** @type {import("php-parser").RetIf} */ (node)
|
|
447
|
+
|
|
448
|
+
if (!ternary.test || !ternary.trueExpr || !ternary.falseExpr) return unsupportedSyntax("php", "short ternary", location)
|
|
449
|
+
if (ternary.test.kind == "variable") {
|
|
450
|
+
const tested = /** @type {import("php-parser").Variable} */ (ternary.test)
|
|
451
|
+
|
|
452
|
+
if (typeof tested.name == "string" && context.bindings.get(tested.name)?.kind == "OptionalType") {
|
|
453
|
+
return unsupportedSyntax("php", "optional truthiness condition", nodeLocation(tested, filename, source))
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (withParserRanges({
|
|
458
|
+
alternate: convertExpression(ternary.falseExpr, filename, source, context),
|
|
459
|
+
condition: convertExpression(ternary.test, filename, source, context),
|
|
460
|
+
consequent: convertExpression(ternary.trueExpr, filename, source, context),
|
|
461
|
+
kind: /** @type {const} */ ("ConditionalExpression"),
|
|
462
|
+
location
|
|
463
|
+
}, {
|
|
464
|
+
alternate: tokenLocation(":", ternary.trueExpr.loc?.end.offset ?? 0, ternary.falseExpr.loc?.start.offset ?? source.length, filename, source),
|
|
465
|
+
colon: tokenLocation(":", ternary.trueExpr.loc?.end.offset ?? 0, ternary.falseExpr.loc?.start.offset ?? source.length, filename, source),
|
|
466
|
+
condition: ternary.test.loc ? nodeLocation(ternary.test, filename, source) : location,
|
|
467
|
+
consequent: ternary.trueExpr.loc ? nodeLocation(ternary.trueExpr, filename, source) : location,
|
|
468
|
+
operator: tokenLocation("?", ternary.test.loc?.end.offset ?? 0, ternary.trueExpr.loc?.start.offset ?? source.length, filename, source)
|
|
469
|
+
})))
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (node.kind == "nullsafepropertylookup" || node.kind == "nullsafemethodcall") {
|
|
473
|
+
return unsupportedSyntax("php", "safe navigation", location)
|
|
474
|
+
}
|
|
475
|
+
|
|
417
476
|
if (node.kind == "bin") {
|
|
418
477
|
const binary = /** @type {import("php-parser").Bin} */ (node)
|
|
419
478
|
if (binary.type == "!==" && binary.left.kind == "variable" && binary.right.kind == "nullkeyword") {
|
package/src/frontends/ruby.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
ConstantReadNode,
|
|
15
15
|
DefNode,
|
|
16
16
|
ElseNode,
|
|
17
|
+
EmbeddedStatementsNode,
|
|
17
18
|
FalseNode,
|
|
18
19
|
HashNode,
|
|
19
20
|
IfNode,
|
|
@@ -49,7 +50,7 @@ import {blockReferencesProtectedEntry} from "../semantic/effects.js"
|
|
|
49
50
|
import {locationFromOffsets, moduleLocation, utf8ByteOffsetToUtf16Offset} from "../semantic/location.js"
|
|
50
51
|
import {withAdaptedOperation} from "../semantic/operators.js"
|
|
51
52
|
import {withParserRanges} from "../semantic/provenance.js"
|
|
52
|
-
import {hasOnlyUnicodeScalars} from "../semantic/scalars.js"
|
|
53
|
+
import {hasOnlyUnicodeScalars, scalarType} from "../semantic/scalars.js"
|
|
53
54
|
import {isTask034ResourceProbe} from "../semantic/capabilities.js"
|
|
54
55
|
import {hasExactEffectSupport} from "../backends/effects.js"
|
|
55
56
|
import {requireSourceReturnType} from "./scalars.js"
|
|
@@ -207,7 +208,65 @@ function convertExpression(node, filename, source, context, expectedType, preser
|
|
|
207
208
|
return withParserRanges({kind: /** @type {const} */ ("StringLiteral"), location, value: decoded.value}, {literal: location})
|
|
208
209
|
}
|
|
209
210
|
|
|
210
|
-
if (node instanceof InterpolatedStringNode)
|
|
211
|
+
if (node instanceof InterpolatedStringNode) {
|
|
212
|
+
if (node.openingLoc && slicePrismSource(source, node.openingLoc.startOffset, node.openingLoc.startOffset + node.openingLoc.length).startsWith("<<")) {
|
|
213
|
+
return unsupportedSyntax("ruby", "heredoc string", location)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const parts = node.parts.map((part) => {
|
|
217
|
+
if (part instanceof StringNode) return convertExpression(part, filename, source, context, scalarType("string"))
|
|
218
|
+
if (part instanceof EmbeddedStatementsNode) {
|
|
219
|
+
const statements = part.statements
|
|
220
|
+
|
|
221
|
+
if (!statements || statements.body.length != 1) return unsupportedSyntax("ruby", "interpolated statement", nodeLocation(part, filename, source))
|
|
222
|
+
|
|
223
|
+
return convertExpression(statements.body[0], filename, source, context, scalarType("string"))
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return unsupportedSyntax("ruby", part.constructor.name, nodeLocation(part, filename, source))
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
if (parts.length == 0) return withParserRanges({kind: /** @type {const} */ ("StringLiteral"), location, value: ""}, {literal: location})
|
|
230
|
+
|
|
231
|
+
let lowered = parts[0]
|
|
232
|
+
|
|
233
|
+
for (const part of parts.slice(1)) {
|
|
234
|
+
const semantic = withAdaptedOperation({
|
|
235
|
+
kind: /** @type {const} */ ("BinaryExpression"),
|
|
236
|
+
left: lowered,
|
|
237
|
+
location,
|
|
238
|
+
right: part
|
|
239
|
+
}, /** @type {import("../semantic/operators.js").AdaptedOperation} */ ("StringConcat"))
|
|
240
|
+
|
|
241
|
+
lowered = /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return withParserRanges(lowered, {literal: location})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (node instanceof IfNode && !node.ifKeywordLoc) {
|
|
248
|
+
const elseClause = node.subsequent
|
|
249
|
+
const consequentStatements = node.statements
|
|
250
|
+
const consequentArm = consequentStatements && consequentStatements.body.length == 1 ? consequentStatements.body[0] : undefined
|
|
251
|
+
|
|
252
|
+
if (!(elseClause instanceof ElseNode) || !elseClause.statements || elseClause.statements.body.length != 1 || !consequentArm || !node.thenKeywordLoc) {
|
|
253
|
+
return unsupportedSyntax("ruby", "ternary without alternate", location)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (withParserRanges({
|
|
257
|
+
alternate: convertExpression(elseClause.statements.body[0], filename, source, context),
|
|
258
|
+
condition: convertExpression(node.predicate, filename, source, context),
|
|
259
|
+
consequent: convertExpression(consequentArm, filename, source, context),
|
|
260
|
+
kind: /** @type {const} */ ("ConditionalExpression"),
|
|
261
|
+
location
|
|
262
|
+
}, {
|
|
263
|
+
alternate: nodeLocation(elseClause.statements.body[0], filename, source),
|
|
264
|
+
colon: prismLocation(elseClause.elseKeywordLoc ?? elseClause.location, filename, source),
|
|
265
|
+
condition: nodeLocation(node.predicate, filename, source),
|
|
266
|
+
consequent: nodeLocation(consequentArm, filename, source),
|
|
267
|
+
operator: prismLocation(node.thenKeywordLoc, filename, source)
|
|
268
|
+
})))
|
|
269
|
+
}
|
|
211
270
|
|
|
212
271
|
if (node instanceof OrNode && node.left instanceof LocalVariableReadNode &&
|
|
213
272
|
context.bindings.get(node.left.name)?.kind == "OptionalType") {
|
|
@@ -863,6 +922,7 @@ function convertStatement(node, comments, context, filename, source) {
|
|
|
863
922
|
if ((context.loopDepth ?? 0) > 0) return unsupportedSyntax("ruby", "nonlocal return from each block", nodeLocation(node, filename, source))
|
|
864
923
|
return convertReturn(node, filename, source, context)
|
|
865
924
|
}
|
|
925
|
+
if (node instanceof IfNode && !node.ifKeywordLoc) return unsupportedSyntax("ruby", "ternary statement", nodeLocation(node, filename, source))
|
|
866
926
|
if (node instanceof IfNode || node instanceof UnlessNode) return convertIf(node, comments, context, filename, source)
|
|
867
927
|
if (node instanceof WhileNode) return convertWhile(node, comments, context, filename, source)
|
|
868
928
|
if (node instanceof BeginNode) return convertRubyTry(node, comments, context, filename, source)
|
package/src/language-registry.js
CHANGED
|
@@ -171,6 +171,8 @@ export function createLanguageRegistry(candidateRecords) {
|
|
|
171
171
|
const featuresCandidate = candidate.features ?? {
|
|
172
172
|
closedRecords: false,
|
|
173
173
|
conditionControlledLoops: false,
|
|
174
|
+
conditionalExpressions: false,
|
|
175
|
+
stringInterpolation: false,
|
|
174
176
|
effectfulCapabilitiesAndResources: false,
|
|
175
177
|
generalFunctionsAndCalls: false,
|
|
176
178
|
immutableCollections: false,
|
|
@@ -183,9 +185,10 @@ export function createLanguageRegistry(candidateRecords) {
|
|
|
183
185
|
}
|
|
184
186
|
|
|
185
187
|
if (!isPlainObject(featuresCandidate) ||
|
|
186
|
-
Object.keys(featuresCandidate).sort().join(",") != "closedRecords,conditionControlledLoops,effectfulCapabilitiesAndResources,generalFunctionsAndCalls,immutableCollections,optionalValues,orderedListIteration,orderedMapIteration,referenceClasses,typeParametersAndGenerics,typedErrors" ||
|
|
188
|
+
Object.keys(featuresCandidate).sort().join(",") != "closedRecords,conditionControlledLoops,conditionalExpressions,effectfulCapabilitiesAndResources,generalFunctionsAndCalls,immutableCollections,optionalValues,orderedListIteration,orderedMapIteration,referenceClasses,stringInterpolation,typeParametersAndGenerics,typedErrors" ||
|
|
187
189
|
typeof featuresCandidate.closedRecords != "boolean" ||
|
|
188
190
|
typeof featuresCandidate.conditionControlledLoops != "boolean" ||
|
|
191
|
+
typeof featuresCandidate.conditionalExpressions != "boolean" ||
|
|
189
192
|
typeof featuresCandidate.effectfulCapabilitiesAndResources != "boolean" ||
|
|
190
193
|
typeof featuresCandidate.generalFunctionsAndCalls != "boolean" ||
|
|
191
194
|
typeof featuresCandidate.immutableCollections != "boolean" ||
|
|
@@ -193,6 +196,7 @@ export function createLanguageRegistry(candidateRecords) {
|
|
|
193
196
|
typeof featuresCandidate.orderedListIteration != "boolean" ||
|
|
194
197
|
typeof featuresCandidate.orderedMapIteration != "boolean" ||
|
|
195
198
|
typeof featuresCandidate.referenceClasses != "boolean" ||
|
|
199
|
+
typeof featuresCandidate.stringInterpolation != "boolean" ||
|
|
196
200
|
typeof featuresCandidate.typedErrors != "boolean" ||
|
|
197
201
|
typeof featuresCandidate.typeParametersAndGenerics != "boolean") {
|
|
198
202
|
invalidRegistry(`Registry record '${id}' has an invalid feature declaration.`, id)
|
|
@@ -200,6 +204,7 @@ export function createLanguageRegistry(candidateRecords) {
|
|
|
200
204
|
const features = deepFreeze(/** @type {import("./semantic/types.js").LanguageFeatureCapabilities} */ ({
|
|
201
205
|
closedRecords: featuresCandidate.closedRecords,
|
|
202
206
|
conditionControlledLoops: featuresCandidate.conditionControlledLoops,
|
|
207
|
+
conditionalExpressions: featuresCandidate.conditionalExpressions,
|
|
203
208
|
effectfulCapabilitiesAndResources: featuresCandidate.effectfulCapabilitiesAndResources,
|
|
204
209
|
generalFunctionsAndCalls: featuresCandidate.generalFunctionsAndCalls,
|
|
205
210
|
immutableCollections: featuresCandidate.immutableCollections,
|
|
@@ -207,6 +212,7 @@ export function createLanguageRegistry(candidateRecords) {
|
|
|
207
212
|
orderedListIteration: featuresCandidate.orderedListIteration,
|
|
208
213
|
orderedMapIteration: featuresCandidate.orderedMapIteration,
|
|
209
214
|
referenceClasses: featuresCandidate.referenceClasses,
|
|
215
|
+
stringInterpolation: featuresCandidate.stringInterpolation,
|
|
210
216
|
typedErrors: featuresCandidate.typedErrors,
|
|
211
217
|
typeParametersAndGenerics: featuresCandidate.typeParametersAndGenerics
|
|
212
218
|
}))
|
|
@@ -565,6 +571,21 @@ const records = [
|
|
|
565
571
|
artifactMultiplicity: "multiple",
|
|
566
572
|
check: {factory: createCCheckPlan, stages: ["compile", "link"], toolchains: ["clang"]},
|
|
567
573
|
defaultFilename: "program.c",
|
|
574
|
+
features: {
|
|
575
|
+
closedRecords: false,
|
|
576
|
+
conditionControlledLoops: false,
|
|
577
|
+
conditionalExpressions: false,
|
|
578
|
+
stringInterpolation: false,
|
|
579
|
+
effectfulCapabilitiesAndResources: false,
|
|
580
|
+
generalFunctionsAndCalls: true,
|
|
581
|
+
immutableCollections: false,
|
|
582
|
+
optionalValues: false,
|
|
583
|
+
orderedListIteration: false,
|
|
584
|
+
orderedMapIteration: false,
|
|
585
|
+
referenceClasses: false,
|
|
586
|
+
typedErrors: false,
|
|
587
|
+
typeParametersAndGenerics: false
|
|
588
|
+
},
|
|
568
589
|
frontend: parseC,
|
|
569
590
|
id: "c",
|
|
570
591
|
mediaType: "text/x-c",
|
|
@@ -592,6 +613,8 @@ const records = [
|
|
|
592
613
|
features: {
|
|
593
614
|
closedRecords: false,
|
|
594
615
|
conditionControlledLoops: false,
|
|
616
|
+
conditionalExpressions: false,
|
|
617
|
+
stringInterpolation: false,
|
|
595
618
|
effectfulCapabilitiesAndResources: false,
|
|
596
619
|
generalFunctionsAndCalls: true,
|
|
597
620
|
immutableCollections: false,
|
|
@@ -615,6 +638,8 @@ const records = [
|
|
|
615
638
|
features: {
|
|
616
639
|
closedRecords: false,
|
|
617
640
|
conditionControlledLoops: false,
|
|
641
|
+
conditionalExpressions: false,
|
|
642
|
+
stringInterpolation: false,
|
|
618
643
|
effectfulCapabilitiesAndResources: false,
|
|
619
644
|
generalFunctionsAndCalls: true,
|
|
620
645
|
immutableCollections: false,
|
|
@@ -636,7 +661,7 @@ const records = [
|
|
|
636
661
|
artifactMultiplicity: "multiple",
|
|
637
662
|
binaryBackend: generateBrowserWasm,
|
|
638
663
|
defaultFilename: "program.wasm",
|
|
639
|
-
features: {closedRecords: false, conditionControlledLoops: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
664
|
+
features: {closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
640
665
|
id: "wasm",
|
|
641
666
|
mapping: {binaryRanges: true, richText: true, sourceMapV3: true},
|
|
642
667
|
mediaType: "application/wasm",
|
|
@@ -646,7 +671,7 @@ const records = [
|
|
|
646
671
|
acceptance: {stages: ["generate", "compile", "validate", "instantiate", "execute"], toolchains: ["android"]},
|
|
647
672
|
applicationBackend: generateAndroidApplication,
|
|
648
673
|
artifactMultiplicity: "multiple",
|
|
649
|
-
features: {closedRecords: false, conditionControlledLoops: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
674
|
+
features: {closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
650
675
|
id: "android",
|
|
651
676
|
mapping: {binaryRanges: true, richText: true, sourceMapV3: true},
|
|
652
677
|
roundTrip: false
|
|
@@ -655,7 +680,7 @@ const records = [
|
|
|
655
680
|
acceptance: {stages: ["generate"], toolchains: []},
|
|
656
681
|
applicationBackend: generateIosApplication,
|
|
657
682
|
artifactMultiplicity: "multiple",
|
|
658
|
-
features: {closedRecords: false, conditionControlledLoops: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
683
|
+
features: {closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
659
684
|
id: "ios",
|
|
660
685
|
mapping: {binaryRanges: true, richText: true, sourceMapV3: true},
|
|
661
686
|
roundTrip: false
|
|
@@ -664,7 +689,7 @@ const records = [
|
|
|
664
689
|
acceptance: {stages: ["generate", "restore", "compile", "validate", "instantiate", "execute"], toolchains: ["flutter", "android"]},
|
|
665
690
|
applicationBackend: generateFlutterApplication,
|
|
666
691
|
artifactMultiplicity: "multiple",
|
|
667
|
-
features: {closedRecords: false, conditionControlledLoops: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
692
|
+
features: {closedRecords: false, conditionControlledLoops: false, conditionalExpressions: false, stringInterpolation: false, effectfulCapabilitiesAndResources: false, generalFunctionsAndCalls: false, immutableCollections: false, optionalValues: false, orderedListIteration: false, orderedMapIteration: false, referenceClasses: false, typedErrors: false, typeParametersAndGenerics: false},
|
|
668
693
|
id: "flutter",
|
|
669
694
|
mapping: {binaryRanges: true, richText: true, sourceMapV3: true},
|
|
670
695
|
roundTrip: false
|
|
@@ -689,6 +714,8 @@ function language(values) {
|
|
|
689
714
|
features: {
|
|
690
715
|
closedRecords: ["php", "ruby", "javascript", "typescript", "java"].includes(String(values.id)),
|
|
691
716
|
conditionControlledLoops: ["php", "ruby", "javascript", "typescript", "java"].includes(String(values.id)),
|
|
717
|
+
conditionalExpressions: ["php", "ruby", "javascript", "typescript", "java"].includes(String(values.id)),
|
|
718
|
+
stringInterpolation: ["php", "ruby", "javascript", "typescript"].includes(String(values.id)),
|
|
692
719
|
effectfulCapabilitiesAndResources: ["php", "ruby", "javascript", "typescript", "java"].includes(String(values.id)),
|
|
693
720
|
generalFunctionsAndCalls: ["php", "ruby", "javascript", "typescript", "java"].includes(String(values.id)),
|
|
694
721
|
immutableCollections: ["php", "ruby", "javascript", "typescript", "java"].includes(String(values.id)),
|
package/src/semantic/effects.js
CHANGED
|
@@ -722,6 +722,7 @@ function expressionChildren(expression) {
|
|
|
722
722
|
if (expression.kind == "MethodCallExpression") return [expression.receiver, ...expression.arguments]
|
|
723
723
|
if (expression.kind == "OwnedBorrowExpression" || expression.kind == "OwnedMoveExpression") return [expression.expression]
|
|
724
724
|
if (expression.kind == "BinaryExpression") return [expression.left, expression.right]
|
|
725
|
+
if (expression.kind == "ConditionalExpression") return [expression.condition, expression.consequent, expression.alternate]
|
|
725
726
|
if (expression.kind == "UnaryExpression") return [expression.operand]
|
|
726
727
|
if (expression.kind == "OptionalSome") return [expression.value]
|
|
727
728
|
if (expression.kind == "OptionalIsPresent" || expression.kind == "OptionalUnwrap") return [expression.operand]
|
|
@@ -592,6 +592,10 @@ export function semanticEntries(module) {
|
|
|
592
592
|
} else if (node.kind == "BinaryExpression") {
|
|
593
593
|
visit(node.left, `${path}/left`, location)
|
|
594
594
|
visit(node.right, `${path}/right`, location)
|
|
595
|
+
} else if (node.kind == "ConditionalExpression") {
|
|
596
|
+
visit(node.condition, `${path}/condition`, location)
|
|
597
|
+
visit(node.consequent, `${path}/consequent`, location)
|
|
598
|
+
visit(node.alternate, `${path}/alternate`, location)
|
|
595
599
|
} else if (node.kind == "CallExpression" || node.kind == "EffectCallExpression") {
|
|
596
600
|
node.arguments.forEach((child, index) => visit(child, `${path}/arguments/${index}`, location))
|
|
597
601
|
} else if (node.kind == "OwnedMoveExpression" || node.kind == "OwnedBorrowExpression") {
|
|
@@ -951,6 +955,10 @@ function resolveSymbols(module, records) {
|
|
|
951
955
|
} else if (expression.kind == "BinaryExpression") {
|
|
952
956
|
visitExpression(expression.left, scope, `${path}/left`)
|
|
953
957
|
visitExpression(expression.right, scope, `${path}/right`)
|
|
958
|
+
} else if (expression.kind == "ConditionalExpression") {
|
|
959
|
+
visitExpression(expression.condition, scope, `${path}/condition`)
|
|
960
|
+
visitExpression(expression.consequent, scope, `${path}/consequent`)
|
|
961
|
+
visitExpression(expression.alternate, scope, `${path}/alternate`)
|
|
954
962
|
} else if (expression.kind == "ListLiteral") {
|
|
955
963
|
for (const [index, element] of expression.elements.entries()) visitExpression(element, scope, `${path}/elements/${index}`)
|
|
956
964
|
} else if (expression.kind == "MapLiteral" || expression.kind == "OrderedMapLiteral") {
|
package/src/semantic/types.js
CHANGED
|
@@ -180,6 +180,8 @@
|
|
|
180
180
|
* @property {boolean} orderedListIteration - Task 008 ordered immutable-list iteration and nearest-loop control.
|
|
181
181
|
* @property {boolean} orderedMapIteration - Task 014 insertion-ordered immutable-map pair iteration.
|
|
182
182
|
* @property {boolean} conditionControlledLoops - Task 032 strict-Boolean pre-condition loops and resolved control.
|
|
183
|
+
* @property {boolean} conditionalExpressions - Task 048 strict-Boolean conditional expressions with joined arm types.
|
|
184
|
+
* @property {boolean} stringInterpolation - Task 048 parser-proved string interpolation lowered to checked string concatenation.
|
|
183
185
|
* @property {boolean} closedRecords - Task 009 nominal closed immutable records, construction, and member reads.
|
|
184
186
|
* @property {boolean} typedErrors - Task 011 nominal unchecked errors, raises, and exact typed catches.
|
|
185
187
|
* @property {boolean} typeParametersAndGenerics - Task 012 invariant unbounded type parameters and closed generic applications.
|
|
@@ -983,6 +985,17 @@
|
|
|
983
985
|
* @property {SemanticNodeSourceProvenance} [sourceProvenance] - Node-associated provenance.
|
|
984
986
|
*/
|
|
985
987
|
|
|
988
|
+
/**
|
|
989
|
+
* @typedef ConditionalExpression
|
|
990
|
+
* @property {"ConditionalExpression"} kind - Node discriminator.
|
|
991
|
+
* @property {Expression} condition - Strict boolean test evaluated exactly once.
|
|
992
|
+
* @property {Expression} consequent - Arm evaluated when the test is true.
|
|
993
|
+
* @property {Expression} alternate - Arm evaluated when the test is false.
|
|
994
|
+
* @property {SemanticTypeName} type - Joined arm result type assigned by validation.
|
|
995
|
+
* @property {SourceLocation} location - Source location.
|
|
996
|
+
* @property {SemanticNodeSourceProvenance} [sourceProvenance] - Node-associated provenance.
|
|
997
|
+
*/
|
|
998
|
+
|
|
986
999
|
/**
|
|
987
1000
|
* @typedef CallExpression
|
|
988
1001
|
* @property {"CallExpression"} kind - Node discriminator.
|
|
@@ -1082,7 +1095,7 @@
|
|
|
1082
1095
|
* @property {SemanticNodeSourceProvenance} [sourceProvenance] - Parser-owned message-member range.
|
|
1083
1096
|
*/
|
|
1084
1097
|
|
|
1085
|
-
/** @typedef {IdentifierExpression | ReceiverExpression | IntegerLiteral | BooleanLiteral | StringLiteral | OptionalNone | OptionalSome | OptionalIsPresent | OptionalUnwrap | ListLiteral | MapLiteral | OrderedMapLiteral | ListIndexExpression | MapLookupExpression | CollectionSizeExpression | UnaryExpression | BinaryExpression | CallExpression | EffectCallExpression | OwnedMoveExpression | OwnedBorrowExpression | MethodCallExpression | ReferenceConstruction | RecordConstruction | MemberRead | PrivateFieldRead | ErrorMessageRead} Expression */
|
|
1098
|
+
/** @typedef {IdentifierExpression | ReceiverExpression | IntegerLiteral | BooleanLiteral | StringLiteral | OptionalNone | OptionalSome | OptionalIsPresent | OptionalUnwrap | ListLiteral | MapLiteral | OrderedMapLiteral | ListIndexExpression | MapLookupExpression | CollectionSizeExpression | UnaryExpression | BinaryExpression | ConditionalExpression | CallExpression | EffectCallExpression | OwnedMoveExpression | OwnedBorrowExpression | MethodCallExpression | ReferenceConstruction | RecordConstruction | MemberRead | PrivateFieldRead | ErrorMessageRead} Expression */
|
|
1086
1099
|
|
|
1087
1100
|
/**
|
|
1088
1101
|
* @typedef LocalDeclaration
|
package/src/semantic/validate.js
CHANGED
|
@@ -8,7 +8,7 @@ import {parserRangeFor} from "./provenance.js"
|
|
|
8
8
|
import {validateEffectGraph} from "./effects.js"
|
|
9
9
|
import {validateResourceLifetimes} from "./lifetime.js"
|
|
10
10
|
|
|
11
|
-
const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "java", "dart", "zig"])
|
|
11
|
+
const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "java", "c", "dart", "zig"])
|
|
12
12
|
|
|
13
13
|
/** @typedef {Map<string, import("./types.js").RecordDeclaration>} RecordRegistry */
|
|
14
14
|
/** @typedef {Map<string, import("./types.js").ClassDeclaration>} ClassRegistry */
|
|
@@ -2284,6 +2284,32 @@ function inferExpressionType(expression, scope, functions, records, fail, normal
|
|
|
2284
2284
|
}
|
|
2285
2285
|
return scalarType(signature.result)
|
|
2286
2286
|
}
|
|
2287
|
+
if (expression.kind == "ConditionalExpression") {
|
|
2288
|
+
const conditionType = inferValueExpressionType(expression.condition, scope, functions, records, fail,
|
|
2289
|
+
normalizeOperations, "a condition")
|
|
2290
|
+
|
|
2291
|
+
if (!isScalarType(conditionType, "boolean")) {
|
|
2292
|
+
fail("NON_BOOLEAN_CONDITION", `Conditional expression condition type ${typeDescription(conditionType)}; expected boolean.`,
|
|
2293
|
+
expression.condition.location)
|
|
2294
|
+
}
|
|
2295
|
+
const consequentScope = createScope(scope, [])
|
|
2296
|
+
const alternateScope = createScope(scope, [])
|
|
2297
|
+
|
|
2298
|
+
applyPresenceNarrowing(expression.condition, scope, consequentScope, true)
|
|
2299
|
+
applyPresenceNarrowing(expression.condition, scope, alternateScope, false)
|
|
2300
|
+
const consequentType = inferValueExpressionType(expression.consequent, consequentScope, functions, records, fail,
|
|
2301
|
+
normalizeOperations, "a conditional expression consequent")
|
|
2302
|
+
const alternateType = inferValueExpressionType(expression.alternate, alternateScope, functions, records, fail,
|
|
2303
|
+
normalizeOperations, "a conditional expression alternate")
|
|
2304
|
+
|
|
2305
|
+
if (!sameType(consequentType, alternateType)) {
|
|
2306
|
+
fail("TYPE_MISMATCH", `Conditional expression arms ${typeDescription(consequentType)} and ${typeDescription(alternateType)}; expected one joined type.`,
|
|
2307
|
+
expression.alternate.location)
|
|
2308
|
+
}
|
|
2309
|
+
Reflect.set(expression, "type", consequentType)
|
|
2310
|
+
|
|
2311
|
+
return consequentType
|
|
2312
|
+
}
|
|
2287
2313
|
if (expression.kind == "BinaryExpression") {
|
|
2288
2314
|
const left = inferValueExpressionType(expression.left, scope, functions, records, fail, normalizeOperations, "a binary operand")
|
|
2289
2315
|
const right = inferValueExpressionType(expression.right, scope, functions, records, fail, normalizeOperations, "a binary operand")
|
package/src/toolchains.js
CHANGED
|
@@ -21,9 +21,9 @@ export const canonicalToolchains = deepFreeze({
|
|
|
21
21
|
javac: definition("javac", "SEMANTIFOLD_JAVAC", ["-version"], /^javac (?:1[7-9]|2[0-9])\./u),
|
|
22
22
|
java: definition("java", "SEMANTIFOLD_JAVA", ["-version"], /^(?:openjdk|java) version "(?:1[7-9]|2[0-9])\./u),
|
|
23
23
|
java25: definition("java", "SEMANTIFOLD_JAVA", ["-version"],
|
|
24
|
-
/^
|
|
24
|
+
/^openjdk version "25\.0\.4\.1" 2026-08-18\nOpenJDK Runtime Environment \(build 25\.0\.4\.1\+1-1-(?:24\.04\.4|26\.04\.4)-Ubuntu\)\nOpenJDK 64-Bit Server VM \(build 25\.0\.4\.1\+1-1-(?:24\.04\.4|26\.04\.4)-Ubuntu, mixed mode, sharing\)$/u),
|
|
25
25
|
kotlinc: definition("kotlinc", "SEMANTIFOLD_KOTLINC", ["-version"],
|
|
26
|
-
/^info: kotlinc-jvm 2\.4\.20 \(JRE
|
|
26
|
+
/^info: kotlinc-jvm 2\.4\.20 \(JRE 25\.0\.4\.1\+1-1-(?:24\.04\.4|26\.04\.4)-Ubuntu\)$/u),
|
|
27
27
|
python: definition("python3", "SEMANTIFOLD_PYTHON", ["--version"], /^Python 3\./u),
|
|
28
28
|
dotnet: definition("dotnet", "SEMANTIFOLD_DOTNET", ["--version"], /^10\./u),
|
|
29
29
|
go: definition("go", "SEMANTIFOLD_GO", ["version"], /^go version go1\.26\.\d+ linux\/amd64$/u),
|