Skip to content

Class/interface field with an object-literal default (foo = {}) crashes: "this.getLookupTable is not a function" #17

Description

@markwpearce

Summary

With brighterscript-jsdocs-plugin@1.0.0-alpha.1 (paired with brighterscript@1.0.0-alpha.52), any class or interface field whose default value is an object literal ({}) crashes the whole file's doc generation — regardless of whether the field also has an explicit type annotation.

Repro

namespace BGE
  class Foo
    colliders as roAssociativeArray = {}
  end class
end namespace
TypeError: this.getLookupTable is not a function
    at BuiltInInterfaceAdder.addBuiltInInterfacesToType (.../brighterscript/dist/types/BuiltInInterfaceAdder.js:53:46)
    at AssociativeArrayType.addBuiltInInterfaces (.../brighterscript/dist/types/BscType.js:150:59)
    at AALiteralExpression.getType (.../brighterscript/dist/parser/Expression.js:998:20)
    at FieldStatement.getType (.../brighterscript/dist/parser/Statement.js:2373:97)
    at processClassField (.../brighterscript-jsdocs-plugin/convert-brighterscript-docs.js:379:47)

An array-literal default (items = []) does not crash — only object/associative-array literals.

Root cause

processClassField/processInterfaceField now call field.getType(typeGetOptions) to compute the @property type (a nice improvement over the old plain CustomType check!). FieldStatement.getType() unconditionally evaluates this.initialValue?.getType(...) first — even when an explicit typeExpression is present and takes precedence in the final return — so an initializer of {} always runs through AALiteralExpression.getType().

That method does:

const resultType = new AssociativeArrayType();
resultType.addBuiltInInterfaces();

which calls into BuiltInInterfaceAdder.addBuiltInInterfacesToType, which for roAssociativeArray (a component with interfaces) hits this unguarded line:

const ifaceType = this.getLookupTable()?.getSymbolType(...)

getLookupTable is a static hook normally installed by a full Program/Scope — since this plugin only lexes+parses a single file (no Program), it's never set. Note BuiltInInterfaceAdder already guards this exact check elsewhere in the same file (if (!this.getLookupTable) { ... } a few lines down) — this one call site is just missing the same guard. So this may really be a rokucommunity/brighterscript bug rather than one in this plugin, surfaced by this plugin's single-file (no-Program) usage pattern — but filing here first since I'm not sure which side should own the fix (worth a fast-follow issue against brighterscript too if so).

Impact

This isn't a rare pattern — foo as SomeType = {} / foo = {} field initializers are extremely common (used all over our own engine: Game.bs, GameEntity.bs, Drawable.bs, etc.), so this currently blocks doc generation entirely for any codebase that uses that style.

Suggested fix

Either:

  1. In brighterscript, add the same if (!this.getLookupTable) return; guard to the interfacesToLoop branch of BuiltInInterfaceAdder.addBuiltInInterfacesToType that already exists a few lines later in the same file, or
  2. In this plugin, avoid triggering full getType() evaluation of the initializer when an explicit typeExpression is present (or wrap the field.getType(...) call defensively).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions