Login
800-000-exception.s2 at [e456c5ab39]
Login

File bindings/s2/unit/800-000-exception.s2 artifact 0cb820e714 part of check-in e456c5ab39


const oldStackTrace = pragma(exception-stacktrace true);
/**
   Exceptions are implicitly tested throughout the earlier unit tests,
   but on 20191228 a new trick was discovered which required making a
   minor backwards-incompatible change (albeit a fix) and prompted the
   addition of a new feature, so...
*/

scope {
    /* 20191228: if the exception keyword is followed by anything
       other than a '(', it resolves to the exception prototype. That
       makes it tempting to add similar keywords for "object" and
       "array" and such, but that's a deep, dark rabbit hole.

       This feature was added primarily as an inexpensive way to get
       at the exception prototype from script code, noting that
       exception(0).prototype captures the exception's source code
       location and stack trace, so is not cheap.
    */
    const e = exception(0);
    assert e.prototype === exception;
    assert e.codeString === exception.codeString;
    assert e.prototype === eval{exception} /* "virtual EOF" check */;
}

scope {
    // Custom exception type.
    const MyEx = {
        prototype: exception /* for inherited exception method(s) */,
        __typename: 'MyEx', // optional
        __new: proc(msg = __FLC){ // constructor
            /* Some prototype gymnastics are needed... */
            const p = this.prototype /* === MyEx */,
                  x = this.prototype = exception(msg)
            /* captures stack trace ^^^^, but now "this"
               no longer inherits MyEx, so... */;
            x.prototype = p
            /* ^^^^ without this part, "this" will inherit exception but NOT
               MyEx. */;
            /**
               Optional (but arguable) cosmetic improvement:
               x.line/column/script refer to this constructor function,
               which may be a bit confusing in practice. We can instead
               "steal" those from one level up in the stack trace (which
               may, of course, also be "differently confusing" in practice)...
            */
            if(const s = x.stackTrace.0){
                x.line = s.line;
                x.column = s.column;
                x.script = s.script;
            }
        }
    };

    assert undefined === MyEx.line;

    const check = proc(x){
        assert x inherits MyEx;
        assert !typeinfo(isexception x) /* because x is not "directly" an exception, but: */;
        assert typeinfo(hasexception x) /* because x inherits an exception */;
        assert "MyEx" === typeinfo(name x);
        assert typeinfo(isarray x.stackTrace) /* actually, it's inherited: */;
        assert x.prototype.stackTrace === x.stackTrace;
        //print(__FLC,'x.stackTrace =',x.stackTrace);
        assert x.stackTrace.0.script === __FILE;
        assert "blah!" === x.message /* derived from x.prototype */;
        assert typeinfo(isinteger x.code) /* derived from x.prototype. */;
        assert typeinfo(isinteger x.line) /* derived from x.prototype. */;
        //print(__FLC,thisLine, x.line);
        assert x.line > thisLine /* because of our finagling in the MyEx ctor */;
        assert typeinfo(isinteger x.column) /* derived from x.prototype. */;
        assert 'CWAL_RC_EXCEPTION' === x.codeString();
        assert !x.hasOwnProperty('script');
        assert x.script === __FILE;
        //print(__FLC, x.prototype);
    } using {
        thisLine: __LINE
    };

    // And now...
    check(catch throw new MyEx("blah!"));
    check(new MyEx("blah!"));
}

scope {
    // Make sure that script-location info is captured for this case...
    const e = catch proc(){throw "hi"}()
                 /* ^^^ need a level of indirection to get the
                    stackTrace property. stackTrace is not included if
                    it's only 1 level deep because it would simply
                    duplicate the line/col/script position set in the
                    other properties.
                 */;
    assert typeinfo(isinteger e.line);
    assert typeinfo(isinteger e.column);
    assert typeinfo(isarray e.stackTrace);
    assert e.stackTrace.0.script === __FILE;
    assert typeinfo(isstring e.script);
    assert "hi" === e.message;
    /**
       There's another use case we can't(?) test from here: if a
       cwal_exception is created in native code and passed to
       s2_throw_value(), it "should" also get script info if a script
       is currently active. Testing that requires adding a native
       function binding solely to test it with, so we'll punt on that
       very hypothetical problem for now. Native-side exceptions which
       pass through a script-side function call get decorated with
       that info if they don't already have it.
    */
}

