Skip to content
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

Ternary operator optimization RELIES on types, and can cause runtime errors #1569

Closed
hepiyellow opened this issue Aug 7, 2024 · 8 comments
Closed

Comments

@hepiyellow
Copy link

hepiyellow commented Aug 7, 2024

Consider:

const someObj = {}
const x = true ? someObj.nonExistingKey : 5

This gets a ts7053 error since someObj has no property of name someObj.nonExistingKey.

But if we add a type to someObj that says it does have nonExistingKey, like so

const someObj = {}  as { nonExistingKey: number }
const x = true ? someObj.nonExistingKey : 5

Then the Ternary operator optimization will transpile it to:

local x = true and someObj.nonExistingKey or 5

Which causes x to have the value 5 instead of undefined

Instead, the code should be keeping non-optimized:

local ____temp_0
if true then
    ____temp_0 = someObj.nonExistingKey
else
    ____temp_0 = 5
end
local x = ____temp_0

Type Assertion is legitimate typing tool when writing functions that generically handle different data types.

I suggest this optimization should be disabled.

@Z3rio
Copy link
Contributor

Z3rio commented Aug 7, 2024

(nevermind, I misunderstood the issue)

@Perryvw
Copy link
Member

Perryvw commented Aug 9, 2024

I disagree here. You are giving an incorrect type to your object here. If it is ever possible that this key is not here, the object should be typed as { nonExistingKey?: number }, which will result in the long-form version.

In general we will always err on the side of caution and use the non-optimized version, but if you tell the compiler for sure the type of this thing is this, and at runtime it is something else, the problem is in your code, and not in TSTL.

@Perryvw Perryvw closed this as not planned Won't fix, can't repro, duplicate, stale Aug 9, 2024
@hepiyellow
Copy link
Author

If we are to accept your approach, then it should be stated clearly in TSTL docs that using Type Assertion ( my_variable as ...) is NOT SUPPORTED or MAY LEAD TO RUN-TIME ERRORS!

I remind you that:
"Type assertions are a way to tell the TypeScript compiler that you know the type of a value better than it does. They are a form of type casting, but unlike other languages, they have no runtime impact in TypeScript. They are purely a compile-time construct."

A developer using TSTL - MUST ACKNOWLEDGE that Type Assertion may have runtime effects.

I just want to remind here , that in the case I presented above, the runtime error was not due to the "wrong" type being asserted on someObj, meaning, not due to the fact that nonExistingKey was undefined, but rather from an apparently unrelated optimization. If this code was transpired to Javascript this wouldn't cause an error.

@KooperNinja
Copy link

KooperNinja commented Aug 10, 2024

So you would want x to be undefined in this case even though you asserted the type of nonExistingKey to always be a number and not undefined.
As @Perryvw mentioned this does not make sense.
You should not assert the type of number if the value in runtime could be undefined.
That defeats the purpose of typings in the first place.

@Zamiell
Copy link
Contributor

Zamiell commented Aug 10, 2024

from a high level, it kind of makes sense that the Lua output would depend on what the types are.

take a simple example:

  • in typescript, we have [] for arrays and {} for objects.
  • in lua, we have {} for both arrays and objects.

thus, the compiler would have to use the types in order to create coherent output.
if you think the docs need to be improved, submit a PR to this repo: https://github.com/TypeScriptToLua/TypeScriptToLua.github.io

@hepiyellow
Copy link
Author

@Zamiell

In the Array vs Object case, it is a matter of correctness.
In the Ternary case, it's a matter of optimization.

My approach would be to keep to a minimum the dependency of the transpiling on the types, and thus, like TypeScript, the developer can be SURE that changing types will not have any run-time implications.

I cases like Arrays vs Objects, it might be a necessity. But optimizations are not necessary.

@hepiyellow
Copy link
Author

@Zamiell

It is kind of hard to document this behavior, Does this sound right?

Avoid Type Assertions

"Due to some optimizations which depend on type correctness, you should keep in mind that using Type Assertions may have an effect on transpiled code, and can in some cause unexpected behavior. We recommend that you don't use Type Assertions"

You may even want to prevent Type Assertions completely, by using ESLint with a rule such as:

{
  "rules": {
    "@typescript-eslint/consistent-type-assertions": ["error", {
      "assertionStyle": "never"
    }]
  }
}

@hepiyellow
Copy link
Author

@Zamiell
I created this PR

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

No branches or pull requests

5 participants