Skip to content

Lua table#516

Merged
Perryvw merged 35 commits intoTypeScriptToLua:masterfrom
hazzard993:lua-table
May 4, 2019
Merged

Lua table#516
Perryvw merged 35 commits intoTypeScriptToLua:masterfrom
hazzard993:lua-table

Conversation

@hazzard993
Copy link
Copy Markdown
Contributor

For #346

@LuaTable can be used to mark a Map-like object to transpile down to a simple Lua table.

/** @LuaTable */
declare class Table<K extends {} = {}, V = any> {
    readonly length: number;
    set(key: K, value: V): void;
    get(key: K): V;
}

const tbl = new Table(); // local tbl = {}

const foo = {};
tbl.set(foo, "bar"); // tbl[foo] = "bar"
print(tbl.get(foo)); // print(tbl[foo])

tbl.set(1, "baz"); // tbl[1] = "baz"
print(tbl.length); // print(#tbl)

get, set and length are the only properties the transpiler pays special attention to. Other properties are treated the same as any other property.

I did make a decision to disallow tbl.set(...) to be used in place of an expression since it never returns a value and transpiles to an ExpressionStatement, not an Expression.

This could help with reducing the amount of Lua outputted through its use in LuaLibs and other source code.

Copy link
Copy Markdown
Collaborator

@tomblind tomblind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Need to make sure other classes don't extend LuaTables
  • Need to make sure non-declared classes don't use this as it results in unexpected behavior

@hazzard993
Copy link
Copy Markdown
Contributor Author

  • If a class is declared with the LuaTable directive without declare modifier the transpiler will error
  • Classes extending a LuaTable will cause the transpiler to error
  • set() takes two parameters, get() takes one

Also made the length property not re-assignable to avoid extra code gen

@tomblind
Copy link
Copy Markdown
Collaborator

One more issue I see is explicit constructors. I can declare a constructor for the class, but it's basically ignored. We should throw an error or do something with the arguments.

Also, it would be good to have tests for the directive when used on an interface instead of a class.

As for assigning length, we now support that for array types with a lib function __TS_ArraySetLength. This could be used here as well.

@hazzard993
Copy link
Copy Markdown
Contributor Author

Now if a LuaTable is constructed with arguments the transpiler will throw an error saying no parameters are allowed.

I chose not to use __TS_ArraySetLength since it seems like the goal of a LuaTable object is to reduce code gen.

Copy link
Copy Markdown
Collaborator

@tomblind tomblind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good - but I thought of one more edge-case 😄 We should probably dis-allow using instanceof on @luaTable objects since it will give incorrect results.

Copy link
Copy Markdown
Member

@Perryvw Perryvw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing functional test(s) for table get/set.

"One parameter is required for get() on a '@LuaTable' object."
);
}
if (isWithinExpressionStatement) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I understand the reasoning here. If you have a table.get(abc) statement it is transformed to ____ = table.get(abc)? That sounds like asking for all kinds of strange bugs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since table.get(abc) transforms to table[abc] it becomes an invalid expression in Lua. In this case I chose to then assign this to an anonymous variable the same way x; is transformed to local ____ = x;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like a more fundamental issue. Table.get(abc) is an expression but you are returning a statement. This is related to my other comment on the function signature, I think it might automatically be resolved if you separate the assignment and expression functions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've separated this logic into three functions atm to keep this logic more consistent. I've grouped the error checking in validateLuaTableCall which takes isWithinExpressionStatement for validation purposes, transformLuaTableExpressionStatement and transformLuaTableExpression.

Would this be something more appropriate?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems okay, these methods should probably be private instead of public though.

}

// LuaTable classes must be declared
if (decorators.has(DecoratorKind.LuaTable) && !tsHelper.isDeclared(statement)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class may considered declared because of it's context

Suggested change
if (decorators.has(DecoratorKind.LuaTable) && !tsHelper.isDeclared(statement)) {
if (decorators.has(DecoratorKind.LuaTable) && (ts.getCombinedModifierFlags(statement) & ts.ModifierFlags.Ambient) === 0) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to test this but good pickup


switch (methodName) {
case "get":
if (expression.arguments.length !== 1) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also should check that it's argument isn't a SpreadElement

if (expression.arguments.length !== 1) {
throw TSTLErrors.ForbiddenLuaTableUseException(
expression,
"One parameter is required for get() on a '@LuaTable' object."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"One parameter is required for get() on a '@LuaTable' object."
"One parameter is required for get() on a '@luaTable' object."

if (params.length !== 2) {
throw TSTLErrors.ForbiddenLuaTableUseException(
expression,
"Two parameters are required for set() on a '@LuaTable' object."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Two parameters are required for set() on a '@LuaTable' object."
"Two parameters are required for set() on a '@luaTable' object."

new TranspileError(`Iterating over arrays with 'for ... in' is not allowed.`, node);

public static ForbiddenLuaTableSetExpression = (node: ts.Node) => new TranspileError(
`A '@LuaTable' object's 'set()' method can only be used as a Statement, not an Expression.`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`A '@LuaTable' object's 'set()' method can only be used as a Statement, not an Expression.`,
`A '@luaTable' object's 'set()' method can only be used as a Statement, not an Expression.`,

@Perryvw Perryvw merged commit 3de430d into TypeScriptToLua:master May 4, 2019
@hazzard993 hazzard993 deleted the lua-table branch May 4, 2019 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants