luau-lang / luau

A fast, small, safe, gradually typed embeddable scripting language derived from Lua
https://luau-lang.org
MIT License
3.8k stars 352 forks source link

Nested tagged unions do not work as I want them to work #1188

Open 0x13479 opened 4 months ago

0x13479 commented 4 months ago

type CircularEntity = {
    shape: 'Circular',
    only_for_circular: any,
}

type RectangularEntity = {
    shape: 'Rectangular',
}

type Entity = CircularEntity | RectangularEntity

local entity: Entity = {} :: any

if entity.shape == 'Circular' then
    print(entity.only_for_circular) -- OK ✅
end
if entity.shape == 'Rectangular' then
    print(entity.only_for_circular) -- Warning ✅
end

in the above case , tagged unions work as expected . in the implementation below , they do not :


type CircularEntity = {
    parameters: {
        shape: 'Circular',
        only_for_circular_inside: any,
    },
    only_for_circular: any,
}

type RectangularEntity = {
    parameters: {
        shape: 'Rectangular',
    },
}

type Entity = CircularEntity | RectangularEntity

local entity: Entity = {} :: any

if entity.parameters.shape == 'Circular' then
    print(entity.only_for_circular) -- Warning ❌
    print(entity.parameters.only_for_circular_inside) -- OK ✅
end
if entity.parameters.shape == 'Rectangular' then
    print(entity.only_for_circular) -- Warning ✅
    print(entity.parameters.only) -- Warning ✅
end

here is my ideal use case :


type DamageModel = ModelCommon & {
    modifier: {
        Value: 'Damage',
    },
    damage_part: Part,
}

type HealModel = ModelCommon & {
    modifier: {
        Value: 'Heal',
    },
}

type ModelCommon = Model & {
    modifier: StringValue,
}

type MyModel = DamageModel | HealModel

local model: MyModel = {} :: any
if model.modifier.Value == 'Damage' then
    print(model.damage_part) -- Warning
end

i have to do this since there is no typechecking for attributes , perhaps if there is a way to accomplish this , i'll be thankful if you let me know .

0x13479 commented 4 months ago

for attributes you could make it work like this :


type HealingButton = ButtonCommon & {
    GetAttribute:
        ((ButtonCommon, "Class") -> ('Healing')),
    health: IntValue,
}

type DamageButton = ButtonCommon & {
    GetAttribute:
        ((ButtonCommon, "Class") -> ('Damage')),
    damage: IntValue,
}

type ButtonCommon = Model & {}

type Button =
    HealingButton |
    DamageButton

local button: Button = {} :: any

-- Type Error: (24,4) Cannot call non-function (((Instance, string) -> any) & ((Model, "Class") -> "Damage")) | (((Instance, string) -> any) & ((Model, "Class") -> "Healing"))
if button:GetAttribute("Class") == 'Damage' then
    -- Type Error: (26,8) Key 'damage' is missing from 'Model & {|  |} & {| GetAttribute: (Model, "Class") -> "Healing", ... 1 more ... |}' in the type '(Model & {|  |} & {| GetAttribute: (Model, "Class") -> "Damage", ... 1 more ... |}) | (Model & {|  |} & {| GetAttribute: (Model, "Class") -> "Healing", ... 1 more ... |})'
    print(button.damage)
end