Skip to content

javascript: promise values are opaque — Promise.resolve, new Promise, .then continuations, Promise.all/race, Promise<T> #485

Description

@swapnilpaliwal-sd

Part of #456.

Symptom

await is transparent, so an async function's return reaches its caller — but every other way a value crosses a promise is a wall:

class Widget { run() {} }
async function get() { return new Widget(); }
function getP() { return Promise.resolve(new Widget()); }
function getNewP() { return new Promise((resolve) => resolve(new Widget())); }

(await get()).run();                          // EXACT (today)
(await getP()).run();                         // MISSED
(await getNewP()).run();                      // MISSED
get().then((w) => w.run());                   // MISSED — the continuation's parameter is empty
getP().then((w) => w.run());                  // MISSED
const [a, b] = await Promise.all([get(), getP()]); a.run();   // MISSED
Promise.all([get()]).then(([w]) => w.run());  // MISSED
(await Promise.race([get()])).run();          // MISSED
await get().then((w) => w).then((w) => w.run());   // MISSED — `.then()` has no value
import('./x.js').then((m) => m.util());       // MISSED (the `await import()` form works)
/** @param {Promise<Widget>} p */ async function f(p) { (await p).run(); }   // MISSED — the generic is not read

The compiler oracle decides all of these (Promise<Widget> is inferred); on a synthetic program of these shapes 10 of 49 decided sites were MISSED and the execution oracle listed 6 missing edges — every .then continuation calling into a project method.

Mechanism

There is no promise value. expr_value for AWAIT passes the operand's value through, which is why await asyncFn() works: return_value(asyncFn) is the instance, not a promise of it. But Promise.resolve(x) is an ambient call with no value; new Promise(executor) is an ambient construction whose executor's resolve parameter is never linked to anything; .then(f) is an ambient site — callback_registered records that f is handed over, but nothing gives f's first parameter the resolved value, and the .then(...) expression itself has no value, so chains stop at the first link.

Fix

Keep the "async return IS the value" convention (no promise kind) and model the four ways a value enters or leaves a promise, in resolution/frameworks.dl or a new resolution/promises.dl:

  1. Promise.resolve(v) → expr_value(ce, k, i) :- … expr_value(v, k, i).
  2. new Promise((resolve) => …): the executor's parameter 0 is a function whose calls settle the promise — expr_value(newExpr, k, i) :- new ambient Promise, call_arg 0 = executor func m, param_decl 0 of m = p, call_site whose callee value is that parameter (callee_value(ce2, ...)for a FUNCTION_CALL throughexpr_param(_, p, callee)), call_arg(ce2, 0, v), expr_value(v, k, i).
  3. x.then(f): param_value(p0 of f) ⊇ expr_value(x); expr_value(thenCall) ⊇ return_value(f); x.catch(f) / x.finally(f) pass expr_value(x) through. Restrict to receivers that hold a project value or an ambient Promise, so an unrelated .then on an untyped receiver stays unknown.
  4. Promise.all([a, b]) → an arr value with elem_value = the union of the elements' values (positional precision is not worth a new kind); Promise.race([...]) / Promise.any → the union directly; Promise.allSettled → nothing.
  5. @param {Promise<T>} / @returns {Promise<T>}: treat Promise as a wrapper in reference-types.dl (type_ref_kind_is_wrapper-style, but for a GENERIC_APPLICATION named Promise with one child), so the declared value is T.
  6. import('./x.js') already has a value (the module); .then on it falls out of (3).

Verification

Synthetic program with the shapes above under both oracles; case 09 covers then only as a callback_registered edge, not the value flowing through it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

engineResolution / call-graph engine rulesenhancementNew feature or requestjavascriptJavaScript

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions