Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/compiler/src/coverage/surface-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
SET_COMBINE_METHODS,
SET_METHODS,
STATIC_MATH_FNS,
STATIC_MATH_PROPS,
STATIC_NUMBER_METHODS,
STR_METHODS,
UNSUPPORTED_EXPR,
Expand Down Expand Up @@ -222,6 +223,9 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes
add({ id: `stdlib.math.${name}`, kind: "stdlib", name: `Math.${name}`, status: "dynamic-only", code: "SC2012" });
}
}
for (const name of Object.keys(STATIC_MATH_PROPS)) {
add({ id: `stdlib.math.${name}`, kind: "stdlib", name: `Math.${name}`, status: "static" });
}
for (const name of Object.keys(ISLAND_SURFACE.math.props)) {
add({ id: `stdlib.math.${name}`, kind: "stdlib", name: `Math.${name}`, status: "dynamic-only", code: "SC2012" });
}
Expand Down
10 changes: 5 additions & 5 deletions packages/compiler/src/frontend/lowering/lower-island.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { InternalCompilerError } from "../../errors.js";
import * as ts from "../ts7/adapter.js";
import type { Lowerer } from "./lowerer.js";
import { BOOL, BYTES_U8, DYN, F64, IrExpr, IrStmt, IrType, JSVAL, MAX_ISLAND_CALLBACK_ARITY, STRING, VOID, canConvertToDyn, canMarshalTypedFuncIntoIsland, islandPromisePayloadTag, isUnitType } from "../../ir/ir.js";
import { ISLAND_SURFACE, IslandFnEntry, STATIC_MATH_FNS, boundaryIntoIslandMsg } from "./surfaces.js";
import { ISLAND_SURFACE, IslandFnEntry, STATIC_MATH_FNS, STATIC_MATH_PROPS, boundaryIntoIslandMsg } from "./surfaces.js";
import { requiresDynamicApiDiag, requiresDynamicPackageDiag } from "../../diagnostics/diagnostic.js";
import { isCjsJsFile, isJsSourceFile, locOf, npmPackageNameOf } from "../program.js";
import { foldedStringKeyOf, lowerDynObjectLiteral, pureReemittable } from "./lower-exprs.js";
Expand Down Expand Up @@ -3218,14 +3218,14 @@ export function lowerStaticReadableStreamReaderCall(
return finish(lowerer.jsvalIn(lowerer.lowerExpr(access.expression), access.expression), entry);
}

