@scriptc/compiler 0.0.14 → 0.0.16
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/ambient/scriptc-node-fallback.d.ts +5 -5
- package/dist/backend/cc.d.ts +19 -2
- package/dist/backend/cc.js +56 -14
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +48 -1
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +3 -1
- package/dist/backend/emission/emitter.js +32 -0
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.js +104 -0
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +13 -0
- package/dist/diagnostics/diagnostic.js +44 -0
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/ffi/profile.d.ts +29 -0
- package/dist/ffi/profile.js +191 -0
- package/dist/ffi/profile.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.js +6 -6
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +17 -0
- package/dist/frontend/lowering/lower-calls.js +191 -2
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +16 -1
- package/dist/frontend/lowering/lowerer.js +44 -5
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +141 -80
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +38 -7
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +1 -1
- package/dist/ir/validate.js +54 -2
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/int-infer.js +1 -0
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as ts from "../ts7/adapter.js";
|
|
2
2
|
import type { Lowerer } from "./lowerer.js";
|
|
3
3
|
import { IrExpr, IrFunction, IrStmt, IrType, SrcLoc } from "../../ir/nodes.js";
|
|
4
|
+
import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js";
|
|
4
5
|
import { type ClassInfo } from "./lower-classes.js";
|
|
5
6
|
/** How a parameter participates in CALL-SITE COMPLETION (the frontend
|
|
6
7
|
* completes every call to the one full signature, so the IR and backends
|
|
@@ -372,6 +373,22 @@ export declare const TIMER_MODULE_MEMBERS: ReadonlySet<string>;
|
|
|
372
373
|
* form (`timers.setTimeout(...)`). Null when the member isn't a lowered
|
|
373
374
|
* timer function (the caller's fence machinery takes over). */
|
|
374
375
|
export declare function lowerTimersMemberCall(L: Lowerer, expr: ts.CallExpression, member: string, loc: SrcLoc): IrExpr | null;
|
|
376
|
+
export interface FfiValidationResult {
|
|
377
|
+
diagnostics: ScrDiagnostic[];
|
|
378
|
+
symbolsByName: ReadonlyMap<string, ReadonlySet<ts.Symbol>>;
|
|
379
|
+
}
|
|
380
|
+
/** Resolve and validate every configured outbound binding before emit.
|
|
381
|
+
* Candidate declarations are signature-only functions bearing the manifest
|
|
382
|
+
* name anywhere in the program. Multiple scoped declarations are all native
|
|
383
|
+
* bindings under the existing name-based call surface, so every candidate
|
|
384
|
+
* must fit the one manifest ABI. */
|
|
385
|
+
export declare function validateFfiImports(L: Lowerer): FfiValidationResult;
|
|
386
|
+
/** A manifest-bound call of a signature-only ambient declaration. This
|
|
387
|
+
* recognition deliberately runs before ambientUndefVarRootOf: without the
|
|
388
|
+
* manifest the exact same source keeps Node's ReferenceError semantics;
|
|
389
|
+
* with it, only the resolved declaration binding (never a shadowing
|
|
390
|
+
* function with a body) becomes a direct native call. */
|
|
391
|
+
export declare function lowerFfiCall(L: Lowerer, expr: ts.CallExpression): IrExpr | null;
|
|
375
392
|
export declare function lowerCall(L: Lowerer, expr: ts.CallExpression): IrExpr;
|
|
376
393
|
/** The RUNTIME-ARITY spread call — `f(...args)`, the rest-forwarding idiom
|
|
377
394
|
* (`const f = (...args) => from(...args)`): a spread whose length is a
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* generic instantiation (bounded by MAX_GENERIC_INSTANCES). */
|
|
5
5
|
import * as ts from "../ts7/adapter.js";
|
|
6
6
|
import { lowerGenMethodCall } from "./lower-generators.js";
|
|
7
|
-
import { BOOL, CAUGHT, DYN, F64, JSVAL, STRING, SYMBOL_T, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, canDynCheckTo, canMarshalTypedFuncIntoIsland, funcOf, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/nodes.js";
|
|
7
|
+
import { BOOL, BYTES_U8, CAUGHT, DYN, F64, JSVAL, STRING, SYMBOL_T, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, canDynCheckTo, canMarshalTypedFuncIntoIsland, funcOf, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/nodes.js";
|
|
8
8
|
import { isJsSourceFile, locOf } from "../program.js";
|
|
9
9
|
import { isGenericCallableMemberType, typeKey } from "../types.js";
|
|
10
10
|
import { PoisonError, dynFallbackType, dynUndefinedExpr, importCallHandleType, jsFuncNameOf, newFnCtx, nodeThrowExpr } from "./lowerer.js";
|
|
11
11
|
import { enforceLibBoundary } from "./lib-boundary.js";
|
|
12
12
|
import { NARROW_FIRST, builtinFenceHintOf, builtinModuleFnOf } from "./surfaces.js";
|
|
13
|
-
import { requiresDynamicDiag } from "../../diagnostics/diagnostic.js";
|
|
13
|
+
import { ffiBindingDiag, ffiSignatureDiag, requiresDynamicDiag } from "../../diagnostics/diagnostic.js";
|
|
14
14
|
import { mixinFnShapeOf } from "./lower-mixins.js";
|
|
15
15
|
import { bufEncoding, dynStringReceiver, lowerArrayFromCall, lowerDynArrayFilterCall, lowerDynArrayFlatMapCall, lowerGroupByStaticCall, lowerIteratorHelperCall, lowerObjectAssignIndexShape, lowerObjectFromEntriesCall, lowerObjectIterOverIndexShape, lowerRegexMethodCall, lowerStringMethodCall, lowerTupleReadMethodCall } from "./lower-containers.js";
|
|
16
16
|
import { lowerChildStreamMethodCall, lowerCreateRequireCall, lowerDirentMethodCall, lowerPerfHooksCall, lowerProcStreamMethodCall, lowerReflectApplyCall, lowerWatcherMethodCall } from "./lower-builtins.js";
|
|
@@ -2271,6 +2271,195 @@ export function lowerTimersMemberCall(L, expr, member, loc) {
|
|
|
2271
2271
|
}
|
|
2272
2272
|
return null;
|
|
2273
2273
|
}
|
|
2274
|
+
function ffiTypeForClass(cls) {
|
|
2275
|
+
switch (cls) {
|
|
2276
|
+
case "bool":
|
|
2277
|
+
return BOOL;
|
|
2278
|
+
case "string":
|
|
2279
|
+
return STRING;
|
|
2280
|
+
case "bytes":
|
|
2281
|
+
return BYTES_U8;
|
|
2282
|
+
case "void":
|
|
2283
|
+
return VOID;
|
|
2284
|
+
default:
|
|
2285
|
+
return F64;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
/** The declaration half of an outbound FFI binding. Kept independent of
|
|
2289
|
+
* call-site argument checks so the whole manifest can be validated even
|
|
2290
|
+
* when a configured function is never called. */
|
|
2291
|
+
function ffiDeclarationDiagnostic(L, binding, symbol, loc) {
|
|
2292
|
+
const declarations = L.checker.declarationsOf(symbol);
|
|
2293
|
+
const functionDecls = declarations.filter(ts.isFunctionDeclaration);
|
|
2294
|
+
if (functionDecls.length === 0 ||
|
|
2295
|
+
declarations.some((decl) => !ts.isFunctionDeclaration(decl)) ||
|
|
2296
|
+
functionDecls.some((decl) => decl.body !== undefined)) {
|
|
2297
|
+
return ffiBindingDiag(binding.name, "the configured name does not resolve exclusively to signature-only function declarations", loc);
|
|
2298
|
+
}
|
|
2299
|
+
if (functionDecls.some((decl) => (decl.typeParameters?.length ?? 0) > 0)) {
|
|
2300
|
+
return ffiSignatureDiag(binding.name, "generic ambient declarations cannot describe one fixed C ABI", loc);
|
|
2301
|
+
}
|
|
2302
|
+
const signatures = L.checker.getCallSignatures(L.checker.getTypeOfSymbol(symbol));
|
|
2303
|
+
if (signatures.length !== 1) {
|
|
2304
|
+
return ffiSignatureDiag(binding.name, `the ambient binding has ${signatures.length} call signatures; exactly one non-overloaded signature is required`, loc);
|
|
2305
|
+
}
|
|
2306
|
+
const signature = signatures[0];
|
|
2307
|
+
const params = signature.getParameters();
|
|
2308
|
+
if (params.length !== binding.params.length) {
|
|
2309
|
+
return ffiSignatureDiag(binding.name, `the TypeScript declaration has ${params.length} parameter(s), but the manifest declares ${binding.params.length}`, loc);
|
|
2310
|
+
}
|
|
2311
|
+
const expectedParams = binding.params.map(ffiTypeForClass);
|
|
2312
|
+
for (let i = 0; i < params.length; i++) {
|
|
2313
|
+
const paramType = L.checker.getTypeOfSymbol(params[i]);
|
|
2314
|
+
// `mapType` deliberately gives uninhabited value positions a cheap f64
|
|
2315
|
+
// slot because no TypeScript value can ever reach them. An FFI
|
|
2316
|
+
// declaration is different: it is a callable external contract, so a
|
|
2317
|
+
// `never` slot cannot truthfully describe any native parameter.
|
|
2318
|
+
if ((paramType.flags & ts.TypeFlags.Never) !== 0) {
|
|
2319
|
+
return ffiSignatureDiag(binding.name, `parameter ${i + 1} is 'never', an uninhabited TypeScript type that cannot describe a native ABI parameter`, loc);
|
|
2320
|
+
}
|
|
2321
|
+
const mapped = L.mapTypeOf(paramType);
|
|
2322
|
+
const expected = expectedParams[i];
|
|
2323
|
+
if (mapped === null || !typeEquals(mapped, expected)) {
|
|
2324
|
+
return ffiSignatureDiag(binding.name, `parameter ${i + 1} maps to '${mapped === null ? L.checker.typeToString(paramType) : L.fmt(mapped)}', ` +
|
|
2325
|
+
`which does not fit manifest class '${binding.params[i]}'`, loc);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
const returnType = L.checker.getReturnTypeOfSignature(signature);
|
|
2329
|
+
// A native function is allowed to return. Accepting `never` here would
|
|
2330
|
+
// let tsc erase all control flow after the call while the linked function
|
|
2331
|
+
// continues, making the generated program disagree with TypeScript.
|
|
2332
|
+
if ((returnType.flags & ts.TypeFlags.Never) !== 0) {
|
|
2333
|
+
return ffiSignatureDiag(binding.name, "the return type is 'never', but a native ABI return cannot uphold TypeScript's non-returning contract", loc);
|
|
2334
|
+
}
|
|
2335
|
+
const declaredReturn = L.mapTypeOf(returnType);
|
|
2336
|
+
const expectedReturn = ffiTypeForClass(binding.returns);
|
|
2337
|
+
if (declaredReturn === null || !typeEquals(declaredReturn, expectedReturn)) {
|
|
2338
|
+
return ffiSignatureDiag(binding.name, `the return maps to '${declaredReturn === null ? L.checker.typeToString(returnType) : L.fmt(declaredReturn)}', ` +
|
|
2339
|
+
`which does not fit manifest class '${binding.returns}'`, loc);
|
|
2340
|
+
}
|
|
2341
|
+
return null;
|
|
2342
|
+
}
|
|
2343
|
+
/** Resolve and validate every configured outbound binding before emit.
|
|
2344
|
+
* Candidate declarations are signature-only functions bearing the manifest
|
|
2345
|
+
* name anywhere in the program. Multiple scoped declarations are all native
|
|
2346
|
+
* bindings under the existing name-based call surface, so every candidate
|
|
2347
|
+
* must fit the one manifest ABI. */
|
|
2348
|
+
export function validateFfiImports(L) {
|
|
2349
|
+
const diagnostics = [];
|
|
2350
|
+
const symbolsByName = new Map();
|
|
2351
|
+
const configuredNames = new Set(L.ffiImports.map((binding) => binding.name));
|
|
2352
|
+
const candidates = new Map();
|
|
2353
|
+
if (configuredNames.size === 0)
|
|
2354
|
+
return { diagnostics, symbolsByName };
|
|
2355
|
+
for (const file of L.program.getSourceFiles()) {
|
|
2356
|
+
ts.walkPreorder(file, (node) => {
|
|
2357
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
2358
|
+
if (node.body === undefined &&
|
|
2359
|
+
node.name !== undefined &&
|
|
2360
|
+
configuredNames.has(node.name.text)) {
|
|
2361
|
+
const symbol = L.checker.getSymbolAtLocation(node.name);
|
|
2362
|
+
if (symbol !== undefined) {
|
|
2363
|
+
let bySymbol = candidates.get(node.name.text);
|
|
2364
|
+
if (bySymbol === undefined) {
|
|
2365
|
+
bySymbol = new Map();
|
|
2366
|
+
candidates.set(node.name.text, bySymbol);
|
|
2367
|
+
}
|
|
2368
|
+
if (!bySymbol.has(symbol))
|
|
2369
|
+
bySymbol.set(symbol, node);
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
return "skip";
|
|
2373
|
+
}
|
|
2374
|
+
if (ts.isFunctionLike(node))
|
|
2375
|
+
return "skip";
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
for (const binding of L.ffiImports) {
|
|
2379
|
+
const bySymbol = candidates.get(binding.name);
|
|
2380
|
+
if (bySymbol === undefined || bySymbol.size === 0) {
|
|
2381
|
+
diagnostics.push(ffiBindingDiag(binding.name, "the program has no signature-only function declaration with this name", { file: L.entry.fileName, start: 0, end: 0 }));
|
|
2382
|
+
continue;
|
|
2383
|
+
}
|
|
2384
|
+
const validSymbols = new Set();
|
|
2385
|
+
let valid = true;
|
|
2386
|
+
for (const [symbol, declaration] of bySymbol) {
|
|
2387
|
+
const diagnostic = ffiDeclarationDiagnostic(L, binding, symbol, locOf(declaration));
|
|
2388
|
+
if (diagnostic === null) {
|
|
2389
|
+
validSymbols.add(symbol);
|
|
2390
|
+
}
|
|
2391
|
+
else {
|
|
2392
|
+
diagnostics.push(diagnostic);
|
|
2393
|
+
valid = false;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
if (valid)
|
|
2397
|
+
symbolsByName.set(binding.name, validSymbols);
|
|
2398
|
+
}
|
|
2399
|
+
return { diagnostics, symbolsByName };
|
|
2400
|
+
}
|
|
2401
|
+
/** A manifest-bound call of a signature-only ambient declaration. This
|
|
2402
|
+
* recognition deliberately runs before ambientUndefVarRootOf: without the
|
|
2403
|
+
* manifest the exact same source keeps Node's ReferenceError semantics;
|
|
2404
|
+
* with it, only the resolved declaration binding (never a shadowing
|
|
2405
|
+
* function with a body) becomes a direct native call. */
|
|
2406
|
+
export function lowerFfiCall(L, expr) {
|
|
2407
|
+
if (!ts.isIdentifier(expr.expression))
|
|
2408
|
+
return null;
|
|
2409
|
+
const binding = L.ffiImportsByName.get(expr.expression.text);
|
|
2410
|
+
if (binding === undefined)
|
|
2411
|
+
return null;
|
|
2412
|
+
const loc = locOf(expr);
|
|
2413
|
+
const bindingError = (detail) => {
|
|
2414
|
+
L.pushDiag(ffiBindingDiag(binding.name, detail, loc));
|
|
2415
|
+
throw new PoisonError();
|
|
2416
|
+
};
|
|
2417
|
+
const signatureError = (detail) => {
|
|
2418
|
+
L.pushDiag(ffiSignatureDiag(binding.name, detail, loc));
|
|
2419
|
+
throw new PoisonError();
|
|
2420
|
+
};
|
|
2421
|
+
const symbol = L.resolveValueSymbol(expr.expression) ??
|
|
2422
|
+
bindingError("the call has no resolved TypeScript symbol");
|
|
2423
|
+
if (L.ffiBindingSymbols !== null) {
|
|
2424
|
+
const validSymbols = L.ffiBindingSymbols.get(binding.name);
|
|
2425
|
+
// No entry means the program-level pass already diagnosed this
|
|
2426
|
+
// binding. Poison the statement without duplicating that diagnostic.
|
|
2427
|
+
if (validSymbols === undefined)
|
|
2428
|
+
throw new PoisonError();
|
|
2429
|
+
if (!validSymbols.has(symbol)) {
|
|
2430
|
+
// TypeScript resolved this call to a distinct local declaration.
|
|
2431
|
+
// The manifest owns only the exact validated ambient binding; a
|
|
2432
|
+
// same-named function with a body remains ordinary scriptc code.
|
|
2433
|
+
return null;
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
else {
|
|
2437
|
+
const diagnostic = ffiDeclarationDiagnostic(L, binding, symbol, loc);
|
|
2438
|
+
if (diagnostic !== null) {
|
|
2439
|
+
L.pushDiag(diagnostic);
|
|
2440
|
+
throw new PoisonError();
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
if (expr.questionDotToken !== undefined || expr.typeArguments !== undefined) {
|
|
2444
|
+
signatureError("native bindings support direct, non-generic calls only");
|
|
2445
|
+
}
|
|
2446
|
+
if (expr.arguments.some(ts.isSpreadElement)) {
|
|
2447
|
+
signatureError("spread arguments do not have a fixed native ABI");
|
|
2448
|
+
}
|
|
2449
|
+
if (expr.arguments.length !== binding.params.length) {
|
|
2450
|
+
signatureError(`this call passes ${expr.arguments.length} argument(s), but the native ABI requires exactly ${binding.params.length}`);
|
|
2451
|
+
}
|
|
2452
|
+
const expectedParams = binding.params.map(ffiTypeForClass);
|
|
2453
|
+
const expectedReturn = ffiTypeForClass(binding.returns);
|
|
2454
|
+
const args = expr.arguments.map((arg, i) => L.lowerExprExpecting(arg, expectedParams[i]));
|
|
2455
|
+
return {
|
|
2456
|
+
kind: "ffiCall",
|
|
2457
|
+
import: binding.name,
|
|
2458
|
+
args,
|
|
2459
|
+
type: expectedReturn,
|
|
2460
|
+
loc,
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2274
2463
|
export function lowerCall(L, expr) {
|
|
2275
2464
|
const loc = locOf(expr);
|
|
2276
2465
|
// A call whose chain ROOTS at an ambient-undefined name (`declare
|