scope {
    /* Test that s2_cstr_to_rc() is working, i.e. that both implementations
       of the hashing routine produce identical results. */
    foreach(@[
        // 'CWAL_RC_OK', this won't work work in this context
        'CWAL_RC_ERROR',
        // 'CWAL_RC_OOM', // see below!
        'CWAL_RC_FATAL',
        'CWAL_RC_CONTINUE',
        'CWAL_RC_BREAK',
        'CWAL_RC_RETURN',
        'CWAL_RC_EXIT',
        'CWAL_RC_EXCEPTION',
        'CWAL_RC_ASSERT',
        'CWAL_RC_MISUSE',
        'CWAL_RC_NOT_FOUND',
        'CWAL_RC_ALREADY_EXISTS',
        'CWAL_RC_RANGE',
        'CWAL_RC_TYPE',
        'CWAL_RC_UNSUPPORTED',
        'CWAL_RC_ACCESS',
        'CWAL_RC_IS_VISITING',
        'CWAL_RC_IS_VISITING_LIST',
        'CWAL_RC_DISALLOW_NEW_PROPERTIES',
        'CWAL_RC_DISALLOW_PROP_SET',
        'CWAL_RC_DISALLOW_PROTOTYPE_SET',
        'CWAL_RC_CONST_VIOLATION',
        'CWAL_RC_LOCKED',
        'CWAL_RC_CYCLES_DETECTED',
        'CWAL_RC_DESTRUCTION_RUNNING',
        'CWAL_RC_FINALIZED',
        'CWAL_RC_HAS_REFERENCES',
        'CWAL_RC_INTERRUPTED',
        'CWAL_RC_CANCELLED',
        'CWAL_RC_IO',
        'CWAL_RC_CANNOT_HAPPEN',
        'CWAL_RC_JSON_INVALID_CHAR',
        'CWAL_RC_JSON_INVALID_KEYWORD',
        'CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE',
        'CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE',
        'CWAL_RC_JSON_INVALID_NUMBER',
        'CWAL_RC_JSON_NESTING_DEPTH_REACHED',
        'CWAL_RC_JSON_UNBALANCED_COLLECTION',
        'CWAL_RC_JSON_EXPECTED_KEY',
        'CWAL_RC_JSON_EXPECTED_COLON',
        'CWAL_SCR_CANNOT_CONSUME',
        'CWAL_SCR_INVALID_OP',
        'CWAL_SCR_UNKNOWN_IDENTIFIER',
        'CWAL_SCR_CALL_OF_NON_FUNCTION',
        'CWAL_SCR_MISMATCHED_BRACE',
        'CWAL_SCR_MISSING_SEPARATOR',
        'CWAL_SCR_UNEXPECTED_TOKEN',
        'CWAL_SCR_UNEXPECTED_EOF',
        'CWAL_SCR_DIV_BY_ZERO',
        'CWAL_SCR_SYNTAX',
        'CWAL_SCR_EOF',
        'CWAL_SCR_TOO_MANY_ARGUMENTS',
        'CWAL_SCR_EXPECTING_IDENTIFIER',
        'S2_RC_END_EACH_ITERATION',
        'S2_RC_TOSS'
    ]=>k){
        if(exception(k,0).codeString()!==k)
            throw "codeString check failed for "+k;
        assert 1 /* to count the above check */;
    }
    affirm 'CWAL_RC_EXCEPTION' === exception('CWAL_RC_OOM',1).codeString()
    /* this gets changed to CWAL_RC_EXCEPTION to avoid an erroneous OOM! */;

}

pragma(exception-stacktrace oldStackTrace);