/** `Math.PI` / `Math.E` property READS: getProp off globalGet("Math"),
* exiting to the declared number type. Math methods referenced without a
* call are rejected specifically (no value form exists, --dynamic or
* not). Null for non-Math receivers (the property chain keeps trying). */
export function lowerMathProperty(lowerer: Lowerer, expr: ts.PropertyAccessExpression): IrExpr | null {
const member = lowerer.stdlibGlobalMember(expr, "Math");
if (member === null) return null;
const loc = locOf(expr);
const staticPropValue = own(STATIC_MATH_PROPS, member);
if (staticPropValue !== undefined) {
return { kind: "numLit", value: staticPropValue, type: F64, loc };
}
const propType = own(ISLAND_SURFACE.math.props, member);
if (propType !== undefined) {
lowerer.requireDynamicApi(`'Math.${member}'`, expr);
Expand Down
10 changes: 7 additions & 3 deletions packages/compiler/src/frontend/lowering/surfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,7 @@ export const boundaryOutOfIslandMsg = (typeName: string): string =>
* own declarations — a drifted entry fails a test instead of surprising a
* user. */
export const ISLAND_SURFACE = {
/** `Math.<fn>(...)` lowers to callMethod(globalGet("Math"), fn, args);
* the readonly number props (`Math.PI`) to getProp(globalGet("Math")).
/** `Math.<fn>(...)` lowers to callMethod(globalGet("Math"), fn, args).
* min/max/atan2/hypot/pow are declared with exactly two parameters
* (rest/optional parameters aren't representable). */
math: {
Expand All @@ -476,7 +475,7 @@ export const ISLAND_SURFACE = {
round: ISL_N1,
sign: ISL_N1, sin: ISL_N1, sqrt: ISL_N1, tan: ISL_N1, trunc: ISL_N1,
} as Record<string, IslandFnEntry | undefined>,
props: { PI: F64, E: F64 } as Record<string, IrType | undefined>,
props: {} as Record<string, IrType | undefined>,
},
/** Methods on `number` receivers. The receiver marshals by value; the
* engine auto-boxes primitives on method calls, so `this` binds the
Expand Down Expand Up @@ -510,6 +509,11 @@ export const ISLAND_SURFACE = {
} as Record<string, IslandFnEntry | undefined>,
};

export const STATIC_MATH_PROPS: Record<string, number | undefined> = {
E: Math.E,
PI: Math.PI,
};

/** Math members with a STATIC lowering — each is one C call that IS the
* JS operation, at the tabled arity (floor: libm's floor; min/max: the
* NaN-poisoning ±0-ordered scalar folds; random: arc4random-backed
Expand Down
6 changes: 2 additions & 4 deletions packages/compiler/surface-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2560,15 +2560,13 @@
"id": "stdlib.math.E",
"kind": "stdlib",
"name": "Math.E",
"status": "dynamic-only",
"code": "SC2012"
"status": "static"
},
{
"id": "stdlib.math.PI",
"kind": "stdlib",
"name": "Math.PI",
"status": "dynamic-only",
"code": "SC2012"
"status": "static"
},
{
"id": "stdlib.math.abs",
Expand Down
8 changes: 8 additions & 0 deletions tests/corpus/1538-math-static-scalar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
// this whole program compiles statically.
console.log(Math.min(2, -9), Math.max(2, -9), Math.min(1.5, 1.5), Math.max(-3, -3));
console.log(Math.min(Infinity, 7), Math.max(-Infinity, 7), Math.min(-Infinity, Infinity));
const pi = Math.PI;
const e = Math.E;
console.log(pi.toFixed(12), e.toFixed(12), (pi + e).toFixed(12), (pi * e).toFixed(12));
const nan = 0 / 0; // NaN-as-a-value stays fenced; the arithmetic form compiles
console.log(Math.min(nan, 1), Math.max(1, nan), Math.min(nan, nan));
// ±0: detect the sign through division (String(-0) is "0" either way).
Expand All @@ -30,3 +33,8 @@ console.log(ok, low < 0.5, high >= 0.5, low !== high);
const a = Math.random();
const b = Math.random();
console.log(typeof a, a === b);

{
const Math = { PI: 4, E: 3 };
console.log(Math.PI, Math.E);
}
15 changes: 5 additions & 10 deletions tests/diagnostics/dynamic-surface.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
// The island-backed ambient surface (Math beyond the static members,
// number methods, string-pattern replace/at, the Number statics, ...)
// typechecks against real static types but executes in the embedded
// engine: in a static build every use site is its own SC2012 naming the
// flag — never an ICE, never a link error. (Math.floor/abs/round and the
// ask-4 wholeness-discharge pair Math.trunc/ceil, .split(string), the
// trim/pad variants, parseInt, isNaN, and the global parseFloat/isFinite
// over exactly-typed arguments compile statically now and no longer
// appear here.)
const up = Math.sqrt(2);
const tau = Math.PI * 2;
const price = (19.99).toPrecision(4);
const swapped = "banana".replace("an", "AN");
const ch = "hello".at(0);
const n = Number.parseFloat("3.14");
void up;
void price;
void swapped;
void ch;
void n;
58 changes: 24 additions & 34 deletions tests/harness/__snapshots__/dynamic-surface.ts.txt
Original file line number Diff line number Diff line change
@@ -1,53 +1,43 @@
dynamic-surface.ts:10:12 - error SC2012: 'Math.sqrt' runs in the embedded dynamic engine, which this build does not include
dynamic-surface.ts:1:12 - error SC2012: 'Math.sqrt' runs in the embedded dynamic engine, which this build does not include

9 | // appear here.)
10 | const up = Math.sqrt(2);
| ^~~~~~~~~~~~
11 | const tau = Math.PI * 2;
1 | const up = Math.sqrt(2);
| ^~~~~~~~~~~~
2 | const price = (19.99).toPrecision(4);

hint: build with --dynamic to run this call in the embedded engine (adds ~620KB to the binary); static builds never include it

dynamic-surface.ts:11:13 - error SC2012: 'Math.PI' runs in the embedded dynamic engine, which this build does not include
dynamic-surface.ts:2:15 - error SC2012: '.toPrecision()' on numbers runs in the embedded dynamic engine, which this build does not include

10 | const up = Math.sqrt(2);
11 | const tau = Math.PI * 2;
| ^~~~~~~
12 | const price = (19.99).toPrecision(4);
1 | const up = Math.sqrt(2);
2 | const price = (19.99).toPrecision(4);
| ^~~~~~~~~~~~~~~~~~~~~~
3 | const swapped = "banana".replace("an", "AN");

hint: build with --dynamic to run this call in the embedded engine (adds ~620KB to the binary); static builds never include it

dynamic-surface.ts:12:15 - error SC2012: '.toPrecision()' on numbers runs in the embedded dynamic engine, which this build does not include
dynamic-surface.ts:3:17 - error SC2012: '.replace()' on strings runs in the embedded dynamic engine, which this build does not include

11 | const tau = Math.PI * 2;
12 | const price = (19.99).toPrecision(4);
| ^~~~~~~~~~~~~~~~~~~~~~
13 | const swapped = "banana".replace("an", "AN");
2 | const price = (19.99).toPrecision(4);
3 | const swapped = "banana".replace("an", "AN");
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 | const ch = "hello".at(0);

hint: build with --dynamic to run this call in the embedded engine (adds ~620KB to the binary); static builds never include it

dynamic-surface.ts:13:17 - error SC2012: '.replace()' on strings runs in the embedded dynamic engine, which this build does not include
dynamic-surface.ts:4:12 - error SC2012: '.at()' on strings runs in the embedded dynamic engine, which this build does not include

12 | const price = (19.99).toPrecision(4);
13 | const swapped = "banana".replace("an", "AN");
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
14 | const ch = "hello".at(0);
3 | const swapped = "banana".replace("an", "AN");
4 | const ch = "hello".at(0);
| ^~~~~~~~~~~~~
5 | const n = Number.parseFloat("3.14");

hint: build with --dynamic to run this call in the embedded engine (adds ~620KB to the binary); static builds never include it

dynamic-surface.ts:14:12 - error SC2012: '.at()' on strings runs in the embedded dynamic engine, which this build does not include
dynamic-surface.ts:5:11 - error SC2012: 'Number.parseFloat' runs in the embedded dynamic engine, which this build does not include

13 | const swapped = "banana".replace("an", "AN");
14 | const ch = "hello".at(0);
| ^~~~~~~~~~~~~
15 | const n = Number.parseFloat("3.14");

hint: build with --dynamic to run this call in the embedded engine (adds ~620KB to the binary); static builds never include it

dynamic-surface.ts:15:11 - error SC2012: 'Number.parseFloat' runs in the embedded dynamic engine, which this build does not include

14 | const ch = "hello".at(0);
15 | const n = Number.parseFloat("3.14");
| ^~~~~~~~~~~~~~~~~~~~~~~~~
16 |
4 | const ch = "hello".at(0);
5 | const n = Number.parseFloat("3.14");
| ^~~~~~~~~~~~~~~~~~~~~~~~~
6 | void up;

hint: build with --dynamic to run this call in the embedded engine (adds ~620KB to the binary); static builds never include it
3 changes: 2 additions & 1 deletion tests/harness/surface-manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ const PROBES: Probe[] = [
{ id: "stdlib.array.unshift", source: "const xs: number[] = [2];\nconsole.log(xs.unshift(1), xs[0]);\n" },
{ id: "stdlib.array.reverse", source: "const xs: number[] = [1, 2];\nconsole.log(xs.reverse()[0]);\n" },
{ id: "stdlib.math.floor", source: "console.log(Math.floor(1.5));\n" },
{ id: "stdlib.math.E", source: "console.log(Math.E);\n" },
{ id: "stdlib.math.PI", source: "console.log(Math.PI);\n" },
{ id: "stdlib.map.has", source: 'const m = new Map<string, number>();\nm.set("a", 1);\nconsole.log(m.has("a"));\n' },
{ id: "stdlib.date.now", source: "console.log(Date.now() > 0);\n" },
{ id: "stdlib.number.toFixed", source: "const n = 1.2345;\nconsole.log(n.toFixed(2));\n" },
Expand Down Expand Up @@ -142,7 +144,6 @@ const PROBES: Probe[] = [
// status dynamic-only — refused with the entry's code statically,
// analyzed clean under --dynamic
{ id: "stdlib.math.sqrt", source: "console.log(Math.sqrt(2));\n" },
{ id: "stdlib.math.PI", source: "console.log(Math.PI);\n" },
{ id: "stdlib.string.replace", source: 'console.log("aa".replace("a", "b"));\n' },
{
id: "stdlib.headers.entries",
Expand Down