Skip to content

Unresolved const type causing assertion failure #1165

Closed
@DuncanUszkay1

Description

@DuncanUszkay1

When you create a constant with an non-existent type, and refer to it in another constant, you get an assertion failed error. The compilation would fail either way of course, but since the assertion failure provides no additional information without inspecting the source it's a bit of a hassle to debug. In our case it was just a typo in the type name.

Here's a minimal example:

const INNER: fake = 1;
const OUTER: i32 = INNER;

Output upon compilation:

ERROR: AssertionError: assertion failed
    at assert (/Users/duncanuszkay/src/github.com/Shopify/as-fork/assemblyscript/std/portable/index.js:184:9)
    at Compiler.compileIdentifierExpression (/Users/duncanuszkay/src/github.com/Shopify/as-fork/assemblyscript/src/compiler.ts:7631:9)
    at Compiler.compileExpression (/Users/duncanuszkay/src/github.com/Shopify/as-fork/assemblyscript/src/compiler.ts:3153:21)
    at Compiler.compileGlobal (/Users/duncanuszkay/src/github.com/Shopify/as-fork/assemblyscript/src/compiler.ts:940:25)
    at Compiler.compileTopLevelStatement (/Users/duncanuszkay/src/github.com/Shopify/as-fork/assemblyscript/src/compiler.ts:1748:20)
    at Compiler.compileFile (/Users/duncanuszkay/src/github.com/Shopify/as-fork/assemblyscript/src/compiler.ts:804:12)
    at Compiler.compile (/Users/duncanuszkay/src/github.com/Shopify/as-fork/assemblyscript/src/compiler.ts:406:14)
    at Object.compile (/Users/duncanuszkay/src/github.com/Shopify/as-fork/assemblyscript/src/index.ts:190:32)
    at /Users/duncanuszkay/src/github.com/Shopify/as-fork/assemblyscript/cli/asc.js:577:31
    at measure (/Users/duncanuszkay/src/github.com/Shopify/as-fork/assemblyscript/cli/asc.js:1068:3)

Did a little bit of digging in the source and found out what's going on:
We're failing an assertion in the compilation stage because it thinks that the type of INNER is void

// in compiler.ts
// when compiling the INNER identifier used by OUTER
if (!this.compileGlobal(<Global>target)) { // reports; not yet compiled if a static   field
  return module.unreachable();
}
let type = (<Global>target).type;
assert(type != Type.void); //This assertion fails because the type is void

The type ends up being void because of the behaviour of the compileGlobal function:

compileGlobal(global: Global): bool {
    console.log(global.name)
    if (global.is(CommonFlags.COMPILED)) return true;
    global.set(CommonFlags.COMPILED);
    assert(global.type == Type.void); //The default type is void

    var module = this.module;
    var initExpr: ExpressionRef = 0;
    var typeNode = global.typeNode;
    var initializerNode = global.initializerNode;

    if (!global.is(CommonFlags.RESOLVED)) {

      // Resolve type if annotated
      if (typeNode) {
        let resolvedType = this.resolver.resolveType(typeNode, global.parent); // reports
        if (!resolvedType) return false; //We get here, fail to resolve the type, and return before setting the type

The false value is then propagated to this statement and is ignored:

case NodeKind.VARIABLE: {
        let declarations = (<VariableStatement>statement).declarations;
        for (let i = 0, k = declarations.length; i < k; ++i) {
          let element = this.program.getElementByDeclaration(declarations[i]);
          if (element) {
            assert(element.kind == ElementKind.GLOBAL);
            if (
              !element.is(CommonFlags.AMBIENT) && // delay imports
              !element.hasDecorator(DecoratorFlags.LAZY)
            ) this.compileGlobal(<Global>element); //Here we get false back, and ignore it
          }
        }
        break;
      }

So we're left with a global with type void despite it actually being of an unresolved type.

I have some questions before looking into this further:

  • Why doesn't compilation fail when we fail to resolve the type of the global? Are we assuming that the type will be resolved at some point in the future? And if that is a real scenario should we use a special type to represent being not-resolved-yet rather than reusing void?
  • What is the point of asserting against the void type in compileIdentifierStatement? Wouldn't it just safely fail with a type mismatch later? Here's the output without this assertion for this case:
ERROR TS2304: Cannot find name 'fake'.

 const INNER: fake = 1;
              ~~~~
 in src/foo.ts(1,13)

ERROR TS2322: Type 'void' is not assignable to type 'i32'.

 const OUTER: i32 = INNER;
                    ~~~~~
 in src/foo.ts(2,19)

What makes this output nice is that the misspelling is presented in the error output so the user could figure out what they did wrong. The second error is maybe not so clear since INNER being resolved as a void doesn't make any sense to the script author, but it's much better than the current output.

Am I misunderstanding something here?

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions