Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion docs/library/built-in-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ fn dataframe_demo() -> i32:
return cast(rows.nrows(), i32)
```

## Casting
## Type-aware builtins

Use the built-in `cast(value, type)` helper to convert values between supported
types.
Expand All @@ -184,6 +184,25 @@ fn cast_demo(a: i32) -> str:
return cast(a, str)
```

Use `isinstance(value, type)` to compare the static semantic type of a value
with a concrete type, type alias, or finite union type.

```arx
type Number = i32 | i64

fn check(value: i32) -> bool:
return isinstance(value, Number)
```

Use `type(value)` to produce the value's semantic type name as `str`.

```arx
type Count = i32

fn type_name(value: Count) -> str:
return type(value)
```

## See Also

- [Data Types](datatypes.md) for annotation rules and placement
Expand Down
28 changes: 28 additions & 0 deletions docs/library/datatypes.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ fn summarize(name: str, values: list[i32]) -> none:
Common places where types appear:

- function parameters: `fn add(a: i32, b: i32) -> i32:`
- union function parameters: `fn id(value: i32 | i64) -> i32 | i64:`
- function return types: `fn test_add() -> none:`
- variable declarations: `var total: i32 = 0`
- type aliases: `type Number = i32 | i64`
- generic collection annotations: `list[i32]`
- shaped 1D tensor annotations: `tensor[i32, 4]`
- multidimensional tensor annotations: `tensor[i32, 2, 2]`
Expand All @@ -61,6 +63,31 @@ function and extern parameter annotations. Static-schema DataFrames can be
constructed with `dataframe({...})`, and their columns can be accessed with
either `rows.score` or `rows["score"]`.

## Type Aliases And Union Types

Type aliases are top-level declarations. The `type` word is contextual, so
`type Name = ...` declares an alias while `type(value)` calls the builtin
type-name helper.

````arx
```
title: Type alias example
summary: Demonstrates a finite numeric union alias.
```
type Number = i32 | i64

fn identity(value: Number) -> Number:
```
title: identity
summary: Returns a numeric union value.
```
return value
````

Union annotations use `|`. Numeric unions currently lower through a shared
numeric storage type. Runtime tagged unions and runtime narrowing are not part
of the current model.

## Built-in Type Reference

For the catalog of built-in types, aliases, and examples, see
Expand All @@ -73,3 +100,4 @@ That page covers:
- string, character, and temporal types
- lists, tensors, dataframes, series, and current limitations
- the `cast(value, type)` helper
- the `isinstance(value, type)` and `type(value)` helpers
1 change: 1 addition & 0 deletions docs/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ Contextual keywords:

- `as`
- `from`
- `type`

### Edge Cases

Expand Down
26 changes: 26 additions & 0 deletions examples/type-aliases.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
```
title: Type aliases and type-aware builtins
summary: Demonstrates union aliases, isinstance, type, and cast.
```

type Number = i32 | i64

fn identity(value: Number) -> Number:
```
title: identity
summary: Returns a numeric union value.
```
return value

fn main() -> i32:
```
title: main
summary: Calls type-aware builtins with a union alias.
```
var value: i64 = identity(5)
var ok: bool = isinstance(value, Number)
var name: str = type(value)
if ok:
return cast(value, i32)
else:
return 1
43 changes: 42 additions & 1 deletion packages/arx/src/arx/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@

BUILTIN_CAST = "cast"
BUILTIN_DATAFRAME = "dataframe"
BUILTIN_ISINSTANCE = "isinstance"
BUILTIN_PRINT = "print"
BUILTIN_RANGE = "range"
BUILTIN_TYPE = "type"
_GENERATORS_MODULE = f"{BUILTIN_NAMESPACE}.generators"


