Skip to content

Unresolved const type causing assertion failure #1165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
DuncanUszkay1 opened this issue Mar 14, 2020 · 1 comment · Fixed by #1321
Closed

Unresolved const type causing assertion failure #1165

DuncanUszkay1 opened this issue Mar 14, 2020 · 1 comment · Fixed by #1321
Assignees

Comments

@DuncanUszkay1
Copy link
Contributor

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?

@willemneal
Copy link
Contributor

If a type isn't found shouldn't it be an any type?

This is the
Screen Shot 2020-03-13 at 11 19 19 PM

Also it would be nice edit each assertion to report the range of the error and a simple message. Perhaps I'll take a crack at as I'm sure it'll be useful in the long term and it'll help me understand all the weird edge cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants