This change more accurately implements ECMAScript prototype chains.
This includes using the prototype chain to lookup properties when
necessary, and copying them down upon writes.
This still isn't 100% faithful -- for example, classes and
constructor functions should be represented as real objects with
the prototype link -- so that examples like those found here will
work: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor.
I've updated marapongo/mu#70 with additional details about this.
I'm sure we'll be forced to fix this as we encounter more "dynamic"
JavaScript. (In fact, it would be interesting to start running the
pre-ES6 output of TypeScript through the compiler as test cases.)
See http://www.ecma-international.org/ecma-262/6.0/#sec-objects
for additional details on the prototype chaining semantics.
The complexity of the last round of property initialization code was
starting to bug me. I've switched away from lazily initializing them
and now eagerly initialize at the proper times: module initialization,
class initialization (for statics), and object allocation (for instances).
This includes the logic around readonly and freezing. The code is a
lot simpler now and actually enforces some invariants that we'd like to
enforce, like not silently adding new properties when a supposedly static
member load is happening (all such accesses should be dynamic ones).
This change accurately enforces readonly properties. Namely, they
should not be written to anywhere outside of the respective initializer,
but writes must be allowed within them. This means the module initializer
for module properties, the class initializer for statics, and the object
constructor for instance properties.
Now that some name lookups are context-sensitive (namely, whether
or not we should use the export or member table for inter vs. intra
module token references), we need to faithfully track the context.
This changes the way we discover the kind of export in the case of
an export declaration. This might be exporting a module member, as in
let x = 42;
export { x as y };
or it could be exporting an entire module previously imported, as in
import * as other from "other";
export { other };
In each case, we must emit a proper fully qualified token.
Our logic for detecting these cases was a bit busted previously. We
now rely on real symbolic information rather than doing hokey name lookups.
This change fixes up some issues in the big export refactoring;
namely, we need to chase down references one link at a time during
binding, before they settle. This is because an export might depend
on another export, which might depend on ...
This change moves the Mu library files back to the root. Until
marapongo/mu#57 allows us to choose a different index file, via
package.json's "main" attribute, the default module logic won't
work properly with the files underneath src/.
This change redoes the way module exports are represented. The old
mechanism -- although laudible for its attempt at consistency -- was
wrong. For example, consider this case:
let v = 42;
export { v };
The old code would silently add *two* members, both with the name "v",
one of which would be dropped since the entries in the map collided.
It would be easy enough just to detect collisions, and update the
above to mark "v" as public, when the export was encountered. That
doesn't work either, as the following two examples demonstrate:
let v = 42;
export { v as w };
let x = w; // error!
This demonstrates:
* Exporting "v" with a different name, "w" to consumers of the
module. In particular, it should not be possible for module
consumers to access the member through the name "v".
* An inability to access the exported name "w" from within the
module itself. This is solely for external consumption.
Because of this, we will use an export table approach. The exports
live alongside the members, and we are smart about when to consult
the export table, versus the member table, during name binding.
This commit implements array l-values (for dynamic loads only, since we do
not yet ever produce static ones). Note that there are some ECMAScript
compliance noteworthies about this change, which are captured in comments.
Namely, we are emulating ECMAScript's ability to index into an array
anywhere (except for negative numbers, which is a problem). This is in
contrast to the usual approach of throwing an error on out-of-bounds access,
which will crop up when we move on to other languages like Python. And yet we
are usuing a real array as the backing store, which can cause problems with
some real ECMAScript programs that use sparse arrays and expect the "bag of
properties" approach to keep memory usage reasonable.
The work item marapongo/mu#70 tracks this among other things.
This change plucks array element types out of more AST nodes, tightening
up the MuIL that MuJS produces. It also adds a few cases where we should
be emitting "object", and permits more dynamic conversions (which are
technically unsafe, but necessary for the sort of duck typing we anticipate).
This change fixes a few things about property initializers:
* Instance properties on classes need an object (`this`) for initialization.
Previously, this wasn't being passed, which leads to a verification error.
* Static properties on classes should go into the `.init` function, not the
`.ctor` function.
* Synthesized constructors need to invoke `super()` when there is a base
class. It's legal for this call to be missing from the source program
when the base's constructor has no args.
* All initializers happen immediately after such a call to `super()`, to
achieve the intended initialization order. Therefore, for an existing
constructor, we must search for the insertion point manually.
* Also add a superclass case to the existing property tests.
In select few cases (right now, just nulls and boolean true/false),
we use predefined object constants to avoid allocating wastefully.
In the future, it's possible we will do this for other types, like
interned strings. So, the graph generator should tolerate this.
This change starts tracking all objects in our MuGL graph. The reason is
that resources can be buried deep within a nest of objects, and unless we
do "on the fly" reachability analysis, we can't know a priori whether any
given object will be of interest or not. So, we track 'em all. For large
programs, this would obviously create space leak problems, so we'll
eventually, I assume, want to prune the graph at some point.
With this change, the EC2instance example produces a (gigantic) graph!
This change fixes up some cases that weren't quite right, when it
comes to load location expressions. This includes functions and
local variables. Furthermore, the loadIdentifierExpression codepath
should be routed through the general purpose load location logic,
so that it can detect the various cases.
After this change, the ec2instance example fully verifies.
Per marapongo/mu#57, this change marks the "index" module as the default.
We don't yet discover and crack open `package.json` files which may override
this by specifying a "main" function.
This change also generates entrypoint functions for all MuJS modules. To
emulate the way Node.js works -- in that every module is still a valid script
target -- we are generating entrypoints for everything; this is in contrast to
only generating them for "blueprint" modules, or somehow marking `main`
somehow. See the notes in marapongo/mu#57 for additional thoughts on this.
This change implements stack traces. This is primarily so that we
can print out a full stack trace in the face of an unhandled exception,
and is done simply by recording the full trace during evaluation
alongside the existing local variable scopes.
In dynamic scenarios, property keys could be arbitrary strings, including
invalid identifiers. This change reflects that in the runtime representation
of objects and also in the object literal and overall indexing logic.
This change transforms element access expressions much like we do
property access expressions, in that we recognize several static load
situations. (The code is rearranged to share more logic.) The code
also now recognizes situations where we must devolve into a dynamic
load (e.g., due to a string literal, dynamic LHS, etc).
This change fixes a few issues:
* Emit ObjectLiteral types as `dynamic`. We can do better here, including
spilling the "anonymous" types, for cases where cross-module exports/imports
are leveraged. For the time being, however, we will erase them to dynamic.
This is a heck of a lot better than emitting garbage `__object` type tokens.
* Implement TypeScript TypeAssertionExpression lowering into casts.
* Recognize static class property references. Previously, we tried to load
the class as a location, which resulted in nonsense, unverifiable MuIL. Now
we properly detect that the target is a property symbol with the static
modifier, and emit the token without an object reference (as expected).
* Accompany the above with a bunch of tests that stress these paths.
This changes two aspects of the ResourceProvider.Update RPC API:
1. Update needs to return an ID, in case the resource had to be
recreated in response to the request.
2. Include both the old and the new values for properties that are
being updated.
We weren't properly emitting fully qualified tokens for properties
and "block scoped variables" (of which, module properties qualify).
This change fixes that, bringing the ec2instance verification error
count down from 22 to 7.
This change "kicks the can" of dealing with `dynamic` down the road.
Namely, the binder will be permissive about letting dynamic objects
flow through to the evaluation phase. At that point, the evaluator
will be responsible for performing duck typing, etc.
This change introduces support for dynamic object literals. If the
type of a literal is dynamic, then the property initializers for that
literal are naked strings; if the type of a literal is anything else,
however, we expect that the property names are full blown member tokens.
This change simplifies certain types while lowering to MuIL, fixing
a number of assertion failures that cropped up after my tightening of
the type checking (namely, asserting rather than silently logging).
This comes in the following forms:
1) StringLiteral, NumberLiteral, and BooleanLiteral types can simply
become String, Number, and Boolean, respectively. This loses some
static type-checking (e.g., ensuring that values are within range),
however at least the physical runtime type will match.
2) EnumLiteral types can simply adopt their base types, with similar
downsides and caveats to the other literal types (range widening).
3) Any union types that contain Undefined or Null types can be simplified
to omit those types. If, after filtering them out, we are left with
a single type (admitting the recursive case of multiple XLiterals that
simplified down to the exact same primitive type), we will eliminate
the union altogether and just use the underlying simple type. This
handles the case of optional interface properties, e.g. `T?`, which are
internally represented in TypeScript as `T|undefined` union types.
There are two follow up work items for later on:
* marapongo/mu#64 tracks support for non-nullability. At the moment we
are tossing away any `|undefined` or `|null` unions, including `T?`,
because MuIL currently permits nullability everywhere, but we want to
consider honoring these annotations (eventually).
* marapongo/mu#82 tracks support for union types. This is how we intended
to support true enum types, so tossing away this information is a bit
unfortunate. But this is a feature for another day.