@kernlang/python 4.0.1-canary.224.1.1a92ac0a → 4.0.1-canary.225.1.f5c8d5fe
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codegen-body-python.js +82 -6
- package/dist/codegen-body-python.js.map +1 -1
- package/dist/core/expr/helpers.d.ts +32 -0
- package/dist/core/expr/helpers.js +75 -1
- package/dist/core/expr/helpers.js.map +1 -1
- package/dist/core/expr/index.d.ts +1 -1
- package/dist/core/expr/index.js +3 -1
- package/dist/core/expr/index.js.map +1 -1
- package/package.json +5 -2
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
* threads a `indent` string. The propagation hoist embeds its own 4-space
|
|
41
41
|
* relative indent on the `return __k_tN` line; the wrapper prepends the
|
|
42
42
|
* surrounding indent so the post-emit result nests correctly. */
|
|
43
|
-
import { applyTemplate, classifyRegexLiteralIndexReadFailClose, classifyRegexLiteralMemberReadFailClose, emitStringKeyArray, expandRegexIFold, instanceofRhsPythonType, instanceofRhsRejectReasonForName, isHostNamespaceRoot, isPostfixMutationOperator, isSupportedAssignOperator, isZeroWidthCapableRegex, KERN_STDLIB_MODULES, lookupStdlibCall, lookupStdlibProperty, lowerRegexAnchorsPython, lowerRegexNamedGroupsPython, needsArgParens, needsBinaryParens, normalizeRegexClasses, parseExpression, parseKeys, REGEX_EXEC_FAILCLOSE, REGEX_HOST_REGEXP_FAILCLOSE, REGEX_MATCHALL_NO_G_FAILCLOSE, REGEX_NONLITERAL_FAILCLOSE, REGEX_REPLACE_NONLITERAL_REPL_FAILCLOSE, REGEX_REPLACEALL_NO_G_FAILCLOSE, REGEX_SPLIT_LIMIT_FAILCLOSE, REGEX_SPLIT_ZEROWIDTH_FAILCLOSE, REGEX_TEST_G_FAILCLOSE, regexAstralFailMessage, regexCaptureMeta, regexIFoldFailMessage, regexLiteralReceiverIR, regexMethodRegexArgIdent, scanRegexAstral, suggestStdlibMethod, translateReplStringToPython, unmappedHostNamespaceMessage, unwrapTransparentReceiverIR, validateRegexNamedGroupsPortable, } from '@kernlang/core';
|
|
43
|
+
import { applyTemplate, assertNoDecimalOperator, classifyRegexLiteralIndexReadFailClose, classifyRegexLiteralMemberReadFailClose, decimalBareConstructionFailMessage, emitStringKeyArray, expandRegexIFold, instanceofRhsPythonType, instanceofRhsRejectReasonForName, isHostNamespaceRoot, isPostfixMutationOperator, isSupportedAssignOperator, isZeroWidthCapableRegex, KERN_DECIMAL_OPS_HELPER_PY, KERN_STDLIB_MODULES, lookupStdlibCall, lookupStdlibProperty, lowerRegexAnchorsPython, lowerRegexNamedGroupsPython, needsArgParens, needsBinaryParens, normalizeRegexClasses, parseExpression, parseKeys, REGEX_EXEC_FAILCLOSE, REGEX_HOST_REGEXP_FAILCLOSE, REGEX_MATCHALL_NO_G_FAILCLOSE, REGEX_NONLITERAL_FAILCLOSE, REGEX_REPLACE_NONLITERAL_REPL_FAILCLOSE, REGEX_REPLACEALL_NO_G_FAILCLOSE, REGEX_SPLIT_LIMIT_FAILCLOSE, REGEX_SPLIT_ZEROWIDTH_FAILCLOSE, REGEX_TEST_G_FAILCLOSE, regexAstralFailMessage, regexCaptureMeta, regexIFoldFailMessage, regexLiteralReceiverIR, regexMethodRegexArgIdent, scanRegexAstral, suggestStdlibMethod, translateReplStringToPython, unmappedHostNamespaceMessage, unwrapTransparentReceiverIR, validateDecimalConstructionArg, validateDecimalDivModArgs, validateDecimalOperands, validateDecimalPowArgs, validateRegexNamedGroupsPortable, } from '@kernlang/core';
|
|
44
44
|
// Slice 0.9 — the TypeScript-AST closure helpers + classifier live on the Node
|
|
45
45
|
// subpath (the barrel is browser-safe). Python codegen is Node-side and parses
|
|
46
46
|
// block-bodied arrows, so it injects `typescriptClosureClassifier`.
|
|
@@ -1862,8 +1862,10 @@ function emitPyExprCtx(node, ctx) {
|
|
|
1862
1862
|
const lowered = lowerChain(node, ctx);
|
|
1863
1863
|
return wrapGuardIfAny(lowered, ctx);
|
|
1864
1864
|
}
|
|
1865
|
-
case 'await':
|
|
1866
|
-
|
|
1865
|
+
case 'await': {
|
|
1866
|
+
const arg = emitPyExprCtx(node.argument, ctx);
|
|
1867
|
+
return `await ${needsLowPrecedenceOperandParens(node.argument) ? `(${arg})` : arg}`;
|
|
1868
|
+
}
|
|
1867
1869
|
case 'new': {
|
|
1868
1870
|
// Host Error mapping (spec §1): `new Error(args)` → `Exception(args)` on
|
|
1869
1871
|
// Python, since `raise Error(...)` / `isinstance(x, Error)` would
|
|
@@ -1930,6 +1932,20 @@ function emitPyExprCtx(node, ctx) {
|
|
|
1930
1932
|
return out;
|
|
1931
1933
|
}
|
|
1932
1934
|
case 'binary': {
|
|
1935
|
+
// DECIMAL Slice 2 (item 3) — fail closed on `+`/`-`/`*` over a syntactically-
|
|
1936
|
+
// proven Decimal operand (`Decimal.of(...)`/`Decimal.<m>(...)`), the SAME
|
|
1937
|
+
// decision the TS leg makes (shared `assertNoDecimalOperator` + message), so
|
|
1938
|
+
// the refusal is byte-identical across targets. Conservative: a no-op for plain
|
|
1939
|
+
// numeric arithmetic and for every non-`+`/`-`/`*` operator below.
|
|
1940
|
+
//
|
|
1941
|
+
// Slice-2 remediation (Finding 2): the assert is now performed AFTER the
|
|
1942
|
+
// operands are lowered (see below, after `emitPyExprCtx(node.left/right)`),
|
|
1943
|
+
// mirroring the TS leg's lower-then-assert order. A bad Decimal operand — an
|
|
1944
|
+
// unknown member (`Decimal.nope(...)`) or a non-canonical literal
|
|
1945
|
+
// (`Decimal.of("1.10")`) — then throws its OWN specific diagnostic during
|
|
1946
|
+
// lowering instead of being masked by the generic operator error. The blocked
|
|
1947
|
+
// operators are only `+`/`-`/`*`, so deferring the assert past the bitwise /
|
|
1948
|
+
// shift / modulo branches below is safe (they never trip the Decimal check).
|
|
1933
1949
|
// Slice 6 — bitwise / shift on the slice-0.75 ToInt32 substrate. Emitted
|
|
1934
1950
|
// DIRECTLY as helper-wrapped strings (operands recurse through
|
|
1935
1951
|
// `emitPyExprCtx`), so nested bitwise ops compose without the double-
|
|
@@ -1958,6 +1974,11 @@ function emitPyExprCtx(node, ctx) {
|
|
|
1958
1974
|
// expected `(a == b) < c` evaluation order.
|
|
1959
1975
|
const left = emitPyExprCtx(node.left, ctx);
|
|
1960
1976
|
const right = emitPyExprCtx(node.right, ctx);
|
|
1977
|
+
// DECIMAL Slice 2 (item 3) — operator fail-close, now AFTER operand lowering
|
|
1978
|
+
// (Finding-2 remediation) so a bad Decimal operand surfaces its own diagnostic
|
|
1979
|
+
// first. No-op for every operator except `+`/`-`/`*`, so it never affects the
|
|
1980
|
+
// bitwise/shift/modulo paths handled above or the comparison/logical paths below.
|
|
1981
|
+
assertNoDecimalOperator(node);
|
|
1961
1982
|
if (node.op === 'instanceof') {
|
|
1962
1983
|
// JS `a instanceof B` → Python `isinstance(a, B)`. Emitting `instanceof`
|
|
1963
1984
|
// verbatim would be a Python *syntax* error, so this lowering is
|
|
@@ -2137,8 +2158,12 @@ function emitPyExprCtx(node, ctx) {
|
|
|
2137
2158
|
}
|
|
2138
2159
|
const forceLeft = needsComparisonChainParens(node.left, node.op);
|
|
2139
2160
|
const forceRight = needsComparisonChainParens(node.right, node.op);
|
|
2140
|
-
const lp = forceLeft || needsBinaryParens(node.left, node.op, 'left')
|
|
2141
|
-
|
|
2161
|
+
const lp = forceLeft || needsLowPrecedenceOperandParens(node.left) || needsBinaryParens(node.left, node.op, 'left')
|
|
2162
|
+
? `(${left})`
|
|
2163
|
+
: left;
|
|
2164
|
+
const rp = forceRight || needsLowPrecedenceOperandParens(node.right) || needsBinaryParens(node.right, node.op, 'right')
|
|
2165
|
+
? `(${right})`
|
|
2166
|
+
: right;
|
|
2142
2167
|
const op = mapBinaryOpToPython(node.op);
|
|
2143
2168
|
return `${lp} ${op} ${rp}`;
|
|
2144
2169
|
}
|
|
@@ -2864,6 +2889,13 @@ function lowerChain(node, ctx) {
|
|
|
2864
2889
|
if (node.callee.kind === 'ident') {
|
|
2865
2890
|
rejectHostRegExpValuePython(node.callee.name, ctx);
|
|
2866
2891
|
}
|
|
2892
|
+
// DECIMAL Slice 1 — bare `Decimal(...)` (ident callee) fail-closes
|
|
2893
|
+
// SYMMETRICALLY with the TS leg. Only `Decimal.of`/`Decimal.add` (member
|
|
2894
|
+
// callees, handled by `applyStdlibLoweringPython` above) are portable. A
|
|
2895
|
+
// proven user binding named `Decimal` is left alone.
|
|
2896
|
+
if (node.callee.kind === 'ident' && node.callee.name === 'Decimal' && !isProvenUserBinding(ctx, 'Decimal')) {
|
|
2897
|
+
throw new Error(decimalBareConstructionFailMessage());
|
|
2898
|
+
}
|
|
2867
2899
|
// Slice H — fail-closed on an UNMAPPED host-namespace member CALL. This runs
|
|
2868
2900
|
// AFTER every explicit lowering hook above (stdlib, regex, lambda/array,
|
|
2869
2901
|
// portable-array, super/String/Error) and BEFORE generic call emission, so a
|
|
@@ -2881,7 +2913,13 @@ function lowerChain(node, ctx) {
|
|
|
2881
2913
|
const callee = node.callee;
|
|
2882
2914
|
const inner = callee.kind === 'member' || callee.kind === 'call' || callee.kind === 'index'
|
|
2883
2915
|
? lowerChain(callee, ctx)
|
|
2884
|
-
: {
|
|
2916
|
+
: {
|
|
2917
|
+
guard: null,
|
|
2918
|
+
expr: (() => {
|
|
2919
|
+
const emitted = emitPyExprCtx(callee, ctx);
|
|
2920
|
+
return needsLowPrecedenceOperandParens(callee) ? `(${emitted})` : emitted;
|
|
2921
|
+
})(),
|
|
2922
|
+
};
|
|
2885
2923
|
const args = node.args.map((a) => emitPyExprCtx(a, ctx)).join(', ');
|
|
2886
2924
|
return { guard: inner.guard, expr: `${inner.expr}(${args})`, lambdaBind: inner.lambdaBind };
|
|
2887
2925
|
}
|
|
@@ -3549,6 +3587,15 @@ function wrapGuardIfAny(g, ctx) {
|
|
|
3549
3587
|
function needsWalrusOperandParens(child) {
|
|
3550
3588
|
return child.kind === 'conditional' || child.kind === 'lambda';
|
|
3551
3589
|
}
|
|
3590
|
+
/** Low-precedence operand positions (`a <op> b`, `await x`, `<callee>(...)`)
|
|
3591
|
+
* must wrap a conditional child so the surrounding operator/call binds to the
|
|
3592
|
+
* whole operand instead of one ternary arm. */
|
|
3593
|
+
function needsLowPrecedenceOperandParens(child) {
|
|
3594
|
+
let node = child;
|
|
3595
|
+
while (node.kind === 'typeAssert' || node.kind === 'nonNull')
|
|
3596
|
+
node = node.expression;
|
|
3597
|
+
return node.kind === 'conditional';
|
|
3598
|
+
}
|
|
3552
3599
|
/** S5 review fix — run `fn` with `ctx.banWalrus` set (save/restore), for
|
|
3553
3600
|
* emitting an operand that will be interpolated into a comprehension/
|
|
3554
3601
|
* generator ITERABLE position, where CPython rejects `:=` outright (see
|
|
@@ -3846,6 +3893,22 @@ function applyStdlibLoweringPython(call, ctx) {
|
|
|
3846
3893
|
if (moduleName === 'Array' && methodName === 'from' && call.args.some((arg) => arg.kind === 'spread')) {
|
|
3847
3894
|
throw new Error('Array.from portable lowering does not accept spread arguments; pass source and mapper directly.');
|
|
3848
3895
|
}
|
|
3896
|
+
// DECIMAL Slice 1 — `Decimal.of(arg)` string-literal + canonical-scale check,
|
|
3897
|
+
// running the SAME shared-core validator the TS leg runs, so the fail-close is
|
|
3898
|
+
// byte-identical across targets.
|
|
3899
|
+
validateDecimalConstructionArg(moduleName, methodName, call);
|
|
3900
|
+
// DECIMAL Slice 3 — same shared compile-time pow fail-close the TS leg runs, so a
|
|
3901
|
+
// non-integer / non-literal exponent or a negative base is refused byte-identically.
|
|
3902
|
+
validateDecimalPowArgs(moduleName, methodName, call);
|
|
3903
|
+
// DECIMAL Slice 3 (robustness) — same shared non-Decimal-operand fail-close the TS
|
|
3904
|
+
// leg runs: a provably-non-Decimal LITERAL operand (a host number/string/…) passed
|
|
3905
|
+
// to any Decimal op but `of` is refused byte-identically, closing the silent
|
|
3906
|
+
// cross-target divergence a raw `0.1` operand would otherwise emit.
|
|
3907
|
+
validateDecimalOperands(moduleName, methodName, call);
|
|
3908
|
+
// DECIMAL Slice 3 — same shared compile-time zero-divisor fail-close: a literal
|
|
3909
|
+
// `Decimal.of("0")` divisor to `Decimal.div`/`Decimal.mod` is refused byte-
|
|
3910
|
+
// identically with the runtime guard's message (a dynamic zero stays runtime-caught).
|
|
3911
|
+
validateDecimalDivModArgs(moduleName, methodName, call);
|
|
3849
3912
|
const listLambda = lowerListLambdaPython(moduleName, methodName, call, ctx);
|
|
3850
3913
|
if (listLambda !== null)
|
|
3851
3914
|
return listLambda;
|
|
@@ -3904,6 +3967,19 @@ function registerStdlibRequirementPython(requirement, ctx) {
|
|
|
3904
3967
|
ctx.helpers.add(KERN_JS_NUMBER_HELPERS_PY);
|
|
3905
3968
|
return;
|
|
3906
3969
|
}
|
|
3970
|
+
// DECIMAL Slice 3 — `Decimal.div/mod/pow` register the guarded-ops helper block
|
|
3971
|
+
// (single-sourced in `decimal-contract.ts`) AND the `decimal` import (the helper
|
|
3972
|
+
// body references `_KernDecimal` for the `0**0 → 1` special-case). The block's
|
|
3973
|
+
// own `from decimal import Decimal as _KernDecimal` line supplies that binding;
|
|
3974
|
+
// we still register the `decimal` module import so the Decimal VALUES the helper
|
|
3975
|
+
// operates on (`__k_decimal.Decimal(...)` from their `Decimal.of` producers) keep
|
|
3976
|
+
// their existing import path — and so a div/mod/pow used WITHOUT a co-located
|
|
3977
|
+
// `Decimal.of` literal still imports decimal.
|
|
3978
|
+
if (requirement === 'decimal-ops') {
|
|
3979
|
+
ctx.helpers.add(KERN_DECIMAL_OPS_HELPER_PY);
|
|
3980
|
+
ctx.imports.add('decimal');
|
|
3981
|
+
return;
|
|
3982
|
+
}
|
|
3907
3983
|
ctx.imports.add(requirement);
|
|
3908
3984
|
}
|
|
3909
3985
|
function lowerListLambdaPython(moduleName, methodName, call, ctx) {
|