It is not clear how to conditionally hide frames depending on what more recent frames are on the stack.
The Dart method foo might require parametric covariant type checks and / or argument defaulting
class X<T> {
void foo(T a, // 8
[T? b = null]) { // 9
throw 'ouch'; // 10
}
}
main() {
X<Object> x = X<String>();
x.foo('abc'); // 21
x.foo(123); // 22
X<int>().foo(1,2); // 23
}
To support the checks, one strategy (used by dart2js) is to compile the method into multiple entry points.
Each entry point handles some combination of checks and argument defaulting without doing unnecessary checks.
Some entry points call others, to eventually reach the main unchecked body.
If JavaScript had tail calls (and the syntax for tail calls was compact), tail calls could be used.
X.prototype = {
foo$body(a, b) { throwExpression("ouch"); }
foo$1$unchecked(a) { return this.foo$body(a, null); }
foo$2(a, b) { return this.foo$body(checkType(a, this.T), checkTypeNullable(b, this.T)); }
foo$1(a) { return this.foo$1$unchecked(checkType(a, this.T)); }
}
(The minifier can pick almost any name for these entry points which erases the naming system, e.g., k7, q, A_, f.)
Without tail calls, multiple stack frames can represent one call. Depending which calls are commented out, one might see different JavaScript stacks for the 'same' call in Dart:
Error: "ouch"
throwExpression
foo$body
foo$1$unchecked
foo$1
main
TypeError: 123: Type 'int' is not a subtype of 'String'
checkType
foo$1
main
Error: "ouch"
throwExpression
foo$body // body is called directly because arguments don't need to be checked since T=int
main
All three stacks should be translated to a stack trace that has one frame for foo.
Error: "ouch"
foo file.dart:10
main file.dart:21
TypeError: 123: Type 'int' is not a subtype of 'String'
foo file.dart:8 // position of 'foo' or. better, position of 'T a' or 'a'
main file.dart:22
Error: "ouch"
foo file.dart:10
main file.dart:23
The frames that might have been tail calls should be hidden, but only if the target of the 'tail' call frame is not above it.
Complications include:
foo could be recursive, calling any of the entry points from the body
- The body, if small, might be inlined into any of the entry points
Does the scopes proposal contain enough mechanism to implement the policy of translating the JavaScript stack to one 'best' frame for Dart?
It is not clear how to conditionally hide frames depending on what more recent frames are on the stack.
The Dart method
foomight require parametric covariant type checks and / or argument defaultingTo support the checks, one strategy (used by dart2js) is to compile the method into multiple entry points.
Each entry point handles some combination of checks and argument defaulting without doing unnecessary checks.
Some entry points call others, to eventually reach the main unchecked body.
If JavaScript had tail calls (and the syntax for tail calls was compact), tail calls could be used.
(The minifier can pick almost any name for these entry points which erases the naming system, e.g.,
k7,q,A_,f.)Without tail calls, multiple stack frames can represent one call. Depending which calls are commented out, one might see different JavaScript stacks for the 'same' call in Dart:
All three stacks should be translated to a stack trace that has one frame for
foo.The frames that might have been tail calls should be hidden, but only if the target of the 'tail' call frame is not above it.
Complications include:
foocould be recursive, calling any of the entry points from the bodyDoes the scopes proposal contain enough mechanism to implement the policy of translating the JavaScript stack to one 'best' frame for Dart?