1
0
Fork 0
Commit Graph

58 Commits (60ce72508a4a854719c798d39a74c98eb19724b9)

Author SHA1 Message Date
Mike Gerwitz 016bb1362b
Copyright year update for all files modified since 0.2.8
* lib/MemberBuilderValidator.js: Updated copyright year.
* lib/class.js: Updated copyright year.
* test/Class/ConstructorTest.js: Updated copyright year.
* test/Class/ExtendTest.js: Updated copyright year.
* test/Class/GeneralTest.js: Updated copyright year.
* test/MemberBuilderValidator/MethodTest.js: Updated copyright year.
* test/Trait/LinearizationTest.js: Updated copyright year.
* test/Trait/VirtualTest.js: Updated copyright year.
2017-11-04 15:08:02 -04:00
Mike Gerwitz bb3956f1b4
Fix trait __inst
`this.__inst' within trait methods will now correctly resolve to the public
visibility object of the class we're mixed into, rather than
`undefined'.  This behavior is consistent with the rest of the system.

* lib/ClassBuilder.js (initInstance): Add `inst' to private metadata.  This
  is the public visibility object.
* lib/Trait.js (tctor): Initialize concrete trait `__inst' to aforementioned
  `inst' metadata value.
* test/Trait/LinearizationTest.js: Add respective test.
2017-11-02 00:15:31 -04:00
Mike Gerwitz a35ecc6f94
Test parameterized traits with named staging object
* test/Trait/NamedTest.js: Add regression test.
  Update copyright to include 2017.
2017-06-30 02:06:36 -04:00
Mike Gerwitz 90fd1a8d08
`override' implies `virtual'
This behavior is consistent with other OO languages like C++ and C# that do
not have virtual methods by default.

This solution isn't ideal, but I don't have time for a larger refactoring
right now.  I sat on this change for a good few weeks before committing it
unchanged.

* lib/MemberBuilderValidator.js (validateMethod): Allow override of
  supertype overrides.

