Skip to content

@forRange directive#626

Merged
tomblind merged 2 commits intomasterfrom
feature/forrange
Jun 13, 2019
Merged

@forRange directive#626
tomblind merged 2 commits intomasterfrom
feature/forrange

Conversation

@tomblind
Copy link
Copy Markdown
Collaborator

@tomblind tomblind commented Jun 6, 2019

This new directive is used to create lua numeric for loops by using an ambient function declaration which can be used in TS for...of loops.

/** @forRange **/
declare function luaForRange(start: number, limit: number, step?: number): Iterable<number>;

for (const i of luaForRange(1, 10, 2)) {
    console.log(i);
}

=>

for i = 1, 10, 2 do
    print(i)
end

An error will be thrown if the function:

  • doesn't take 2-3 number arguments, or it returns something other than a number iterable/array
  • doesn't declare a single control variable
  • is non-ambient
  • is referenced outside of a for...of loop

@hazzard993
Copy link
Copy Markdown
Contributor

This is looking pretty solid. I have got something picky that is kinda 🤷‍

Thought about it after LuaTables

When declaring things like this an identifier is revealed to TypeScript that doesn't actually exist. This identifier can be used almost everywhere.

console.log(luaForRange);
luaForRange.call(null, 0, 0, 0);
let array = [0, luaForRange, 1];
const call = cb => cb();
call(luaForRange);

If this is something that is considered a problem I'm thinking a functional solution could be to iterate over all identifiers in each source file.

Each identifier in a source file would have their parents' kinds and directives checked to make sure identifiers revealing forRange functions are being used in call expressions on the right hand side of for..of statements. I am not entirely sure how to iterate through every node, but it might look something like the below code:

// TSHelper
validateNode(checker: ts.Checker, node: Node) {
    if (node.kind === ts.SyntaxKind.Identifier) {
        const type = checker.getTypeAtLocation(node);
        if (this.getCustomDecorators(type).has(DecoratorKind.ForRange)) {
            // further checks for a valid ForRange use
        }
    }
    ts.forEachChild(node, this.validateNode);
}

// LuaTransformer
transformSourceFile(node: ts.SourceFile) {
    ...
    ts.forEachChild(node, tsHelper.validateNode.bind(tsHelper, this.checker));
}

@tomblind
Copy link
Copy Markdown
Collaborator Author

tomblind commented Jun 7, 2019

While all directives have situations where they can be broken (they fall into the "use at your own risk" category), you're right that these cases can be caught easily enough. We actively try to avoid "scanning" the AST, but I was able to add a general check in transformIdentifier to catch all of the illegal references. This also had the side effect of preventing non-ambient declarations since those counted as an illegal reference. We might want to try doing something like this as well with @luaTable.

@tomblind tomblind requested a review from Perryvw June 12, 2019 22:57
@tomblind tomblind merged commit a940c71 into master Jun 13, 2019
@tomblind tomblind deleted the feature/forrange branch June 13, 2019 20:39
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.

3 participants