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:
Promise.resolve(v) → expr_value(ce, k, i) :- … expr_value(v, k, i).
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).
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.
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.
@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.
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.
Part of #456.
Symptom
awaitis transparent, so anasyncfunction's return reaches its caller — but every other way a value crosses a promise is a wall: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.thencontinuation calling into a project method.Mechanism
There is no promise value.
expr_valueforAWAITpasses the operand's value through, which is whyawait asyncFn()works:return_value(asyncFn)is the instance, not a promise of it. ButPromise.resolve(x)is an ambient call with no value;new Promise(executor)is an ambient construction whose executor'sresolveparameter is never linked to anything;.then(f)is an ambient site —callback_registeredrecords thatfis handed over, but nothing givesf'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.dlor a newresolution/promises.dl:Promise.resolve(v)→expr_value(ce, k, i) :- … expr_value(v, k, i).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).x.then(f):param_value(p0 of f) ⊇ expr_value(x);expr_value(thenCall) ⊇ return_value(f);x.catch(f)/x.finally(f)passexpr_value(x)through. Restrict to receivers that hold a project value or an ambient Promise, so an unrelated.thenon an untyped receiver stays unknown.Promise.all([a, b])→ anarrvalue withelem_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.@param {Promise<T>}/@returns {Promise<T>}: treatPromiseas a wrapper inreference-types.dl(type_ref_kind_is_wrapper-style, but for aGENERIC_APPLICATIONnamedPromisewith one child), so the declared value isT.import('./x.js')already has a value (the module);.thenon it falls out of (3).Verification
Synthetic program with the shapes above under both oracles; case 09 covers
thenonly as acallback_registerededge, not the value flowing through it.