Similar bug report: Tagged unions do not work when directly applied as an intersection type
Example of tagged unions:
As shown in the documentation:
type Ok<T> = { type: "ok", value: T }
type Err<E> = { type: "err", error: E }
type Result<T, E> = Ok<T> | Err<E>
if result.type == "ok" then
-- result is known to be Ok<T>
-- and attempting to index for error here will fail
print(result.value)
elseif result.type == "err" then
-- result is known to be Err<E>
-- and attempting to index for value here will fail
print(result.error)
end
Example of a tagged union and intersection:
--!strict
type TaggedUnionAndIntersection = ({
tag: true,
valueA: number,
} | {
tag: false,
valueB: string,
}) & {
intersectionValue: string,
}
-- Works as expected
local variable: TaggedUnionAndIntersection = {
tag = true,
valueA = 0,
intersectionValue = "",
}
-- Works as expected
local function func(parameter: TaggedUnionAndIntersection)
if parameter.tag == true then
print(parameter.valueA)
else
print(parameter.valueB)
end
print(parameter.intersectionValue)
end
Using a nested union type within a tagged union raises a Type Error with --!strict type inference (tested on old type solver and new type solver):
--!strict
type NestedUnionType1 = "Option1" | "Option2"
type NestedUnionType2 = "OptionA" | "OptionB"
type TaggedUnionAndIntersection = ({
tag: "A",
valueA: NestedUnionType1,
} | {
tag: "B",
valueB: NestedUnionType2,
}) & {
intersectionValue: string,
}
-- Type Error
local variable: TaggedUnionAndIntersection = {
tag = "A",
valueA = "Option1", -- `valueA` does not autofill
intersectionValue = "",
}
Type Error: Expected this to be '({ tag: "A", valueA: "Option1" | "Option2" } | { tag: "B", valueB: "OptionA" | "OptionB" }) & { intersectionValue: string }' but got '{ intersectionValue: string, tag: "A", valueA: "Option1" }'; the 1st component of the intersection is `{ tag: "A", valueA: "Option1" | "Option2" } | { tag: "B", valueB: "OptionA" | "OptionB" }`, and `{ intersectionValue: string, tag: "A", valueA: "Option1" }` is not a subtype of `{ tag: "A", valueA: "Option1" | "Option2" } | { tag: "B", valueB: "OptionA" | "OptionB" }`
Functions do not raise a Type Error, but do not autofill the possible options for the nested union type
local function func(parameter: TaggedUnionAndIntersection)
if parameter.tag == "A" then
if parameter.valueA == "Option1" then -- `parameter.valueA` autofills, but `Option1` does not
end
else
if parameter.valueB == "OptionA" then -- Same here
end
end
print(parameter.intersectionValue)
end
Indirectly intersecting the types yields the same behavior:
--!strict
type UnionType1 = "Option1" | "Option2"
type UnionType2 = "OptionA" | "OptionB"
type TaggedUnion = {
tag: "A",
valueA: UnionType1,
} | {
tag: "B",
valueB: UnionType2,
}
type Intersection = {
intersectionValue: string,
}
type TaggedUnionAndIntersection = TaggedUnion & Intersection
-- Same Type Error
local variable: TaggedUnionAndIntersection = {
tag = "A",
valueA = "Option1",
intersectionValue = "",
}
Expected behavior
Using nested unions within a tagged union type should not raise a Type Error, and should autofill:
-- No Type Error
local variable: TaggedUnionAndIntersection = {
tag = "A",
valueA = "Option1", -- `valueA` should autofill
intersectionValue = "",
}
local function func(parameter: TaggedUnionAndIntersection)
if parameter.tag == "A" then
if parameter.valueA == "Option1" then -- `Option1` should autofill
end
else
if parameter.valueB == "OptionA" then -- Same here
end
end
print(parameter.intersectionValue)
end