* test/*: Stripped `virtual' keyword where appropriate.

* doc/classes.texi (Inheritance): Update to state that `override' implies
  `virtual'.
2017-06-30 02:01:40 -04:00
Mike Gerwitz b841b9cc5e
Fix trait extending of supertype with constructor
Supertypes that extend constructors may now be extended by traits without
completely blowing up.  Good feature.

* lib/Trait.js (__tconstruct): Add function.
  (createVirtProxy): Use it.

* test/Trait/ClassExtendTest.js: Add test.
2017-01-02 23:34:29 -05:00
Mike Gerwitz 35dacc00da Minor cleanup to trait naming test
test/Trait/NamedTest.js: Cleanup interface-related naming test.
2016-12-29 03:50:09 -05:00
Mike Gerwitz e93f3a70f9 Support for named trait class extending
We can call this a bugfix...it's more of a neglected feature that's
otherwise completely inconsistent with the rest of the system. :)

* lib/Trait.js (createNamedTrait): Support base.
  (_createStaging) [extend]: Support base.

* test/Trait/NamedTest.js: Add test.
2016-12-29 03:49:47 -05:00
Mike Gerwitz 017b85d75b
Copyright year updates for recent trait changes
* lib/Trait.js: Add Copyright year 2016.
* test/Trait/ContextTest.js: Add Copyright year 2016.
2016-12-29 03:11:49 -05:00
Mike Gerwitz 54b0c79fed
Rename Trait/{Scope=>Context}Test
This better reflects its focus.

* test/Trait/ScopeTest.js: Rename file (delete).
* test/Trait/ContextTest.js: Rename file (create).
2016-12-29 02:39:26 -05:00
Mike Gerwitz 04e98e682e
Correct trait->class calling context on class supertype
See test cases for more information.  This was a pretty unfortunate and
nasty bug that I discovered while working on a project that uses easejs; it
wasn't something that was found previously because this support was only
added relatively recently, and this problem does not exist if an interface
is used.

* lib/Trait.js (bindSuperCtx): Add function.
  (tctor): Use it.

* test/Trait/ScopeTest.js: Add calling context tests.
2016-12-29 02:39:16 -05:00
Mike Gerwitz 903a1a135c
Prevent mixin failure on null/undefined supertype properties 2015-10-26 22:17:28 -04:00
Mike Gerwitz 47d51fd5da
Provide useful error on attempt to mix in non-trait
Before this change, mixin attempts would fail at the time of mixin when
easejs attempts to invoke the `__mixin` method on the object.  This is both
cryptic and void of any useful information on the stack.
2015-10-25 22:15:34 -04:00
Mike Gerwitz d9b86c1544
Support for trait class supertype overrides
Traits can now override methods of their class supertypes.  Previously, in
order to override a method of some class `C` by mixing in some trait `T`,
both had to implement a common interface.  This had two notable downsides:

  1. A trait that was only compatible with details of `C` could only work
     with `C#M` if it implemented an interface `I` that declared `I#M`.
     This required the author of `C` to create interfaces where they would
     otherwise not be necessary.

  2. As a corollary of #1---since methods of interfaces must be public, it
     was not possible for `T` to override any protected method of `C`; this
     meant that `C` would have to declare such methods public, which may
     break encapsulation or expose unnecessary concerns to the outside
     world.

Until documentation is available---hopefully in the near future---the test
cases provide detailed documentation of the behavior.  Stackable traits work
as you would expect:

```javascript
var C = Class(
{
    'virtual foo': function()
    {
        return 'C';
    },
} );

var T1 = Trait.extend( C,
{
    'virtual abstract override foo': function()
    {
        return 'T1' + this.__super();
    },
} );

var T2 = Trait.extend( C,
{
    'virtual abstract override foo': function()
    {
        return 'T2' + this.__super();
    },
} );

C.use( T1 )
    .use( T1 )
    .use( T2 )
    .use( T2 )
    .foo();
// result: "T2T2T1T1C"
```

If the `override` keyword is used without `abstract`, then the super method
is statically bound to the supertype, rather than being resolved at runtime:

```javascript
var C = Class(
{
    'virtual foo': function()
    {
        return 'C';
    },
} );

var T1 = Trait.extend( C,
{
    'virtual abstract override foo': function()
    {
        return 'T1' + this.__super();
    },
} );

var T2 = Trait.extend( C,
{
    // static override
    'virtual override foo': function()
    {
        return 'T2' + this.__super();
    },
} );

C.use( T1 )
    .use( T1 )
    .use( T2 )
    .use( T2 )
    .foo();
// result: "T2C"
```

This latter form should be discouraged in most circumstances (as it prevents
stackable traits), but the behavior is consistent with the rest of the
system.

Happy hacking.
2015-10-24 23:53:23 -04:00
Mike Gerwitz db3ade378a
[copyright] Copyright update 2015-05-28 01:01:51 -04:00
Mike Gerwitz f3a8dea25d Test ensuring `#use` staging object satisfies `Class.isClass` 2015-05-27 23:38:07 -04:00
Mike Gerwitz 867127ed2f Trait class extension support
"Extending" a class C simply creates a contract stating that the trait may
only be mixed into something of type C (so, C or its subtypes).
2015-05-27 23:23:47 -04:00
Mike Gerwitz 96c5a702ce Abstract mixin initial implementation 2015-05-27 23:23:47 -04:00
Mike Gerwitz 991f4086fa
Remove Trait/ParameterTest ctor ordering comment (since resolved) 2015-05-06 22:32:55 -04:00
Mike Gerwitz e934338b41 Subtype ctor guarantees with parent __mixin or __construct
A solution for this problem took a disproportionally large amount of time,
attempting many different approaches, and arriving still at a kluge; this is
indicative of a larger issue---we've long since breached the comfort of the
original design, and drastic refactoring is needed.

I have ideas for this, and have already started in another branch, but I
cannot but this implementation off any longer while waiting for it.

Sorry for anyone waiting on the next release: this is what held it up, in
combination with my attention being directed elsewhere during that time (see
the sparse commit timestamps). Including this ordering guarantee is very
important for a stable, well-designed [trait] system.
2014-07-27 01:54:30 -04:00
Mike Gerwitz 90a32a104f __construct and __mixin ordering guarantees
See test cases for rationale.

I'm not satisfied with this implementation, but the current state of ease.js
makes this kind of thing awkward. Will revisit at some point.
2014-07-27 01:54:30 -04:00
Mike Gerwitz f3cb815baa Sibling traits will each have __mixin called distinctly 2014-07-27 01:54:30 -04:00
Mike Gerwitz 2204ff6d28 Parameterized traits may now be mixed in without configuration
Rationale is available in the test case.
2014-07-27 01:54:30 -04:00
Mike Gerwitz 3fc0f90e01 Initial implementation of parameterized traits
This is an important feature to permit trait reuse without excessive
subtyping---composition over inheritance. For example, consider that you
have a `HttpPlainAuth` trait that adds authentication support to some
transport layer. Without parameterized traits, you have two options:

  1. Expose setters for credentials
  2. Trait closure
  3. Extend the trait (not yet supported)

The first option seems like a simple solution:

```javascript
  Transport.use( HttpPlainAuth )()
    .setUser( 'username', 'password' )
    .send( ... );
```

But we are now in the unfortunate situation that our initialization
procedure has changed. This, for one, means that authentication logic must
be added to anything that instantiates classes that mix in `HttpPlainAuth`.
We'll explore that in more detail momentarily.

More concerning with this first method is that, not only have we prohibited
immutability, but we have also made our code reliant on *invocation order*;
`setUser` must be called before `send`. What if we have other traits mixed
in that have similar conventions? Normally, this is the type of problem that
would be solved with a builder, but would we want every configurable trait
to return a new `Transport` instance? All that on top of littering the
API---what a mess!

The second option is to store configuration data outside of the Trait acting
as a closure:

```javascript
  var _user, _pass;
  function setCredentials( user, pass ) { _user = user; _pass = pass; }
  Trait( 'HttpPlainAuth', { /* use _user and _pass */ } )
