mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-03 07:54:40 +08:00
Merge base branch
This commit is contained in:
@@ -156,18 +156,20 @@ export function prepareExecutableModule(
|
||||
);
|
||||
}
|
||||
// A failed program whose compiled packages need the runtime or a
|
||||
// mutable object model (NI-2's report): try them as islands, and
|
||||
// report this failure if that build fails too.
|
||||
// mutable object model (NI-2's report): try them as islands. If that
|
||||
// build fails too, its failure is the one reported. This build typed
|
||||
// those packages from their JavaScript, where the types they only
|
||||
// declare (interfaces, aliases such as `Static<T>`) are `any`, so its
|
||||
// diagnostics describe a program the island build does not compile:
|
||||
// there the packages' declarations type the user code.
|
||||
const marked = retryFences
|
||||
? fenceIslands(entryPath, opts, fe, lowered, lowerIslands, false)
|
||||
: null;
|
||||
if (marked !== null) {
|
||||
const diagnostics = lowered.diagnostics;
|
||||
const texts = fe.sourceTexts();
|
||||
fe.dispose();
|
||||
frontendDisposed();
|
||||
disposed = true;
|
||||
const islanded = prepareExecutableModule(
|
||||
return prepareExecutableModule(
|
||||
entryPath,
|
||||
opts,
|
||||
ffi,
|
||||
@@ -177,8 +179,6 @@ export function prepareExecutableModule(
|
||||
marked,
|
||||
false,
|
||||
);
|
||||
if (islanded.ok) return islanded;
|
||||
return { ok: false, diagnostics, sourceTexts: texts };
|
||||
}
|
||||
return fail(lowered.diagnostics);
|
||||
}
|
||||
|
||||
@@ -929,11 +929,20 @@ function toIsland(lowerer: Lowerer, node: ts.Expression): IrExpr {
|
||||
// NI-5: the runtime exports dyn objects by reference and copies only
|
||||
// primitives, strings and (for now) arrays.
|
||||
return lowerer.coerceToExpected(lowerer.lowerExpr(node), DYN);
|
||||
case "refused":
|
||||
case "refused": {
|
||||
const type = lowerer.typeOf(node);
|
||||
// A type parameter the instance binds to dyn (a generic function
|
||||
// over island types, `<T extends TSchema>`): its values are dyn
|
||||
// values, and they cross the way `any` values do.
|
||||
if (
|
||||
(type.flags & ts.TypeFlags.TypeParameter) !== 0 &&
|
||||
lowerer.typeParamResolver(type)?.kind === "dyn"
|
||||
)
|
||||
return lowerer.coerceToExpected(lowerer.lowerExpr(node), DYN);
|
||||
// NI-5: closures, promises, records and class instances cross by
|
||||
// reference (scr_node_island_export.c): their dyn boxes keep the
|
||||
// native referent, and the closure's thunk checks its arguments.
|
||||
if (exportedByReference(lowerer, lowerer.typeOf(node))) {
|
||||
if (exportedByReference(lowerer, type)) {
|
||||
const lowered = lowerer.lowerExpr(node);
|
||||
const value = lowerer.coerceToExpected(lowered, DYN);
|
||||
return lowered.type.kind === "object"
|
||||
@@ -942,9 +951,10 @@ function toIsland(lowerer: Lowerer, node: ts.Expression): IrExpr {
|
||||
}
|
||||
return boundaryFail(
|
||||
lowerer,
|
||||
`a '${lowerer.checker.typeToString(lowerer.typeOf(node))}' value (a native object would cross as a copy and lose its identity)`,
|
||||
`a '${lowerer.checker.typeToString(type)}' value (a native object would cross as a copy and lose its identity)`,
|
||||
node,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/6829-node-island-declared-types/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// @requires-libnode
|
||||
// Typed code names an island package's declared interfaces and generic
|
||||
// aliases: the package's declarations type it, while its values cross the
|
||||
// boundary as handles.
|
||||
import { Arr, Num, Str, check, type Static, type TArray, type TNumber, type TSchema, type TString } from "schemakit";
|
||||
|
||||
interface Tool<T extends TSchema = TSchema> {
|
||||
name: string;
|
||||
parameters: T;
|
||||
execute(id: string, params: Static<T>): string;
|
||||
}
|
||||
|
||||
const sum: Tool<TArray<TNumber>> = {
|
||||
name: "sum",
|
||||
parameters: Arr(Num()),
|
||||
execute: (id, params) => `${id}: ${params.reduce((a, b) => a + b, 0)}`,
|
||||
};
|
||||
const shout: Tool<TString> = {
|
||||
name: "shout",
|
||||
parameters: Str(),
|
||||
execute: (id, params) => `${id}: ${params.toUpperCase()}`,
|
||||
};
|
||||
|
||||
function run<T extends TSchema>(tool: Tool<T>, input: unknown): string {
|
||||
if (!check(tool.parameters, input)) return `${tool.name}: rejected ${JSON.stringify(input)}`;
|
||||
return tool.execute(tool.name, input);
|
||||
}
|
||||
|
||||
function describeAll(schemas: TSchema[]): string[] {
|
||||
return schemas.map((s) => `${s.kind} ${s.describe()}`);
|
||||
}
|
||||
|
||||
console.log(run(sum, [1, 2, 3]));
|
||||
console.log(run(sum, [1, "2"]));
|
||||
console.log(run(shout, "quiet"));
|
||||
console.log(run(shout, 7));
|
||||
console.log(describeAll([sum.parameters, shout.parameters, Arr(Arr(Str()))]).join(" | "));
|
||||
const tools: Tool[] = [sum, shout];
|
||||
console.log(tools.map((t) => t.name).join(","));
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Types the package only declares: its JavaScript has no interfaces and no
|
||||
// conditional aliases, so code typed from the JavaScript sees `any` here.
|
||||
export interface TSchema {
|
||||
readonly kind: string;
|
||||
describe(): string;
|
||||
}
|
||||
export interface TNumber extends TSchema {
|
||||
readonly kind: "number";
|
||||
}
|
||||
export interface TString extends TSchema {
|
||||
readonly kind: "string";
|
||||
}
|
||||
export interface TArray<T extends TSchema = TSchema> extends TSchema {
|
||||
readonly kind: "array";
|
||||
readonly items: T;
|
||||
}
|
||||
export type Static<T extends TSchema> = T extends TNumber
|
||||
? number
|
||||
: T extends TString
|
||||
? string
|
||||
: T extends TArray<infer I>
|
||||
? Static<I>[]
|
||||
: unknown;
|
||||
export declare function Num(): TNumber;
|
||||
export declare function Str(): TString;
|
||||
export declare function Arr<T extends TSchema>(items: T): TArray<T>;
|
||||
export declare function check<T extends TSchema>(schema: T, value: unknown): value is Static<T>;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
"use strict";
|
||||
// The esbuild CommonJS bundle prologue: getters over live `let` bindings
|
||||
// and a constructor function with prototype methods. scriptc compiles it
|
||||
// natively only with runtime fences, so it runs in a Node island.
|
||||
var __defProp = Object.defineProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all) __defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var src_exports = {};
|
||||
__export(src_exports, {
|
||||
Num: () => Num,
|
||||
Str: () => Str,
|
||||
Arr: () => Arr,
|
||||
check: () => check,
|
||||
});
|
||||
module.exports = src_exports;
|
||||
function Schema(kind, items) {
|
||||
this.kind = kind;
|
||||
if (items !== undefined) this.items = items;
|
||||
}
|
||||
Schema.prototype.describe = function () {
|
||||
return this.items === undefined ? this.kind : this.kind + "<" + this.items.describe() + ">";
|
||||
};
|
||||
function Num() {
|
||||
return new Schema("number");
|
||||
}
|
||||
function Str() {
|
||||
return new Schema("string");
|
||||
}
|
||||
function Arr(items) {
|
||||
return new Schema("array", items);
|
||||
}
|
||||
function check(schema, value) {
|
||||
if (schema.kind === "array") return Array.isArray(value) && value.every((v) => check(schema.items, v));
|
||||
return typeof value === schema.kind;
|
||||
}
|
||||
0 && (module.exports = { Num, Str, Arr, check });
|
||||
+1
@@ -0,0 +1 @@
|
||||
{ "name": "schemakit", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" }
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// A failed build whose package runs in a Node island reports the island
|
||||
// build's errors: there the package's declarations type the program. The
|
||||
// test builds it beside corpus 6829's schemakit package.
|
||||
import { Num, check, type Static, type TNumber, type TSchema } from "schemakit";
|
||||
|
||||
interface Tool<T extends TSchema = TSchema> {
|
||||
name: string;
|
||||
parameters: T;
|
||||
execute(id: string, params: Static<T>): string;
|
||||
}
|
||||
|
||||
const double: Tool<TNumber> = {
|
||||
name: "double",
|
||||
parameters: Num(),
|
||||
execute: (id, params) => `${id}: ${params * 2}`,
|
||||
};
|
||||
const input: unknown = 4;
|
||||
if (check(double.parameters, input)) console.log(double.execute(double.name, input));
|
||||
// The build's own error: member decorators have no native lowering.
|
||||
function logged(method: () => number, context: ClassMethodDecoratorContext): void {}
|
||||
class Counter {
|
||||
@logged
|
||||
next(): number {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
console.log(new Counter().next());
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 3400, "san": 62000 }
|
||||
@@ -354,3 +354,28 @@ test("recursive JSON types cross into Node", async () => {
|
||||
const ir = JSON.stringify(deserializeModule(readFileSync(outPath, "utf8")));
|
||||
expect(ir).toContain("nodeIsland.callSync");
|
||||
}, 300_000);
|
||||
|
||||
// When a build fails and the packages it compiled with runtime fences then
|
||||
// fail as islands too, the island build's errors are reported. That build
|
||||
// types the user code from the packages' declarations; the native one
|
||||
// typed it from their JavaScript, where declared-only types (`Static<T>`)
|
||||
// are `any`. Needs no libnode pack: both builds fail before linking.
|
||||
test("a failed build reports the island build's errors", async () => {
|
||||
const dir = join(outDir, "declared-types");
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
cpSync(
|
||||
join(repoRoot, "tests/corpus/6829-node-island-declared-types/node_modules"),
|
||||
join(dir, "node_modules"),
|
||||
{ recursive: true },
|
||||
);
|
||||
copyFileSync(
|
||||
join(repoRoot, "tests/fixtures/node-island/declared-types.ts"),
|
||||
join(dir, "main.ts"),
|
||||
);
|
||||
const result = await compile(join(dir, "main.ts"), { outPath: join(dir, "main"), outDir: dir });
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) return;
|
||||
const messages = result.diagnostics.map((d) => `${d.code}: ${d.message}`);
|
||||
expect(messages.some((m) => m.startsWith("SC1090: method decorators"))).toBe(true);
|
||||
expect(messages.filter((m) => /Tool|Static|'check'/.test(m))).toEqual([]);
|
||||
}, 300_000);
|
||||
|
||||
Reference in New Issue
Block a user