Union types (reference)¶
A union type is an anonymous, closed set of member types; a value of the union is a value of one member. This page specifies union spellings, assignability, collection literals at a union destination, narrowing, and matching.
Spellings¶
| Spelling | Position |
|---|---|
Union[A, B, ...] |
Any type position |
A | B | ... |
Any type position; the same type as Union[A, B, ...] |
- Nested unions flatten, duplicate members collapse, and member order does not matter:
Union[str, int],int | strandint | str | intare one type. - A union with
Noneas a member is anOptionof the other members:str | NoneisOption[str], andint | str | NoneisOption[Union[int, str]]. - A member that names a model, class, enum or newtype is the declaration that name resolves to where the union is written.
Product | intwritten in two modules that each declareProductis two different union types.
# first.incn
pub model Product:
pub value: int
pub type Answer = Product | int # accepted
# second.incn
pub model Product:
pub value: str
pub type Answer = Product | int # accepted
Assignability¶
- A value of a member type, or of a type assignable to a member type, is assignable to the union, as a return value, an assigned value or an argument.
- A union is assignable to another union when each of its members is assignable to a member of the other; a numeric member is assignable only to its own type (see Assignment between numeric types). A union with a member assignable to no member of the target is refused (
INCAN-T0001). - A union in the signature of another package's function or method is the same type as a union with the same members in the calling package.
# querykit/src/lib.incn
pub model Box:
pub value: int
def answer(self) -> int | str:
return self.value
# src/main.incn
from pub::querykit import Box
def show(value: int | str) -> str:
match value:
int(number) => return f"number {number}"
str(text) => return f"text {text}"
def widen(value: int | str) -> int | str | bool:
return value # accepted
def narrow(value: int | str | bool) -> int | str:
return value # refused: bool is not a member of int | str
def main() -> None:
println(show(Box(value=3).answer())) # accepted
Collection literals¶
- A list, dict or tuple literal at a union or
Optiondestination has the type of the destination's one member of the literal's kind (for a tuple literal, a tuple of the literal's length) when each other member is a scalar, a tuple, alist,dict,set,ResultorGenerator, or a model, class or enum. An empty literal and a literal ofNoneelements have that member's type. - A literal at a destination with two or more members of its kind has the type its elements give. A literal whose elements leave part of its type open (
[],[None],{},(None, 1)) is refused there (INCAN-T0001). - A literal at a destination with a type parameter, newtype, trait or frozen collection member has the type its elements give.
def main() -> None:
mut values: list[int] | str = "none"
values = [] # accepted
mut names: Option[list[Option[str]]] = None
names = [None] # accepted
mut either: list[int] | list[str] = [1]
either = ["a"] # accepted
either = [] # refused: list[int] or list[str]
Narrowing¶
- A union value has no member-specific methods or operators. Calling one, or applying an operator, is refused (
INCAN-T0001). - A union value has no printed form.
print(value),println(value),str(value)and an f-string{value}over it are refused (INCAN-T0103); a narrowed member displays as its own type (see Display). - In an
if isinstance(value, T):branch,valuehas typeT. In theelsebranch and in each followingelifbranch, it has the members no earlier branch tested. - An
Optionunion narrows throughis Noneandis not Noneas well. - Narrowing ends with the conditional statement: after it,
valuehas the union type again.
def size(value: int | str) -> int:
if isinstance(value, str):
return len(value) # accepted
else:
return value + 1 # accepted
def shout(value: int | str) -> str:
return value.upper() # refused: upper is not a method of int | str
def label(value: str | None) -> str:
if value is not None:
return value.upper() # accepted
return "missing"
def show(value: int | str) -> None:
match value:
int(n) => println(n) # accepted
str(s) => println(s) # accepted
println(value) # refused: a union value has no printed form
Matching¶
- A type pattern
T(p)matches a value of member typeT(see Match patterns). - The unguarded arms of a
matchover a union cover every member type, or include_(see Coverage). - An alternation of type patterns binds a name only when every alternative binds it at the same type. An alternation whose alternatives bind a name at different types is refused (
INCAN-T0001).
def classify(value: int | str | None) -> str:
match value:
int(_) | str(_) => return "present" # accepted
None => return "missing"
def pick(value: int | str) -> str:
match value:
int(item) | str(item) => return "item" # refused: item is an int in one alternative and a str in the other