```

There are a number of problems with this; the most apparent is that, in this
case, the variables `_user` and `_pass` act in place of static fields---all
instances will share that data, and if the data is modified, it will affect
all instances; you are therefore relying on external state, and mutability
is forced upon you. You are also left with an awkward `setCredentials` call
that is disjoint from `HttpPlainAuth`.

The other notable issue arises if you did want to support instance-specific
credentials. You would have to use ease.js' internal identifiers (which is
undocumented and subject to change in future versions), and would likely
accumulate garbage data as mixin instances are deallocated, since ECMAScript
does not have destructor support.

To recover from memory leaks, you could instead create a trait generator:

```javascript
  function createHttpPlainAuth( user, pass )
  {
      return Trait( { /* ... */ } );
  }
```

This uses the same closure concept, but generates new traits at runtime.
This has various implications depending on your engine, and may thwart
future ease.js optimization attempts.

The third (which will be supported in the near future) is prohibitive: we'll
add many unnecessary traits that are a nightmare to develop and maintain.

Parameterized traits are similar in spirit to option three, but without
creating new traits each call: traits now support being passed configuration
data at the time of mixin that will be passed to every new instance:

```javascript
  Transport.use( HttpPlainAuth( user, pass ) )()
    .send( ... );
```

Notice now how the authentication configuration is isolated to the actual
mixin, *prior to* instantiation; the caller performing instantiation need
not be aware of this mixin, and so the construction logic can remain wholly
generic for all `Transport` types.

It further allows for a convenient means of providing useful, reusable
exports:

```javascript
  module.exports = {
      ServerFooAuth: HttpPlainAuth( userfoo, passfoo ),
      ServerBarAuth: HttpPlainAuth( userbar, passbar ),

      ServerFooTransport: Transport.use( module.exports.ServerFooAuth ),
      // ...
  };

  var module = require( 'foo' );

  // dynamic auth
  Transport.use( foo.ServerFooAuth )().send( ... );

  // or predefined classes
  module.ServerFooTransport().send( ... );
```

Note that, in all of the above cases, the initialization logic is
unchanged---the caller does not need to be aware of any authentication
mechanism, nor should the caller care of its existence.

So how do you create parameterized traits? You need only define a `__mixin`
method:

  Trait( 'HttpPlainAuth', { __mixin: function( user, pass ) { ... } } );

The method `__mixin` will be invoked upon instantiation of the class into
which a particular configuration of `HttpPlainAuth` is mixed into; it was
named differently from `__construct` to make clear that (a) traits cannot be
instantiated and (b) the constructor cannot be overridden by traits.

A configured parameterized trait is said to be an *argument trait*; each
argument trait's configuration is discrete, as was demonstrated by
`ServerFooAuth` and `ServerBarAuth` above. Once a parameterized trait is
configured, its arguments are stored within the argument trait and those
arguments are passed, by reference, to `__mixin`. Since any mixed in trait
can have its own `__mixin` method, this permits traits to have their own
initialization logic without the need for awkward overrides or explicit
method calls.
2014-07-27 01:54:30 -04:00
Mike Gerwitz 887d5ef0a3 GNU ease.js and test cases now compile in strict mode
During its initial development, no environments (e.g. Node.js, Chromium,
Firefox) supported strict mode; this has since changed, and node has a
--use-strict option, which is used in the test runner to ensure conformance.
2014-05-04 22:17:28 -04:00
Mike Gerwitz 779dbc37bc Corrected virtual non-overridden trait method invocations
What a mouthful. And nightmare, having been away from the trait
implementation for so long.
2014-05-02 21:08:41 -04:00
Mike Gerwitz 74b170d089 Moved class virtual param test into ClassVirtualTest case
VirtualTest was not the correct spot.
2014-05-02 20:13:41 -04:00
Mike Gerwitz 8c95932446 Trait virtual method proxies now set __length metadata
This fixes a bug that causesd virtual definitions with parameters on classes
that a trait is mixed into to fail, and prevented proper param length
validations in the reverse case.

See test case description for a less confusing description.
2014-05-02 00:19:25 -04:00
Mike Gerwitz fd404fc69f Named trait staging object
The syntax Trait( "Name" ) now works as Class( "Name" ), permitting
implementing interfaces and extending from the staged object.
2014-04-26 00:42:29 -04:00
Mike Gerwitz 82a02c0081 [copyright] Copyright assignment to the FSF
Thanks to Donald Robertson III for his help and guidance during this
process.
2014-04-09 19:05:07 -04:00
Mike Gerwitz cc43f4b339 Prohibiting trait getters/setters 2014-03-15 21:16:27 -04:00
Mike Gerwitz eab4dbfe4d Throwing exception on trait static member
These will be supported in future versions; this is not something that I
want to rush, nor is it something I want to hold up the first GNU release;
it is likely to be a much lesser-used feature.
2014-03-15 21:16:27 -04:00
Mike Gerwitz 255a60e425 Implemented and abstract with mixins properly handled
Classes will now properly be recognized as concrete or abstract when mixing
in any number of traits, optionally in conjunction with interfaces.
2014-03-15 21:16:27 -04:00
Mike Gerwitz 696b8d05a6 Class definition mixin now requires explicit extend
See the rather verbose docblocks in this diff for more information.
Additional rationale will be contained in the commits that follow.
2014-03-15 21:16:27 -04:00
Mike Gerwitz 55e993a74d Non-private properties now expressly prohibited in trait dfns
:O What?! See Trait/PropertyTest for more information on why this is the
case, at least for now.
2014-03-15 21:16:27 -04:00
Mike Gerwitz 3005cda543 Support for stacked mixins
The concept of stacked traits already existed in previous commits, but until
now, mixins could not be stacked without some ugly errors. This also allows
mixins to be stacked atop of themselves, duplicating their effect. This
would naturally have limited use, but it's there.

This differs slightly from Scala. For example, consider this ease.js mixin:

  C.use( T ).use( T )()

This is perfectly valid---it has the effect of stacking T twice. In reality,
ease.js is doing this:

  - C' = C.use( T );
  - new C'.use( T );

That is, it each call to `use' creates another class with T mixed in.