Expand Down Expand Up @@ -71,14 +73,18 @@ class AmbientBuiltinBinding:
__all__ = [
"BUILTIN_CAST",
"BUILTIN_DATAFRAME",
"BUILTIN_ISINSTANCE",
"BUILTIN_NAMESPACE",
"BUILTIN_PRINT",
"BUILTIN_RANGE",
"BUILTIN_SOURCE_EXTENSION",
"BUILTIN_TYPE",
"AmbientBuiltinBinding",
"BuiltinModuleAsset",
"build_cast",
"build_isinstance",
"build_print",
"build_type_of",
"get_ambient_builtin_imports",
"get_builtin_source",
"is_builtin",
Expand All @@ -98,7 +104,13 @@ def is_builtin(name: str) -> bool:
returns:
type: bool
"""
return name in {BUILTIN_CAST, BUILTIN_DATAFRAME, BUILTIN_PRINT}
return name in {
BUILTIN_CAST,
BUILTIN_DATAFRAME,
BUILTIN_ISINSTANCE,
BUILTIN_PRINT,
BUILTIN_TYPE,
}


def build_cast(
Expand All @@ -117,6 +129,23 @@ def build_cast(
return irx_astx.Cast(value=value, target_type=target_type)


def build_isinstance(
value: astx.Expr,
target_type: astx.DataType,
) -> irx_astx.IsInstanceExpr:
"""
title: Build an IRx IsInstanceExpr node.
parameters:
value:
type: astx.Expr
target_type:
type: astx.DataType
returns:
type: irx_astx.IsInstanceExpr
"""
return irx_astx.IsInstanceExpr(value=value, target_type=target_type)


def build_print(message: astx.Expr) -> irx_astx.PrintExpr:
"""
title: Build an IRx PrintExpr node.
Expand All @@ -129,6 +158,18 @@ def build_print(message: astx.Expr) -> irx_astx.PrintExpr:
return irx_astx.PrintExpr(message=message)


def build_type_of(value: astx.Expr) -> irx_astx.TypeOfExpr:
"""
title: Build an IRx TypeOfExpr node.
parameters:
value:
type: astx.Expr
returns:
type: irx_astx.TypeOfExpr
"""
return irx_astx.TypeOfExpr(value=value)


def is_builtin_module_specifier(specifier: str) -> bool:
"""
title: Return whether one specifier targets the bundled builtins.
Expand Down
7 changes: 6 additions & 1 deletion packages/arx/src/arx/lexer/syntax.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"var",
"while"
],
"contextual": ["as", "from", "binary", "unary", "operator"]
"contextual": ["as", "from", "type", "binary", "unary", "operator"]
},

"literals": {
Expand Down Expand Up @@ -147,6 +147,11 @@
"requires_from": true,
"allows_trailing_comma": true
},
"type_alias": {
"form": "type Name = type",
"supports_union_rhs": true,
"top_level_only": true
},
"relative_imports": {
"leading_dot_tokens": true
},
Expand Down
3 changes: 3 additions & 0 deletions packages/arx/src/arx/parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class Parser(
type: list[astx.DataType]
template_type_scopes:
type: list[dict[str, astx.DataType]]
type_aliases:
type: dict[str, astx.DataType]
value_scopes:
type: list[set[str]]
tokens:
Expand All @@ -56,6 +58,7 @@ class Parser(
tensor_scopes: list[dict[str, TensorBinding | None]]
return_type_scopes: list[astx.DataType]
template_type_scopes: list[dict[str, astx.DataType]]
type_aliases: dict[str, astx.DataType]
value_scopes: list[set[str]]
tokens: TokenList

Expand Down
17 changes: 17 additions & 0 deletions packages/arx/src/arx/parser/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class ParserMixinBase:
type: list[astx.DataType]
template_type_scopes:
type: list[dict[str, astx.DataType]]
type_aliases:
type: dict[str, astx.DataType]
value_scopes:
type: list[set[str]]
tokens:
Expand All @@ -53,6 +55,7 @@ class ParserMixinBase:
dataframe_scopes: list[dict[str, DataFrameBinding | None]]
return_type_scopes: list[astx.DataType]
template_type_scopes: list[dict[str, astx.DataType]]
type_aliases: dict[str, astx.DataType]
value_scopes: list[set[str]]
tokens: TokenList

Expand Down Expand Up @@ -454,6 +457,20 @@ def parse_type(
del allow_template_vars, allow_union, type_context
raise NotImplementedError

def is_type_alias_decl_start(self) -> bool:
"""
title: Return whether the current token starts a type alias.
returns:
type: bool
"""
raise NotImplementedError

def parse_type_alias_decl(self) -> None:
"""
title: Parse one top-level type alias declaration.
"""
raise NotImplementedError

def parse_function(
self,
template_params: tuple[astx.TemplateParam, ...] = (),
Expand Down
10 changes: 10 additions & 0 deletions packages/arx/src/arx/parser/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class ParserCore(ParserMixinBase):
type: list[astx.DataType]
template_type_scopes:
type: list[dict[str, astx.DataType]]
type_aliases:
type: dict[str, astx.DataType]
value_scopes:
type: list[set[str]]
tokens:
Expand All @@ -55,6 +57,7 @@ class ParserCore(ParserMixinBase):
dataframe_scopes: list[dict[str, DataFrameBinding | None]]
return_type_scopes: list[astx.DataType]
template_type_scopes: list[dict[str, astx.DataType]]
type_aliases: dict[str, astx.DataType]
value_scopes: list[set[str]]
tokens: TokenList

Expand Down Expand Up @@ -89,6 +92,7 @@ def __init__(self, tokens: TokenList = TokenList([])) -> None:
self.dataframe_scopes = [{}]
self.return_type_scopes = []
self.template_type_scopes = []
self.type_aliases = {}
self.value_scopes = [set()]
self.tokens = tokens

Expand All @@ -103,6 +107,7 @@ def clean(self) -> None:
self.dataframe_scopes = [{}]
self.return_type_scopes = []
self.template_type_scopes = []
self.type_aliases = {}
self.value_scopes = [set()]
self.tokens = TokenList([])

Expand Down Expand Up @@ -197,6 +202,11 @@ def parse(
allow_module_docstring = False
continue

if self.is_type_alias_decl_start():
self.parse_type_alias_decl()
allow_module_docstring = False
continue

if self.tokens.cur_tok.kind == TokenKind.kw_function:
tree.nodes.append(self.parse_function())
allow_module_docstring = False
Expand Down
11 changes: 7 additions & 4 deletions packages/arx/src/arx/parser/declarations.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ def parse_method_signature(

self._consume_operator(":")
param_type = self.parse_type(
type_context=TypeUseContext.PARAMETER
allow_union=True, type_context=TypeUseContext.PARAMETER
)
self._append_argument(
args,
Expand All @@ -599,7 +599,10 @@ def parse_method_signature(
)

self._consume_operator("->")
return_type = self.parse_type(type_context=TypeUseContext.RETURN)
return_type = self.parse_type(
allow_union=True,
type_context=TypeUseContext.RETURN,
)
return (
astx.FunctionPrototype(
method_name,
Expand Down Expand Up @@ -1079,7 +1082,7 @@ def parse_prototype(self, expect_colon: bool) -> astx.FunctionPrototype:

self._consume_operator(":")
arg_type = self.parse_type(
type_context=TypeUseContext.PARAMETER
allow_union=True, type_context=TypeUseContext.PARAMETER
)

self._append_argument(args, arg_name, arg_type, arg_loc)
Expand All @@ -1098,7 +1101,7 @@ def parse_prototype(self, expect_colon: bool) -> astx.FunctionPrototype:
)
self._consume_operator("->")
ret_type: astx.DataType = self.parse_type(
type_context=TypeUseContext.RETURN
allow_union=True, type_context=TypeUseContext.RETURN
)

if expect_colon:
Expand Down
Loading
Loading