Skip to content

Add support for Optionals and type-narrowing to the type system#10992

Open
regexident wants to merge 29 commits intoslint-ui:masterfrom
regexident:optionals
Open

Add support for Optionals and type-narrowing to the type system#10992
regexident wants to merge 29 commits intoslint-ui:masterfrom
regexident:optionals

Conversation

@regexident
Copy link

@regexident regexident commented Mar 10, 2026

Having grown increasingly upset about the lack of optionals in the language and the consequential need for dirty workarounds like in-band signalling via default type values a few days ago I couldn't take it any longer and decided to give them a shot myself.

There are a few features I'm desperately missing from slint (e.g. conditional/multiple/named @children), all of which would require optionals, afaict, so they felt worth tackling far beyond mere code cleanliness.

Now I usually don't throw massive code changes over the wall unsolicitedly, but in the context of #5164 having been referenced as something desirable multiple times by slint project owners and collaborators I hope you don't mind the lack of a prior "would you mind me opening a PR for this?". 🫣

Anyway, … this PR adds generalized support for:

  1. T?, aka optionals
  2. a none literal, representing , the absence of a value, aka null
  3. checking via optional.has-value()
  4. unwrapping via optional!
  5. null-coalescing via optional ?? fallback, or optional.value-or(fallback), or optional.value-or-default()
  6. scoped type-narrowing from T? to T via if-blocks
  7. implicit type-widening from T to T?

This PR does not (yet) add/change any documentation for any of the language/API additions/changes.

I also focused on the Rust backend for now, … and may need some assistance for the C++/Python ones eventually, due to a lack of practice with these languages on my part.

The addition of optionals by itself should be (mostly?) backwards-compatible, afaict. 🤞

What certainly isn't however is my removal of AccessibleRole.none in favor of AccessibleRole?. There may also be some issue with existing enums with a none variant. Both of which could (I think) be made non-breaking by making slint use something else like null as keyword/literal, rather than none. I did this mostly in order to be able to test the feature on material-ui as a guinea pig. I wouldn't mind reverting these AccessibleRole changes, even though from an API design point of view AccessibleRole? would be cleaner.

Fixes #5164

- Add `Optional(Box<Type>)` variant to Type enum with support for implicit `T` -> `T?` conversion and `Optional(T)` -> `Optional(U)` if `T` can convert to `U`
- Add `NoneValue` expression variant representing the 'none' literal for optional types
- Update all pattern matches in compiler passes, LLR lowering, and optimization passes to handle new variants
- Default Optional types to `NoneValue` in `default_value_for_type` functions
- Map `Type::Optional(T)` to `Option<T>` in Rust backend
- Map `Type::Optional(T)` to `std::optional<T>` in C++ backend
- Map `Type::Optional(T)` to `typing.Optional[T]` in Python backend
- Add `OptionalType` syntax node that wraps a base `Type` when followed by `?`
- Update `parse_type()` to check for trailing `?` and create `OptionalType` node
- Parse the `none` keyword as an identifier and resolve it in the resolution phase
- Add `OptionalType` handling in `type_from_node()` to resolve `T?` syntax to `Type::Optional(T)`
- Add `none` keyword resolution to `Expression::NoneValue` in identifier lookup
- Handle `Type::Optional` in property info, value type checking, and default values
- Handle `Expression::NoneValue` in expression evaluation (returns `Value::Void`)
- Add `UnwrapExpression` syntax node for postfix `!` operator
- Add `QuestionQuestion` token and `NullCoalesceExpression` syntax node
- Add `NullCoalesce` precedence level between Logical and Default (ternary)
- Parse `expr!` as `UnwrapExpression` and expr `??` fallback as `NullCoalesceExpression`
…neration

- Add `Expression::Unwrap` and `Expression::NullCoalesce` to AST with `ty()`, `visit()`, `is_constant()` support
- Implement LLR lowering for both operators
- Rust: Generate `.unwrap()` and `.unwrap_or_else(|| fallback)`
- C++: Generate `.value()` and `.value_or(fallback)`
- Python: Use interpreter evaluation (`Value::Void` for `none`)
- Add `from_unwrap_expression_node()` to resolve `UnwrapExpression` to `Expression::Unwrap`
- Add `from_null_coalesce_expression_node()` to resolve `NullCoalesceExpression` to `Expression::NullCoalesce`
- Handle type validation for unwrap and null-coalesce by their `ty()` methods
- Return `Type::Invalid` for invalid usage which triggers type mismatch errors elsewhere
…role

- BREAKING CHANGE: Remove `AccessibleRole.None` enum variant (in favor of `AccessibleRole?`)
- Change accessible-role type from `AccessibleRole` to AccessibleRole?
- Use `accessible-role: none` instead of `AccessibleRole.None`
- Remove `AccessibleRole::None` references from winit, qt, and testing backends
- Rely on `#[non_exhaustive]` wildcard matching in Qt and winit backends
- Add `Optional(Invalid)` -> `Optional(T)` conversion for none literal
- Fix enum value lookup for optional enum types in `ReturnTypeSpecificLookup`
- Fix `lower_accessibility` to handle optional `AccessibleRole`
- Add interpreter evaluation for `Unwrap` and `NullCoalesce` expressions
- All core packages and demos now compile successfully
- Handle none literal (potentially wrapped in `Cast`) in `lower_accessibility`
- Update error checking to accept constant expressions
- Includes test cases for Rust and C++ code generation
- Add "optionals_edge_cases.slint" testing duration, length, color, brush, image optionals
- Add "optional_errors.slint" testing error detection for invalid operations
- Add "optional_conversions.slint" testing type conversions with optionals
- Test property bindings, updates, and dependency tracking with optional types
- Convert tooltip properties from string to string? to match base.tooltip type in StateLayerArea. Update accessible-label to use .value-or("") for optional tooltip values.
- Update struct definitions (`NavigationItem`, `MenuItem`, `ListItem`) to use optional types for icon and text fields to match component expectations.
- Fix `DropDownMenu` leading_icon to be optional.
- Fix conditional lowering when false branch is NoneValue and true is Optional
- Add Cast match arm for `check_styles` test, fix `none` keyword shadowing enum variants in non-optional contexts
- Add type validation for unwrap (`!`) and null-coalesce (`??`) operators
- Handle Cast-wrapped EnumerationValue in accessibility checks
- Update syntax test expectations
@CLAassistant
Copy link

CLAassistant commented Mar 10, 2026

CLA assistant check
All committers have signed the CLA.

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

Successfully merging this pull request may close these issues.

Optional in the type system

2 participants