Scala, on the other hand, complains in this situation:

  new C with T with T

will produce an error stating that "trait T is inherited twice". You can
work around this, however, by doing this:

  class Ca extends T
  new Ca with T

In fact, this is precisely what ease.js is doing, as mentioned above; the
"use.use" syntax is merely shorthand for this:

  new C.use( T ).extend( {} ).use( T )

Just keep that in mind.
2014-03-15 21:16:27 -04:00
Mike Gerwitz 88713987e2 Mixin use method calls can now be chained
Syntatic sugar; could have previously extended explicitly and then mixed in.
2014-03-15 21:16:27 -04:00
Mike Gerwitz 8480d8f92c Added support for abstract overrides 2014-03-15 21:16:27 -04:00
Mike Gerwitz 14bd552361 Trait can now implement interfaces
Note the incomplete test case: the next commit will introduce the ability
for mixins to override methods that may have already been defined.
2014-03-15 21:16:27 -04:00
Mike Gerwitz c8023cb382 Trait method return value implementation correction and testing 2014-03-15 21:16:27 -04:00
Mike Gerwitz 6473cf35ae Began Scala-influenced linearization implementation
More information on this implementation and the rationale behind it will
appear in the manual. See future commits.

(Note the TODOs; return values aren't quite right here, but that will be
handled in the next commit.)
2014-03-15 21:16:27 -04:00
Mike Gerwitz 8d81373ef8 Began named trait implementation
Does not yet support staging object like classes
2014-03-15 21:16:27 -04:00
Mike Gerwitz 26bd6b88dd Named classes now support mixins 2014-03-15 21:16:27 -04:00
Mike Gerwitz 455d3a5815 Added immediate partial class invocation support after mixin 2014-03-15 21:16:27 -04:00
Mike Gerwitz 897a4afab4 Added support for mixing in traits using use method on a base class 2014-03-15 21:16:27 -04:00
Mike Gerwitz 999c10c3bf Subtype mixin support 2014-03-15 21:16:27 -04:00
Mike Gerwitz 451ec48a5c Objects are now considered types of class's mixed in traits
This is a consequence of ease.js' careful trait implementation that ensures
that any mixed in trait retains its API in the same manner that interfaces
and supertypes do.
2014-03-15 21:16:27 -04:00
Mike Gerwitz ee46fc2182 Began testing class subtyping with mixins 2014-03-15 21:16:27 -04:00
Mike Gerwitz 66cab74cc1 Initial trait virtual member proxy implementation
This has some flaws that should be addressed, but those will be detailed in
later commits; this works for now.
2014-03-15 21:16:26 -04:00
Mike Gerwitz b7a314753a Began implementing virtual trait methods
These require special treatment with proxying.
2014-03-15 21:16:26 -04:00
Mike Gerwitz b04a8473b8 Implemented abstract traits
This is just an initial implementation, so there may be some quirks; there
are more tests to come.
2014-03-07 00:47:43 -05:00