@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.
Files changed (39) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +5 -5
  2. package/dist/backend/cc.d.ts +19 -2
  3. package/dist/backend/cc.js +56 -14
  4. package/dist/backend/cc.js.map +1 -1
  5. package/dist/backend/emission/emit-exprs.js +48 -1
  6. package/dist/backend/emission/emit-exprs.js.map +1 -1
  7. package/dist/backend/emission/emitter.d.ts +3 -1
  8. package/dist/backend/emission/emitter.js +32 -0
  9. package/dist/backend/emission/emitter.js.map +1 -1
  10. package/dist/backend/llvm/emitter.js +104 -0
  11. package/dist/backend/llvm/emitter.js.map +1 -1
  12. package/dist/diagnostics/diagnostic.d.ts +13 -0
  13. package/dist/diagnostics/diagnostic.js +44 -0
  14. package/dist/diagnostics/diagnostic.js.map +1 -1
  15. package/dist/ffi/profile.d.ts +29 -0
  16. package/dist/ffi/profile.js +191 -0
  17. package/dist/ffi/profile.js.map +1 -0
  18. package/dist/frontend/lowering/lower-builtins.js +6 -6
  19. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  20. package/dist/frontend/lowering/lower-calls.d.ts +17 -0
  21. package/dist/frontend/lowering/lower-calls.js +191 -2
  22. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  23. package/dist/frontend/lowering/lowerer.d.ts +16 -1
  24. package/dist/frontend/lowering/lowerer.js +44 -5
  25. package/dist/frontend/lowering/lowerer.js.map +1 -1
  26. package/dist/index.d.ts +7 -0
  27. package/dist/index.js +141 -80
  28. package/dist/index.js.map +1 -1
  29. package/dist/ir/nodes.d.ts +38 -7
  30. package/dist/ir/nodes.js.map +1 -1
  31. package/dist/ir/serialize.d.ts +1 -1
  32. package/dist/ir/serialize.js +1 -1
  33. package/dist/ir/validate.js +54 -2
  34. package/dist/ir/validate.js.map +1 -1
  35. package/dist/library/int-infer.js +1 -0
  36. package/dist/library/int-infer.js.map +1 -1
  37. package/dist/library/int-infer.test.js +1 -1
  38. package/package.json +2 -2
  39. 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