use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
use crate::langtype::{
BuiltinElement, BuiltinStruct, EnumerationValue, Function, Keys, PropertyLookupMode, Struct,
Type,
};
use crate::layout::Orientation;
use crate::lookup::LookupCtx;
use crate::object_tree::*;
use crate::parser::{NodeOrToken, SyntaxNode};
use crate::symbol_counters::SymbolCounters;
use crate::typeregister;
use core::cell::RefCell;
use smol_str::{SmolStr, format_smolstr};
use std::cell::Cell;
use std::collections::BTreeMap;
use std::rc::{Rc, Weak};
use std::sync::Arc;
pub use crate::namedreference::NamedReference;
pub use crate::passes::resolving;
#[derive(Debug, Clone, PartialEq, Eq, strum::EnumString)]
pub enum BuiltinFunction {
GetWindowScaleFactor,
GetWindowDefaultFontSize,
AnimationTick,
Debug,
Mod,
Round,
Ceil,
Floor,
Abs,
Sqrt,
Cos,
Sin,
Tan,
ACos,
ASin,
ATan,
ATan2,
Log,
Ln,
Pow,
Exp,
ToFixed,
ToPrecision,
ToStringUnlocalized,
SetFocusItem,
ClearFocusItem,
ShowPopupWindow,
ClosePopupWindow,
ShowPopupMenu,
ShowPopupMenuInternal,
SetSelectionOffsets,
ItemFontMetrics,
StringToFloat,
StringIsFloat,
StringIsEmpty,
StringCharacterCount,
StringToLowercase,
StringToUppercase,
StringStartsWith,
StringEndsWith,
StringReplaceAll,
KeysToString,
ColorRgbaStruct,
ColorHsvaStruct,
ColorOklchStruct,
ColorBrighter,
ColorDarker,
ColorTransparentize,
ColorMix,
ColorWithAlpha,
ImageSize,
ArrayLength,
ArrayPush,
ArrayRemove,
ArrayInsert,
ArrayAny,
ArrayAll,
ArrayFindIndex,
Rgb,
Hsv,
Oklch,
ColorScheme,
AccentColor,
SupportsNativeMenuBar,
SetupMenuBar,
SetupSystemTrayIcon,
Use24HourFormat,
MonthDayCount,
MonthOffset,
FormatDate,
DateNow,
ValidDate,
ParseDate,
TextInputFocused,
SetTextInputFocused,
#[strum(disabled)]
ImplicitLayoutInfo(Orientation),
ItemAbsolutePosition,
RegisterCustomFontByPath,
RegisterCustomFontByMemory,
RegisterBitmapFont,
Translate,
UpdateTimers,
DetectOperatingSystem,
StartTimer,
StopTimer,
RestartTimer,
OpenUrl,
MacosBringAllWindowsToFront,
ParseMarkdown,
StringToStyledText,
ColorToStyledText,
DecimalSeparator,
DefaultWindowTitle,
PathPointAt,
PathAngleAt,
}
#[derive(Debug, Clone)]
pub enum BuiltinMacroFunction {
Min,
Max,
Clamp,
Mod,
Abs,
Sign,
CubicBezier,
Rgb,
Hsv,
Oklch,
Debug,
ArrayPush,
ArrayRemove,
ArrayInsert,
ArrayIndexOf,
CustomMouseCursor,
Spring,
}
macro_rules! declare_builtin_function_types {
($( $Name:ident $(($Pattern:tt))? : ($( $Arg:expr ),*) -> $ReturnType:expr $(,)? )*) => {
#[allow(non_snake_case)]
pub struct BuiltinFunctionTypes {
$(pub $Name : Arc<Function>),*
}
impl BuiltinFunctionTypes {
pub fn new() -> Self {
Self {
$($Name : Arc::new(Function{
args: vec![$($Arg),*],
return_type: $ReturnType,
arg_names: Vec::new(),
})),*
}
}
pub fn ty(&self, function: &BuiltinFunction) -> Arc<Function> {
match function {
$(BuiltinFunction::$Name $(($Pattern))? => self.$Name.clone()),*
}
}
}
};
}
declare_builtin_function_types!(
GetWindowScaleFactor: () -> Type::UnitProduct(vec![(Unit::Phx, 1), (Unit::Px, -1)]),
GetWindowDefaultFontSize: () -> Type::LogicalLength,
AnimationTick: () -> Type::Duration,
Debug: (Type::String) -> Type::Void,
Mod: (Type::Int32, Type::Int32) -> Type::Int32,
Round: (Type::Float32) -> Type::Int32,
Ceil: (Type::Float32) -> Type::Int32,
Floor: (Type::Float32) -> Type::Int32,
Sqrt: (Type::Float32) -> Type::Float32,
Abs: (Type::Float32) -> Type::Float32,
Cos: (Type::Angle) -> Type::Float32,
Sin: (Type::Angle) -> Type::Float32,
Tan: (Type::Angle) -> Type::Float32,
ACos: (Type::Float32) -> Type::Angle,
ASin: (Type::Float32) -> Type::Angle,
ATan: (Type::Float32) -> Type::Angle,
ATan2: (Type::Float32, Type::Float32) -> Type::Angle,
DecimalSeparator: () -> Type::String,
DefaultWindowTitle: () -> Type::String,
Log: (Type::Float32, Type::Float32) -> Type::Float32,
Ln: (Type::Float32) -> Type::Float32,
Pow: (Type::Float32, Type::Float32) -> Type::Float32,
Exp: (Type::Float32) -> Type::Float32,
ToFixed: (Type::Float32, Type::Int32) -> Type::String,
ToPrecision: (Type::Float32, Type::Int32) -> Type::String,
ToStringUnlocalized: (Type::Float32) -> Type::String,
SetFocusItem: (Type::ElementReference) -> Type::Void,
ClearFocusItem: (Type::ElementReference) -> Type::Void,
ShowPopupWindow: (Type::ElementReference) -> Type::Void,
ClosePopupWindow: (Type::ElementReference) -> Type::Void,
ShowPopupMenu: (Type::ElementReference, Type::ElementReference, typeregister::logical_point_type().into()) -> Type::Void,
ShowPopupMenuInternal: (Type::ElementReference, Type::Model, typeregister::logical_point_type().into()) -> Type::Void,
SetSelectionOffsets: (Type::ElementReference, Type::Int32, Type::Int32) -> Type::Void,
ItemFontMetrics: (Type::ElementReference) -> typeregister::font_metrics_type(),
StringToFloat: (Type::String) -> Type::Float32,
StringIsFloat: (Type::String) -> Type::Bool,
StringIsEmpty: (Type::String) -> Type::Bool,
StringCharacterCount: (Type::String) -> Type::Int32,
StringToLowercase: (Type::String) -> Type::String,
StringToUppercase: (Type::String) -> Type::String,
StringStartsWith: (Type::String, Type::String) -> Type::Bool,
StringEndsWith: (Type::String, Type::String) -> Type::Bool,
StringReplaceAll: (Type::String, Type::String, Type::String) -> Type::String,
KeysToString: (Type::Keys) -> Type::String,
ImplicitLayoutInfo(..): (Type::ElementReference, Type::Float32) -> typeregister::layout_info_type().into(),
ColorRgbaStruct: (Type::Color) -> Type::Struct(Arc::new(Struct::new(IntoIterator::into_iter([
(SmolStr::new_static("red"), Type::Int32),
(SmolStr::new_static("green"), Type::Int32),
(SmolStr::new_static("blue"), Type::Int32),
(SmolStr::new_static("alpha"), Type::Int32),
])
.collect(), BuiltinStruct::Color))),
ColorHsvaStruct: (Type::Color) -> Type::Struct(Arc::new(Struct::new(IntoIterator::into_iter([
(SmolStr::new_static("hue"), Type::Float32),
(SmolStr::new_static("saturation"), Type::Float32),
(SmolStr::new_static("value"), Type::Float32),
(SmolStr::new_static("alpha"), Type::Float32),
])
.collect(), BuiltinStruct::Color))),
ColorOklchStruct: (Type::Color) -> Type::Struct(Arc::new(Struct::new(IntoIterator::into_iter([
(SmolStr::new_static("lightness"), Type::Float32),
(SmolStr::new_static("chroma"), Type::Float32),
(SmolStr::new_static("hue"), Type::Float32),
(SmolStr::new_static("alpha"), Type::Float32),
])
.collect(), BuiltinStruct::Color))),
ColorBrighter: (Type::Brush, Type::Float32) -> Type::Brush,
ColorDarker: (Type::Brush, Type::Float32) -> Type::Brush,
ColorTransparentize: (Type::Brush, Type::Float32) -> Type::Brush,
ColorWithAlpha: (Type::Brush, Type::Float32) -> Type::Brush,
ColorMix: (Type::Color, Type::Color, Type::Float32) -> Type::Color,
ImageSize: (Type::Image) -> Type::Struct(Arc::new(Struct::new(IntoIterator::into_iter([
(SmolStr::new_static("width"), Type::Int32),
(SmolStr::new_static("height"), Type::Int32),
])
.collect(), crate::langtype::BuiltinStruct::Size))),
ArrayLength: (Type::Model) -> Type::Int32,
ArrayPush: (Type::Model, Type::InferredProperty) -> Type::Void,
ArrayRemove: (Type::Model, Type::Int32) -> Type::Void,
ArrayInsert: (Type::Model, Type::Int32, Type::InferredProperty) -> Type::Void,
ArrayAny: (Type::Model, Type::Closure) -> Type::Bool,
ArrayAll: (Type::Model, Type::Closure) -> Type::Bool,
ArrayFindIndex: (Type::Model, Type::Closure) -> Type::Int32,
Rgb: (Type::Int32, Type::Int32, Type::Int32, Type::Float32) -> Type::Color,
Hsv: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color,
Oklch: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color,
ColorScheme: () -> Type::Enumeration(
typeregister::BUILTIN.enums.ColorScheme.clone(),
),
AccentColor: () -> Type::Color,
SupportsNativeMenuBar: () -> Type::Bool,
SetupMenuBar: (Type::Model, typeregister::noarg_callback_type(), typeregister::noarg_callback_type()) -> Type::Void,
SetupSystemTrayIcon: (Type::ElementReference, Type::ElementReference) -> Type::Void,
MonthDayCount: (Type::Int32, Type::Int32) -> Type::Int32,
MonthOffset: (Type::Int32, Type::Int32) -> Type::Int32,
FormatDate: (Type::String, Type::Int32, Type::Int32, Type::Int32) -> Type::String,
TextInputFocused: () -> Type::Bool,
DateNow: () -> Type::Array(Arc::new(Type::Int32)),
ValidDate: (Type::String, Type::String) -> Type::Bool,
ParseDate: (Type::String, Type::String) -> Type::Array(Arc::new(Type::Int32)),
SetTextInputFocused: (Type::Bool) -> Type::Void,
ItemAbsolutePosition: (Type::ElementReference) -> typeregister::logical_point_type().into(),
RegisterCustomFontByPath: (Type::String) -> Type::Void,
RegisterCustomFontByMemory: (Type::Int32) -> Type::Void,
RegisterBitmapFont: (Type::Int32) -> Type::Void,
Translate: (Type::String, Type::String, Type::String, Type::Array(Type::String.into())) -> Type::String,
Use24HourFormat: () -> Type::Bool,
UpdateTimers: () -> Type::Void,
DetectOperatingSystem: () -> Type::Enumeration(
typeregister::BUILTIN.enums.OperatingSystemType.clone(),
),
StartTimer: (Type::ElementReference) -> Type::Void,
StopTimer: (Type::ElementReference) -> Type::Void,
RestartTimer: (Type::ElementReference) -> Type::Void,
ParseMarkdown: (Type::String, Type::Array(Type::StyledText.into())) -> Type::StyledText,
StringToStyledText: (Type::String) -> Type::StyledText,
ColorToStyledText: (Type::Color) -> Type::StyledText
OpenUrl: (Type::String) -> Type::Bool,
MacosBringAllWindowsToFront: () -> Type::Void,
PathPointAt: (Type::ElementReference, Type::Float32) -> typeregister::logical_point_type().into(),
PathAngleAt: (Type::ElementReference, Type::Float32) -> Type::Angle,
);
impl Default for BuiltinFunctionTypes {
fn default() -> Self {
Self::new()
}
}
impl BuiltinFunction {
pub fn ty(&self) -> Arc<Function> {
static TYPES: std::sync::LazyLock<BuiltinFunctionTypes> =
std::sync::LazyLock::new(BuiltinFunctionTypes::new);
TYPES.ty(self)
}
fn is_const(&self, global_analysis: Option<&crate::passes::GlobalAnalysis>) -> bool {
match self {
BuiltinFunction::GetWindowScaleFactor => {
global_analysis.is_some_and(|x| x.const_scale_factor.is_some())
}
BuiltinFunction::GetWindowDefaultFontSize => {
global_analysis.is_some_and(|x| x.default_font_size.is_const())
}
BuiltinFunction::AnimationTick => false,
BuiltinFunction::ColorScheme => false,
BuiltinFunction::AccentColor => false,
BuiltinFunction::SupportsNativeMenuBar => false,
BuiltinFunction::SetupMenuBar => false,
BuiltinFunction::SetupSystemTrayIcon => false,
BuiltinFunction::MonthDayCount => false,
BuiltinFunction::MonthOffset => false,
BuiltinFunction::FormatDate => false,
BuiltinFunction::DateNow => false,
BuiltinFunction::ValidDate => false,
BuiltinFunction::ParseDate => false,
BuiltinFunction::DecimalSeparator => false,
BuiltinFunction::DefaultWindowTitle => false,
BuiltinFunction::Debug => true,
BuiltinFunction::Mod
| BuiltinFunction::Round
| BuiltinFunction::Ceil
| BuiltinFunction::Floor
| BuiltinFunction::Abs
| BuiltinFunction::Sqrt
| BuiltinFunction::Cos
| BuiltinFunction::Sin
| BuiltinFunction::Tan
| BuiltinFunction::ACos
| BuiltinFunction::ASin
| BuiltinFunction::Log
| BuiltinFunction::Ln
| BuiltinFunction::Pow
| BuiltinFunction::Exp
| BuiltinFunction::ATan
| BuiltinFunction::ATan2
| BuiltinFunction::ToStringUnlocalized => true,
BuiltinFunction::ToFixed
| BuiltinFunction::ToPrecision
| BuiltinFunction::StringToFloat
| BuiltinFunction::StringIsFloat => false,
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
BuiltinFunction::ShowPopupWindow
| BuiltinFunction::ClosePopupWindow
| BuiltinFunction::ShowPopupMenu
| BuiltinFunction::ShowPopupMenuInternal => false,
BuiltinFunction::SetSelectionOffsets => false,
BuiltinFunction::ItemFontMetrics => false, BuiltinFunction::StringIsEmpty
| BuiltinFunction::StringCharacterCount
| BuiltinFunction::StringToLowercase
| BuiltinFunction::StringToUppercase
| BuiltinFunction::StringStartsWith
| BuiltinFunction::StringEndsWith
| BuiltinFunction::StringReplaceAll
| BuiltinFunction::KeysToString => true,
BuiltinFunction::ColorRgbaStruct
| BuiltinFunction::ColorHsvaStruct
| BuiltinFunction::ColorOklchStruct
| BuiltinFunction::ColorBrighter
| BuiltinFunction::ColorDarker
| BuiltinFunction::ColorTransparentize
| BuiltinFunction::ColorMix
| BuiltinFunction::ColorWithAlpha => true,
BuiltinFunction::ImageSize => global_analysis.is_some_and(|x| x.const_image_sizes),
BuiltinFunction::ArrayLength => true,
BuiltinFunction::ArrayPush
| BuiltinFunction::ArrayRemove
| BuiltinFunction::ArrayInsert => false,
BuiltinFunction::Rgb => true,
BuiltinFunction::Hsv => true,
BuiltinFunction::Oklch => true,
BuiltinFunction::SetTextInputFocused => false,
BuiltinFunction::TextInputFocused => false,
BuiltinFunction::ImplicitLayoutInfo(_) => false,
BuiltinFunction::ItemAbsolutePosition => true,
BuiltinFunction::RegisterCustomFontByPath
| BuiltinFunction::RegisterCustomFontByMemory
| BuiltinFunction::RegisterBitmapFont => false,
BuiltinFunction::Translate => false,
BuiltinFunction::Use24HourFormat => false,
BuiltinFunction::UpdateTimers => false,
BuiltinFunction::DetectOperatingSystem => true,
BuiltinFunction::StartTimer => false,
BuiltinFunction::StopTimer => false,
BuiltinFunction::RestartTimer => false,
BuiltinFunction::ParseMarkdown => false,
BuiltinFunction::StringToStyledText => true,
BuiltinFunction::ColorToStyledText => true,
BuiltinFunction::OpenUrl => false,
BuiltinFunction::MacosBringAllWindowsToFront => false,
BuiltinFunction::PathPointAt => true,
BuiltinFunction::PathAngleAt => true,
BuiltinFunction::ArrayAny
| BuiltinFunction::ArrayAll
| BuiltinFunction::ArrayFindIndex => true,
}
}
pub fn is_pure(&self) -> bool {
match self {
BuiltinFunction::GetWindowScaleFactor => true,
BuiltinFunction::GetWindowDefaultFontSize => true,
BuiltinFunction::AnimationTick => true,
BuiltinFunction::ColorScheme => true,
BuiltinFunction::AccentColor => true,
BuiltinFunction::SupportsNativeMenuBar => true,
BuiltinFunction::SetupMenuBar => false,
BuiltinFunction::SetupSystemTrayIcon => false,
BuiltinFunction::MonthDayCount => true,
BuiltinFunction::MonthOffset => true,
BuiltinFunction::FormatDate => true,
BuiltinFunction::DateNow => true,
BuiltinFunction::ValidDate => true,
BuiltinFunction::ParseDate => true,
BuiltinFunction::DecimalSeparator => true,
BuiltinFunction::DefaultWindowTitle => true,
BuiltinFunction::Debug => true,
BuiltinFunction::Mod
| BuiltinFunction::Round
| BuiltinFunction::Ceil
| BuiltinFunction::Floor
| BuiltinFunction::Abs
| BuiltinFunction::Sqrt
| BuiltinFunction::Cos
| BuiltinFunction::Sin
| BuiltinFunction::Tan
| BuiltinFunction::ACos
| BuiltinFunction::ASin
| BuiltinFunction::Log
| BuiltinFunction::Ln
| BuiltinFunction::Pow
| BuiltinFunction::Exp
| BuiltinFunction::ATan
| BuiltinFunction::ATan2
| BuiltinFunction::ToFixed
| BuiltinFunction::ToPrecision
| BuiltinFunction::ToStringUnlocalized => true,
BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false,
BuiltinFunction::ShowPopupWindow
| BuiltinFunction::ClosePopupWindow
| BuiltinFunction::ShowPopupMenu
| BuiltinFunction::ShowPopupMenuInternal => false,
BuiltinFunction::SetSelectionOffsets => false,
BuiltinFunction::ItemFontMetrics => true,
BuiltinFunction::StringToFloat
| BuiltinFunction::StringIsFloat
| BuiltinFunction::StringIsEmpty
| BuiltinFunction::StringCharacterCount
| BuiltinFunction::StringToLowercase
| BuiltinFunction::StringToUppercase
| BuiltinFunction::StringStartsWith
| BuiltinFunction::StringEndsWith
| BuiltinFunction::StringReplaceAll
| BuiltinFunction::KeysToString => true,
BuiltinFunction::ColorRgbaStruct
| BuiltinFunction::ColorHsvaStruct
| BuiltinFunction::ColorOklchStruct
| BuiltinFunction::ColorBrighter
| BuiltinFunction::ColorDarker
| BuiltinFunction::ColorTransparentize
| BuiltinFunction::ColorMix
| BuiltinFunction::ColorWithAlpha => true,
BuiltinFunction::ImageSize => true,
BuiltinFunction::ArrayLength => true,
BuiltinFunction::ArrayPush
| BuiltinFunction::ArrayRemove
| BuiltinFunction::ArrayInsert => false,
BuiltinFunction::Rgb => true,
BuiltinFunction::Hsv => true,
BuiltinFunction::Oklch => true,
BuiltinFunction::ImplicitLayoutInfo(_) => true,
BuiltinFunction::ItemAbsolutePosition => true,
BuiltinFunction::SetTextInputFocused => false,
BuiltinFunction::TextInputFocused => true,
BuiltinFunction::RegisterCustomFontByPath
| BuiltinFunction::RegisterCustomFontByMemory
| BuiltinFunction::RegisterBitmapFont => false,
BuiltinFunction::Translate => true,
BuiltinFunction::Use24HourFormat => true,
BuiltinFunction::UpdateTimers => false,
BuiltinFunction::DetectOperatingSystem => true,
BuiltinFunction::StartTimer => false,
BuiltinFunction::StopTimer => false,
BuiltinFunction::RestartTimer => false,
BuiltinFunction::ParseMarkdown => true,
BuiltinFunction::StringToStyledText => true,
BuiltinFunction::ColorToStyledText => true,
BuiltinFunction::OpenUrl => false,
BuiltinFunction::MacosBringAllWindowsToFront => false,
BuiltinFunction::PathPointAt => true,
BuiltinFunction::PathAngleAt => true,
BuiltinFunction::ArrayAny
| BuiltinFunction::ArrayAll
| BuiltinFunction::ArrayFindIndex => true,
}
}
}
#[derive(Debug, Clone)]
pub enum Callable {
Callback(NamedReference),
Function(NamedReference),
Builtin(BuiltinFunction),
}
impl Callable {
pub fn ty(&self) -> Type {
match self {
Callable::Callback(nr) => nr.ty(),
Callable::Function(nr) => nr.ty(),
Callable::Builtin(b) => Type::Function(b.ty()),
}
}
}
impl From<BuiltinFunction> for Callable {
fn from(function: BuiltinFunction) -> Self {
Self::Builtin(function)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum OperatorClass {
ComparisonOp,
LogicalOp,
ArithmeticOp,
}
pub fn operator_class(op: char) -> OperatorClass {
match op {
'=' | '!' | '<' | '>' | '≤' | '≥' => OperatorClass::ComparisonOp,
'&' | '|' => OperatorClass::LogicalOp,
'+' | '-' | '/' | '*' => OperatorClass::ArithmeticOp,
_ => panic!("Invalid operator {op:?}"),
}
}
macro_rules! declare_units {
(@normalize $value:ident, $ident:ident) => { ($value, Unit::$ident) };
(@normalize $value:ident, $ident:ident, $canon:ident, $factor:expr) => {
($value * ($factor as f64), Unit::$canon)
};
($( $(#[$m:meta])* $ident:ident = $string:literal $(-> $canon:ident * $factor:expr)? ,)*) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter)]
pub enum WrittenUnit {
$($(#[$m])* $ident,)*
}
impl std::fmt::Display for WrittenUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$(Self::$ident => write!(f, $string), )*
}
}
}
impl std::str::FromStr for WrittenUnit {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
$($string => Ok(Self::$ident), )*
_ => Err(())
}
}
}
impl WrittenUnit {
pub fn normalize(self, value: f64) -> (f64, Unit) {
match self {
$(Self::$ident => declare_units!(@normalize value, $ident $(, $canon, $factor)?), )*
}
}
}
};
}
declare_units! {
None = "",
Percent = "%",
Phx = "phx",
Px = "px",
Cm = "cm" -> Px * 37.8,
Mm = "mm" -> Px * 3.78,
In = "in" -> Px * 96,
Pt = "pt" -> Px * 96./72.,
Rem = "rem",
S = "s" -> Ms * 1000,
Ms = "ms",
Deg = "deg",
Grad = "grad" -> Deg * 360./180.,
Turn = "turn" -> Deg * 360.,
Rad = "rad" -> Deg * 360./std::f32::consts::TAU,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Unit {
#[default]
None,
Percent,
Phx,
Px,
Rem,
Ms,
Deg,
}
impl Unit {
pub fn ty(self) -> Type {
match self {
Unit::None => Type::Float32,
Unit::Percent => Type::Percent,
Unit::Px => Type::LogicalLength,
Unit::Phx => Type::PhysicalLength,
Unit::Rem => Type::Rem,
Unit::Ms => Type::Duration,
Unit::Deg => Type::Angle,
}
}
}
impl std::fmt::Display for Unit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Unit::None => "",
Unit::Percent => "%",
Unit::Px => "px",
Unit::Phx => "phx",
Unit::Rem => "rem",
Unit::Ms => "ms",
Unit::Deg => "deg",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy)]
pub enum MinMaxOp {
Min,
Max,
}
#[derive(Debug, Clone)]
pub enum ConditionLocation {
Question(SourceLocation),
StateSelection { name: SourceLocation, when: SourceLocation },
StateChange(SourceLocation),
}
#[derive(Debug, Clone, Default)]
pub enum Expression {
#[default]
Invalid,
Uncompiled(SyntaxNode),
StringLiteral(SmolStr),
NumberLiteral(f64, Unit),
BoolLiteral(bool),
PropertyReference(NamedReference),
ElementReference(Weak<RefCell<Element>>),
RepeaterIndexReference {
element: Weak<RefCell<Element>>,
},
RepeaterModelReference {
element: Weak<RefCell<Element>>,
},
FunctionParameterReference {
index: usize,
ty: Type,
},
StoreLocalVariable {
name: SmolStr,
value: Box<Expression>,
},
ReadLocalVariable {
name: SmolStr,
ty: Type,
},
StructFieldAccess {
base: Box<Expression>,
name: SmolStr,
},
ArrayIndex {
array: Box<Expression>,
index: Box<Expression>,
},
Cast {
from: Box<Expression>,
to: Type,
},
CodeBlock(Vec<Expression>),
FunctionCall {
function: Callable,
arguments: Vec<Expression>,
source_location: Option<SourceLocation>,
},
SelfAssignment {
lhs: Box<Expression>,
rhs: Box<Expression>,
op: char,
node: Option<NodeOrToken>,
},
BinaryExpression {
lhs: Box<Expression>,
rhs: Box<Expression>,
op: char,
source_location: Option<SourceLocation>,
},
UnaryOp {
sub: Box<Expression>,
op: char,
},
ImageReference {
resource_ref: ImageReference,
source_location: Option<SourceLocation>,
nine_slice: Option<[u16; 4]>,
},
Condition {
condition: Box<Expression>,
true_expr: Box<Expression>,
false_expr: Box<Expression>,
source_location: Option<ConditionLocation>,
},
Array {
element_ty: Type,
values: Vec<Expression>,
},
Struct {
ty: Arc<Struct>,
values: BTreeMap<SmolStr, Expression>,
},
PathData(Path),
EasingCurve(EasingCurve),
EmptyDataTransfer,
MouseCursor(MouseCursorInner),
LinearGradient {
angle: Box<Expression>,
stops: Vec<(Expression, Expression)>,
},
RadialGradient {
center: Option<(Box<Expression>, Box<Expression>)>,
radius: Option<Box<Expression>>,
stops: Vec<(Expression, Expression)>,
},
ConicGradient {
from_angle: Box<Expression>,
center: Option<(Box<Expression>, Box<Expression>)>,
stops: Vec<(Expression, Expression)>,
},
EnumerationValue(EnumerationValue),
Keys(Keys),
ReturnStatement(Option<Box<Expression>>),
LayoutCacheAccess {
layout_cache_prop: NamedReference,
index: usize,
repeater_index: Option<Box<Expression>>,
entries_per_item: usize,
},
GridRepeaterCacheAccess {
layout_cache_prop: NamedReference,
index: usize,
repeater_index: Box<Expression>,
stride: Box<Expression>,
child_offset: usize,
inner_repeater_index: Option<Box<Expression>>,
entries_per_item: usize,
},
OrganizeGridLayout(crate::layout::GridLayout),
ComputeBoxLayoutInfo {
layout: crate::layout::BoxLayout,
orientation: crate::layout::Orientation,
cross_axis_size: Option<Box<Expression>>,
},
ComputeGridLayoutInfo {
layout_organized_data_prop: NamedReference,
layout: crate::layout::GridLayout,
orientation: crate::layout::Orientation,
cross_axis_size: Option<Box<Expression>>,
},
SolveBoxLayout(crate::layout::BoxLayout, crate::layout::Orientation),
SolveGridLayout {
layout_organized_data_prop: NamedReference,
layout: crate::layout::GridLayout,
orientation: crate::layout::Orientation,
},
SolveFlexboxLayout(crate::layout::FlexboxLayout),
ComputeFlexboxLayoutInfo {
layout: crate::layout::FlexboxLayout,
orientation: crate::layout::Orientation,
cross_axis_size: Option<Box<Expression>>,
},
MinMax {
ty: Type,
op: MinMaxOp,
lhs: Box<Expression>,
rhs: Box<Expression>,
},
DebugHook {
expression: Box<Expression>,
id: SmolStr,
synthetic: bool,
},
EmptyComponentFactory,
Closure {
arg_name: SmolStr,
expression: Box<Expression>,
},
}
impl Expression {
pub fn ty(&self) -> Type {
match self {
Expression::Invalid => Type::Invalid,
Expression::Uncompiled(_) => Type::Invalid,
Expression::StringLiteral(_) => Type::String,
Expression::NumberLiteral(_, unit) => unit.ty(),
Expression::BoolLiteral(_) => Type::Bool,
Expression::PropertyReference(nr) => nr.ty(),
Expression::ElementReference(_) => Type::ElementReference,
Expression::RepeaterIndexReference { .. } => Type::Int32,
Expression::RepeaterModelReference { element } => element
.upgrade()
.unwrap()
.borrow()
.repeated
.as_ref()
.map_or(Type::Invalid, |e| model_inner_type(&e.model)),
Expression::FunctionParameterReference { ty, .. } => ty.clone(),
Expression::StructFieldAccess { base, name } => match base.ty() {
Type::Struct(s) => s.fields.get(name.as_str()).unwrap_or(&Type::Invalid).clone(),
_ => Type::Invalid,
},
Expression::ArrayIndex { array, .. } => match array.ty() {
Type::Array(ty) => (*ty).clone(),
_ => Type::Invalid,
},
Expression::Cast { to, .. } => to.clone(),
Expression::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty()),
Expression::FunctionCall { function, .. } => match function.ty() {
Type::Function(f) | Type::Callback(f) => f.return_type.clone(),
_ => Type::Invalid,
},
Expression::SelfAssignment { .. } => Type::Void,
Expression::ImageReference { .. } => Type::Image,
Expression::Condition { condition: _, true_expr, false_expr, .. } => {
let true_type = true_expr.ty();
let false_type = false_expr.ty();
if true_type == false_type {
true_type
} else if true_type == Type::Invalid {
false_type
} else if false_type == Type::Invalid {
true_type
} else {
Type::Void
}
}
Expression::BinaryExpression { op, lhs, rhs, .. } => {
if operator_class(*op) != OperatorClass::ArithmeticOp {
Type::Bool
} else if *op == '+' || *op == '-' {
let (rhs_ty, lhs_ty) = (rhs.ty(), lhs.ty());
if rhs_ty == lhs_ty { rhs_ty } else { Type::Invalid }
} else {
debug_assert!(*op == '*' || *op == '/');
let unit_vec = |ty| {
if let Type::UnitProduct(v) = ty {
v
} else if let Some(u) = ty.default_unit() {
vec![(u, 1)]
} else {
Vec::new()
}
};
let mut l_units = unit_vec(lhs.ty());
let mut r_units = unit_vec(rhs.ty());
if *op == '/' {
for (_, power) in &mut r_units {
*power = -*power;
}
}
for (unit, power) in r_units {
if let Some((_, p)) = l_units.iter_mut().find(|(u, _)| *u == unit) {
*p += power;
} else {
l_units.push((unit, power));
}
}
l_units.retain(|(_, p)| *p != 0);
l_units.sort_unstable_by(|(u1, p1), (u2, p2)| match p2.cmp(p1) {
std::cmp::Ordering::Equal => u1.cmp(u2),
x => x,
});
if l_units.is_empty() {
Type::Float32
} else if l_units.len() == 1 && l_units[0].1 == 1 {
l_units[0].0.ty()
} else {
Type::UnitProduct(l_units)
}
}
}
Expression::UnaryOp { sub, .. } => sub.ty(),
Expression::Array { element_ty, .. } => Type::Array(Arc::new(element_ty.clone())),
Expression::Struct { ty, .. } => ty.clone().into(),
Expression::PathData { .. } => Type::PathData,
Expression::EmptyDataTransfer => Type::DataTransfer,
Expression::StoreLocalVariable { .. } => Type::Void,
Expression::ReadLocalVariable { ty, .. } => ty.clone(),
Expression::EasingCurve(_) => Type::Easing,
Expression::MouseCursor(_) => Type::MouseCursor,
Expression::LinearGradient { .. } => Type::Brush,
Expression::RadialGradient { .. } => Type::Brush,
Expression::ConicGradient { .. } => Type::Brush,
Expression::EnumerationValue(value) => Type::Enumeration(value.enumeration.clone()),
Expression::Keys(_) => Type::Keys,
Expression::ReturnStatement(_) => Type::Invalid,
Expression::LayoutCacheAccess { .. } => Type::LogicalLength,
Expression::GridRepeaterCacheAccess { .. } => Type::LogicalLength,
Expression::OrganizeGridLayout(..) => Type::ArrayOfU16,
Expression::ComputeBoxLayoutInfo { .. } => typeregister::layout_info_type().into(),
Expression::ComputeGridLayoutInfo { .. } => typeregister::layout_info_type().into(),
Expression::SolveBoxLayout(..) => Type::LayoutCache,
Expression::SolveGridLayout { .. } => Type::LayoutCache,
Expression::SolveFlexboxLayout(..) => Type::LayoutCache,
Expression::ComputeFlexboxLayoutInfo { .. } => typeregister::layout_info_type().into(),
Expression::MinMax { ty, .. } => ty.clone(),
Expression::EmptyComponentFactory => Type::ComponentFactory,
Expression::DebugHook { expression, .. } => expression.ty(),
Expression::Closure { .. } => Type::Closure,
}
}
pub fn visit(&self, mut visitor: impl FnMut(&Self)) {
self.visit_dyn(&mut visitor)
}
fn visit_dyn(&self, visitor: &mut dyn FnMut(&Self)) {
match self {
Expression::Invalid => {}
Expression::Uncompiled(_) => {}
Expression::StringLiteral(_) => {}
Expression::NumberLiteral(_, _) => {}
Expression::BoolLiteral(_) => {}
Expression::PropertyReference { .. } => {}
Expression::FunctionParameterReference { .. } => {}
Expression::ElementReference(_) => {}
Expression::StructFieldAccess { base, .. } => visitor(base),
Expression::ArrayIndex { array, index } => {
visitor(array);
visitor(index);
}
Expression::RepeaterIndexReference { .. } => {}
Expression::RepeaterModelReference { .. } => {}
Expression::Cast { from, .. } => visitor(from),
Expression::CodeBlock(sub) => {
sub.iter().for_each(visitor);
}
Expression::FunctionCall { function: _, arguments, source_location: _ } => {
arguments.iter().for_each(visitor);
}
Expression::SelfAssignment { lhs, rhs, .. } => {
visitor(lhs);
visitor(rhs);
}
Expression::ImageReference { .. } => {}
Expression::Condition { condition, true_expr, false_expr, .. } => {
visitor(condition);
visitor(true_expr);
visitor(false_expr);
}
Expression::BinaryExpression { lhs, rhs, .. } => {
visitor(lhs);
visitor(rhs);
}
Expression::UnaryOp { sub, .. } => visitor(sub),
Expression::Array { values, .. } => {
for x in values {
visitor(x);
}
}
Expression::Struct { values, .. } => {
for x in values.values() {
visitor(x);
}
}
Expression::PathData(data) => match data {
Path::Elements(elements) => {
for element in elements {
element.bindings.values().for_each(|binding| visitor(&binding.borrow()))
}
}
Path::Events(events, coordinates) => {
events.iter().chain(coordinates.iter()).for_each(visitor);
}
Path::Commands(commands) => visitor(commands),
},
Expression::EmptyDataTransfer => {}
Expression::StoreLocalVariable { value, .. } => visitor(value),
Expression::ReadLocalVariable { .. } => {}
Expression::EasingCurve(_) => {}
Expression::MouseCursor(cursor) => match cursor {
MouseCursorInner::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
visitor(image);
visitor(hotspot_x);
visitor(hotspot_y);
}
MouseCursorInner::BuiltIn(e) => visitor(e),
},
Expression::LinearGradient { angle, stops } => {
visitor(angle);
for (c, s) in stops {
visitor(c);
visitor(s);
}
}
Expression::RadialGradient { center, radius, stops } => {
if let Some((cx, cy)) = center {
visitor(cx);
visitor(cy);
}
if let Some(r) = radius {
visitor(r);
}
for (c, s) in stops {
visitor(c);
visitor(s);
}
}
Expression::ConicGradient { from_angle, center, stops } => {
visitor(from_angle);
if let Some((cx, cy)) = center {
visitor(cx);
visitor(cy);
}
for (c, s) in stops {
visitor(c);
visitor(s);
}
}
Expression::EnumerationValue(_) => {}
Expression::Keys(_) => {}
Expression::ReturnStatement(expr) => {
expr.as_deref().map(visitor);
}
Expression::LayoutCacheAccess { repeater_index, .. } => {
repeater_index.as_deref().map(visitor);
}
Expression::GridRepeaterCacheAccess {
repeater_index,
stride,
inner_repeater_index,
..
} => {
visitor(repeater_index);
visitor(stride);
inner_repeater_index.as_deref().map(visitor);
}
Expression::OrganizeGridLayout(..) => {}
Expression::ComputeBoxLayoutInfo { cross_axis_size, .. }
| Expression::ComputeGridLayoutInfo { cross_axis_size, .. }
| Expression::ComputeFlexboxLayoutInfo { cross_axis_size, .. } => {
if let Some(cas) = cross_axis_size {
visitor(cas);
}
}
Expression::SolveBoxLayout(..) => {}
Expression::SolveGridLayout { .. } => {}
Expression::SolveFlexboxLayout(..) => {}
Expression::MinMax { lhs, rhs, .. } => {
visitor(lhs);
visitor(rhs);
}
Expression::EmptyComponentFactory => {}
Expression::DebugHook { expression, .. } => visitor(expression),
Expression::Closure { expression, .. } => visitor(expression),
}
}
pub fn visit_mut(&mut self, mut visitor: impl FnMut(&mut Self)) {
self.visit_mut_dyn(&mut visitor)
}
fn visit_mut_dyn(&mut self, visitor: &mut dyn FnMut(&mut Self)) {
match self {
Expression::Invalid => {}
Expression::Uncompiled(_) => {}
Expression::StringLiteral(_) => {}
Expression::NumberLiteral(_, _) => {}
Expression::BoolLiteral(_) => {}
Expression::PropertyReference { .. } => {}
Expression::FunctionParameterReference { .. } => {}
Expression::ElementReference(_) => {}
Expression::StructFieldAccess { base, .. } => visitor(base),
Expression::ArrayIndex { array, index } => {
visitor(array);
visitor(index);
}
Expression::RepeaterIndexReference { .. } => {}
Expression::RepeaterModelReference { .. } => {}
Expression::Cast { from, .. } => visitor(from),
Expression::CodeBlock(sub) => {
sub.iter_mut().for_each(visitor);
}
Expression::FunctionCall { function: _, arguments, source_location: _ } => {
arguments.iter_mut().for_each(visitor);
}
Expression::SelfAssignment { lhs, rhs, .. } => {
visitor(lhs);
visitor(rhs);
}
Expression::ImageReference { .. } => {}
Expression::Condition { condition, true_expr, false_expr, .. } => {
visitor(condition);
visitor(true_expr);
visitor(false_expr);
}
Expression::BinaryExpression { lhs, rhs, .. } => {
visitor(lhs);
visitor(rhs);
}
Expression::UnaryOp { sub, .. } => visitor(sub),
Expression::Array { values, .. } => {
for x in values {
visitor(x);
}
}
Expression::Struct { values, .. } => {
for x in values.values_mut() {
visitor(x);
}
}
Expression::PathData(data) => match data {
Path::Elements(elements) => {
for element in elements {
element
.bindings
.values_mut()
.for_each(|binding| visitor(&mut binding.borrow_mut()))
}
}
Path::Events(events, coordinates) => {
events.iter_mut().chain(coordinates.iter_mut()).for_each(visitor);
}
Path::Commands(commands) => visitor(commands),
},
Expression::EmptyDataTransfer => {}
Expression::StoreLocalVariable { value, .. } => visitor(value),
Expression::ReadLocalVariable { .. } => {}
Expression::EasingCurve(_) => {}
Expression::MouseCursor(cursor) => match cursor {
MouseCursorInner::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
visitor(image);
visitor(hotspot_x);
visitor(hotspot_y);
}
MouseCursorInner::BuiltIn(e) => visitor(e),
},
Expression::LinearGradient { angle, stops } => {
visitor(angle);
for (c, s) in stops {
visitor(c);
visitor(s);
}
}
Expression::RadialGradient { center, radius, stops } => {
if let Some((cx, cy)) = center {
visitor(cx);
visitor(cy);
}
if let Some(r) = radius {
visitor(r);
}
for (c, s) in stops {
visitor(c);
visitor(s);
}
}
Expression::ConicGradient { from_angle, center, stops } => {
visitor(from_angle);
if let Some((cx, cy)) = center {
visitor(cx);
visitor(cy);
}
for (c, s) in stops {
visitor(c);
visitor(s);
}
}
Expression::EnumerationValue(_) => {}
Expression::Keys(_) => {}
Expression::ReturnStatement(expr) => {
expr.as_deref_mut().map(visitor);
}
Expression::LayoutCacheAccess { repeater_index, .. } => {
repeater_index.as_deref_mut().map(visitor);
}
Expression::GridRepeaterCacheAccess {
repeater_index,
stride,
inner_repeater_index,
..
} => {
visitor(repeater_index);
visitor(stride);
inner_repeater_index.as_deref_mut().map(visitor);
}
Expression::OrganizeGridLayout(..) => {}
Expression::ComputeBoxLayoutInfo { cross_axis_size, .. }
| Expression::ComputeGridLayoutInfo { cross_axis_size, .. }
| Expression::ComputeFlexboxLayoutInfo { cross_axis_size, .. } => {
if let Some(cas) = cross_axis_size {
visitor(cas);
}
}
Expression::SolveBoxLayout(..) => {}
Expression::SolveGridLayout { .. } => {}
Expression::SolveFlexboxLayout(..) => {}
Expression::MinMax { lhs, rhs, .. } => {
visitor(lhs);
visitor(rhs);
}
Expression::EmptyComponentFactory => {}
Expression::DebugHook { expression, .. } => visitor(expression),
Expression::Closure { expression, .. } => visitor(expression),
}
}
pub fn visit_recursive(&self, visitor: &mut dyn FnMut(&Self)) {
visitor(self);
self.visit(|e| e.visit_recursive(visitor));
}
pub fn visit_recursive_mut(&mut self, visitor: &mut dyn FnMut(&mut Self)) {
visitor(self);
self.visit_mut(|e| e.visit_recursive_mut(visitor));
}
pub fn is_constant(&self, ga: Option<&crate::passes::GlobalAnalysis>) -> bool {
match self {
Expression::Invalid => true,
Expression::Uncompiled(_) => false,
Expression::StringLiteral(_) => true,
Expression::NumberLiteral(_, _) => true,
Expression::BoolLiteral(_) => true,
Expression::PropertyReference(nr) => nr.is_constant(),
Expression::ElementReference(_) => false,
Expression::RepeaterIndexReference { .. } => false,
Expression::RepeaterModelReference { .. } => false,
Expression::FunctionParameterReference { .. } => true,
Expression::StructFieldAccess { base, .. } => base.is_constant(ga),
Expression::ArrayIndex { array, index } => {
array.is_constant(ga) && index.is_constant(ga)
}
Expression::Cast { from, to } => {
if *to == Type::String
&& from.ty() == Type::Float32
&& !matches!(&**from, Expression::NumberLiteral(n, Unit::None)
if locale_independent_number_to_string(*n).is_some())
{
return false;
}
from.is_constant(ga)
}
Expression::CodeBlock(sub) => sub.iter().all(|s| s.is_constant(ga)),
Expression::FunctionCall { function, arguments, .. } => {
let is_const = match function {
Callable::Builtin(b) => b.is_const(ga),
Callable::Function(nr) => nr.is_constant(),
Callable::Callback(..) => false,
};
is_const && arguments.iter().all(|a| a.is_constant(ga))
}
Expression::SelfAssignment { .. } => false,
Expression::ImageReference { .. } => true,
Expression::Condition { condition, false_expr, true_expr, .. } => {
condition.is_constant(ga) && false_expr.is_constant(ga) && true_expr.is_constant(ga)
}
Expression::BinaryExpression { lhs, rhs, .. } => {
lhs.is_constant(ga) && rhs.is_constant(ga)
}
Expression::UnaryOp { sub, .. } => sub.is_constant(ga),
Expression::Array { .. } => false,
Expression::Struct { values, .. } => values.iter().all(|(_, v)| v.is_constant(ga)),
Expression::PathData(data) => match data {
Path::Elements(elements) => elements
.iter()
.all(|element| element.bindings.values().all(|v| v.borrow().is_constant(ga))),
Path::Events(_, _) => true,
Path::Commands(_) => false,
},
Expression::EmptyDataTransfer => true,
Expression::StoreLocalVariable { value, .. } => value.is_constant(ga),
Expression::ReadLocalVariable { .. } => true,
Expression::EasingCurve(_) => true,
Expression::MouseCursor(cursor) => match cursor {
MouseCursorInner::BuiltIn(cursor) => cursor.is_constant(ga),
MouseCursorInner::CustomMouseCursor { image, hotspot_x, hotspot_y } => {
image.is_constant(ga) && hotspot_x.is_constant(ga) && hotspot_y.is_constant(ga)
}
},
Expression::LinearGradient { angle, stops } => {
angle.is_constant(ga)
&& stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
}
Expression::RadialGradient { center, radius, stops } => {
center.as_ref().is_none_or(|(cx, cy)| cx.is_constant(ga) && cy.is_constant(ga))
&& radius.as_ref().is_none_or(|r| r.is_constant(ga))
&& stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
}
Expression::ConicGradient { from_angle, center, stops } => {
from_angle.is_constant(ga)
&& center
.as_ref()
.is_none_or(|(cx, cy)| cx.is_constant(ga) && cy.is_constant(ga))
&& stops.iter().all(|(c, s)| c.is_constant(ga) && s.is_constant(ga))
}
Expression::EnumerationValue(_) => true,
Expression::Keys(_) => true,
Expression::ReturnStatement(expr) => {
expr.as_ref().is_none_or(|expr| expr.is_constant(ga))
}
Expression::LayoutCacheAccess { .. } => false,
Expression::GridRepeaterCacheAccess { .. } => false,
Expression::OrganizeGridLayout { .. } => false,
Expression::ComputeBoxLayoutInfo { .. } => false,
Expression::ComputeGridLayoutInfo { .. } => false,
Expression::SolveBoxLayout(..) => false,
Expression::SolveGridLayout { .. } => false,
Expression::SolveFlexboxLayout(..) => false,
Expression::ComputeFlexboxLayoutInfo { .. } => false,
Expression::MinMax { lhs, rhs, .. } => lhs.is_constant(ga) && rhs.is_constant(ga),
Expression::EmptyComponentFactory => true,
Expression::DebugHook { .. } => false,
Expression::Closure { expression, .. } => expression.is_constant(ga),
}
}
#[must_use]
pub fn maybe_convert_to(
self,
target_type: Type,
node: &dyn Spanned,
diag: &mut BuildDiagnostics,
symbol_counters: &SymbolCounters,
) -> Expression {
let ty = self.ty();
if let Expression::Condition { .. } = self
&& ty == Type::Void
{
self
} else if ty == target_type
|| target_type == Type::Void
|| target_type == Type::Invalid
|| ty == Type::Invalid
{
self
} else if ty.can_convert(&target_type) {
let from = match (ty, &target_type) {
(Type::Brush, Type::Color) => match self {
Expression::LinearGradient { .. }
| Expression::RadialGradient { .. }
| Expression::ConicGradient { .. } => {
let message = format!(
"Narrowing conversion from {0} to {1}. This can lead to unexpected behavior because the {0} is a gradient",
Type::Brush,
Type::Color
);
diag.push_warning(message, node);
self
}
_ => self,
},
(Type::Percent, Type::Float32) => Expression::BinaryExpression {
lhs: Box::new(self),
rhs: Box::new(Expression::NumberLiteral(0.01, Unit::None)),
op: '*',
source_location: None,
},
(ref from_ty @ Type::Struct(ref left), Type::Struct(right))
if left.fields != right.fields =>
{
#[cfg(feature = "slint-sc")]
if diag.slint_sc {
for f in left.fields.keys() {
if !right.fields.contains_key(f) {
diag.slint_sc_error(
&format!("Providing the extra struct member '{f}' is"),
node,
);
}
}
for f in right.fields.keys() {
if !left.fields.contains_key(f) {
diag.slint_sc_error(
&format!("Omitting the struct field '{f}' is"),
node,
);
}
}
}
if !diag.is_slint_sc() {
let extra = left
.fields
.keys()
.filter(|f| !right.fields.contains_key(*f))
.map(|f| format!("'{f}'"))
.collect::<Vec<_>>();
if let Some((last, rest)) = extra.split_last() {
let (noun, list) = match rest {
[] => ("field", last.clone()),
_ => ("fields", format!("{} and {last}", rest.join(", "))),
};
diag.push_warning(
format!(
"Conversion to {target_type} ignores the extra {noun} {list}"
),
node,
);
}
}
if let Expression::Struct { mut values, .. } = self {
let mut new_values = BTreeMap::new();
for (key, ty) in &right.fields {
let (key, expression) = values.remove_entry(key).map_or_else(
|| (key.clone(), right.default_value_for_field(key)),
|(k, e)| {
(k, e.maybe_convert_to(ty.clone(), node, diag, symbol_counters))
},
);
new_values.insert(key, expression);
}
return Expression::Struct { values: new_values, ty: right.clone() };
}
let var_name = symbol_counters.generate_name("tmpobj_conv_");
let mut new_values = BTreeMap::new();
for (key, ty) in &right.fields {
let expression = if left.fields.contains_key(key) {
Expression::StructFieldAccess {
base: Box::new(Expression::ReadLocalVariable {
name: var_name.clone(),
ty: from_ty.clone(),
}),
name: key.clone(),
}
.maybe_convert_to(
ty.clone(),
node,
diag,
symbol_counters,
)
} else {
right.default_value_for_field(key)
};
new_values.insert(key.clone(), expression);
}
return Expression::CodeBlock(vec![
Expression::StoreLocalVariable { name: var_name, value: Box::new(self) },
Expression::Struct { values: new_values, ty: right.clone() },
]);
}
(left, right) => match (left.as_unit_product(), right.as_unit_product()) {
(Some(left), Some(right)) => {
if let Some(conversion_powers) =
crate::langtype::unit_product_length_conversion(&left, &right)
{
let apply_power =
|mut result, power: i8, builtin_fn: BuiltinFunction| {
let op = if power < 0 { '*' } else { '/' };
for _ in 0..power.abs() {
result = Expression::BinaryExpression {
source_location: None,
lhs: Box::new(result),
rhs: Box::new(Expression::FunctionCall {
function: Callable::Builtin(builtin_fn.clone()),
arguments: Vec::new(),
source_location: Some(node.to_source_location()),
}),
op,
}
}
result
};
let mut result = self;
if conversion_powers.rem_to_px_power != 0 {
result = apply_power(
result,
conversion_powers.rem_to_px_power,
BuiltinFunction::GetWindowDefaultFontSize,
)
}
if conversion_powers.px_to_phx_power != 0 {
result = apply_power(
result,
conversion_powers.px_to_phx_power,
BuiltinFunction::GetWindowScaleFactor,
)
}
result
} else {
self
}
}
_ => self,
},
};
Expression::Cast { from: Box::new(from), to: target_type }
} else if matches!(
(&ty, &target_type, &self),
(Type::Array(_), Type::Array(_), Expression::Array { .. })
) {
match (self, target_type) {
(Expression::Array { values, .. }, Type::Array(target_type)) => Expression::Array {
values: values
.into_iter()
.map(|e| {
e.maybe_convert_to((*target_type).clone(), node, diag, symbol_counters)
})
.take_while(|e| !matches!(e, Expression::Invalid))
.collect(),
element_ty: (*target_type).clone(),
},
_ => unreachable!(),
}
} else if let (Type::Struct(target_struct_type), Expression::Struct { values, .. }) =
(&target_type, &self)
{
let mut target_fields = target_struct_type.fields.clone();
let mut new_values = BTreeMap::new();
for (f, v) in values {
if let Some(t) = target_fields.remove(f) {
new_values.insert(
f.clone(),
v.clone().maybe_convert_to(t, node, diag, symbol_counters),
);
} else {
let available_fields_message = if target_struct_type.name.slint_name().is_some()
{
let available_fields = target_struct_type
.fields
.keys()
.map(SmolStr::as_str)
.collect::<Vec<_>>()
.join("', '");
format!(". Available fields: '{available_fields}'")
} else {
String::new()
};
diag.push_error(
format!("Cannot convert {ty} to {target_type}: Field '{f}' not found{available_fields_message}"),
node,
);
return self;
}
}
for f in target_fields.into_keys() {
let default_value = target_struct_type.default_value_for_field(&f);
new_values.insert(f, default_value);
}
Expression::Struct { ty: target_struct_type.clone(), values: new_values }
} else if let Expression::Condition { condition, true_expr, false_expr, source_location } =
self
{
let true_expr_converted = true_expr.clone().maybe_convert_to(
target_type.clone(),
node,
diag,
symbol_counters,
);
if true_expr_converted.ty() != target_type.clone() {
Expression::Condition { condition, true_expr, false_expr, source_location }
} else {
Expression::Condition {
condition,
source_location,
true_expr: Box::new(true_expr_converted),
false_expr: Box::new(false_expr.maybe_convert_to(
target_type,
node,
diag,
symbol_counters,
)),
}
}
} else {
let mut message = format!("Cannot convert {ty} to {target_type}");
if let Some(from_unit) = ty.default_unit() {
if matches!(&target_type, Type::Int32 | Type::Float32 | Type::String) {
message =
format!("{message}. Divide by 1{from_unit} to convert to a plain number");
}
} else if matches!(target_type, Type::StyledText) && ty.can_convert(&Type::String) {
message = format!(
"{message}. Wrap the expression in `@markdown(\"\\{{...}}\")` to convert it explicitly"
);
} else if let Some(to_unit) = target_type.default_unit()
&& matches!(ty, Type::Int32 | Type::Float32)
{
if let Expression::NumberLiteral(value, Unit::None) = self
&& value == 0.
{
return Expression::NumberLiteral(0., to_unit);
}
message = format!(
"{message}. Use an unit, or multiply by 1{to_unit} to convert explicitly"
);
}
diag.push_error(message, node);
self
}
}
pub fn default_value_for_type(ty: &Type) -> Expression {
match ty {
Type::Invalid
| Type::Callback { .. }
| Type::Function { .. }
| Type::InferredProperty
| Type::InferredCallback
| Type::ElementReference
| Type::LayoutCache
| Type::ArrayOfU16 => Expression::Invalid,
Type::Void => Expression::CodeBlock(Vec::new()),
Type::DataTransfer => Expression::EmptyDataTransfer,
Type::Float32 => Expression::NumberLiteral(0., Unit::None),
Type::String => Expression::StringLiteral(SmolStr::default()),
Type::Int32 | Type::Color | Type::UnitProduct(_) => Expression::Cast {
from: Box::new(Expression::NumberLiteral(0., Unit::None)),
to: ty.clone(),
},
Type::Duration => Expression::NumberLiteral(0., Unit::Ms),
Type::Angle => Expression::NumberLiteral(0., Unit::Deg),
Type::PhysicalLength => Expression::NumberLiteral(0., Unit::Phx),
Type::LogicalLength => Expression::NumberLiteral(0., Unit::Px),
Type::Rem => Expression::NumberLiteral(0., Unit::Rem),
Type::Percent => Expression::NumberLiteral(100., Unit::Percent),
Type::Image => Expression::ImageReference {
resource_ref: ImageReference::None,
source_location: None,
nine_slice: None,
},
Type::Bool => Expression::BoolLiteral(false),
Type::Model => Expression::Invalid,
Type::PathData => Expression::PathData(Path::Elements(Vec::new())),
Type::Array(element_ty) => {
Expression::Array { element_ty: (**element_ty).clone(), values: Vec::new() }
}
Type::Struct(s) => Expression::Struct {
ty: s.clone(),
values: s
.fields
.keys()
.map(|k| (k.clone(), s.default_value_for_field(k)))
.collect(),
},
Type::Easing => Expression::EasingCurve(EasingCurve::default()),
Type::MouseCursor => {
let e = crate::typeregister::BUILTIN.enums.BuiltInMouseCursor.clone();
Expression::MouseCursor(MouseCursorInner::BuiltIn(Box::new(
Expression::EnumerationValue(e.default_value()),
)))
}
Type::Brush => Expression::Cast {
from: Box::new(Expression::default_value_for_type(&Type::Color)),
to: Type::Brush,
},
Type::Enumeration(enumeration) => {
Expression::EnumerationValue(enumeration.clone().default_value())
}
Type::Keys => Expression::Keys(Keys::default()),
Type::ComponentFactory => Expression::EmptyComponentFactory,
Type::StyledText => Expression::FunctionCall {
function: Callable::Builtin(BuiltinFunction::StringToStyledText),
arguments: vec![Self::default_value_for_type(&Type::String)],
source_location: None,
},
Type::Closure => Expression::Invalid,
}
}
pub fn try_set_rw(
&mut self,
ctx: &mut LookupCtx,
what: &'static str,
node: &dyn Spanned,
) -> bool {
match self {
Expression::PropertyReference(nr) => {
nr.mark_as_set();
let mut lookup = nr
.element()
.borrow()
.lookup_property(nr.name(), PropertyLookupMode::InternalName);
lookup.is_local_to_component &= ctx.is_local_element(&nr.element());
if lookup.property_visibility == PropertyVisibility::Constexpr {
ctx.diag.push_error(
"The property must be known at compile time and cannot be changed at runtime"
.into(),
node,
);
false
} else if lookup.is_valid_for_assignment() {
if !nr
.element()
.borrow()
.property_analysis
.borrow()
.get(nr.name())
.is_some_and(|d| d.is_linked_to_read_only)
{
true
} else if ctx.is_legacy_component() {
ctx.diag.push_warning("Modifying a property that is linked to a read-only property is deprecated".into(), node);
true
} else {
ctx.diag.push_error(
"Cannot modify a property that is linked to a read-only property"
.into(),
node,
);
false
}
} else if ctx.is_legacy_component()
&& lookup.property_visibility == PropertyVisibility::Output
{
ctx.diag.push_warning(
format!(
"{what} on an '{}' property is deprecated",
PropertyVisibility::Output
),
node,
);
true
} else {
ctx.diag.push_error(
format!("{what} on an '{}' property", lookup.property_visibility),
node,
);
false
}
}
Expression::StructFieldAccess { base, .. } => base.try_set_rw(ctx, what, node),
Expression::RepeaterModelReference { .. } => true,
Expression::ArrayIndex { array, .. } => array.try_set_rw(ctx, what, node),
_ => {
ctx.diag.push_error(format!("{what} needs to be done on a property"), node);
false
}
}
}
pub fn ignore_debug_hooks(&self) -> &Expression {
match self {
Expression::DebugHook { expression, .. } => expression.as_ref(),
_ => self,
}
}
pub fn ignore_debug_hooks_mut(&mut self) -> &mut Expression {
match self {
Expression::DebugHook { expression, .. } => expression.as_mut(),
_ => self,
}
}
pub fn is_synthetic_debug_hook(&self) -> bool {
matches!(self, Expression::DebugHook { synthetic: true, .. })
}
}
fn model_inner_type(model: &Expression) -> Type {
match model {
Expression::Cast { from, to: Type::Model } => model_inner_type(from),
Expression::CodeBlock(cb) => cb.last().map_or(Type::Invalid, model_inner_type),
_ => match model.ty() {
Type::Float32 | Type::Int32 => Type::Int32,
Type::Array(elem) => (*elem).clone(),
_ => Type::Invalid,
},
}
}
pub fn locale_independent_number_to_string(n: f64) -> Option<SmolStr> {
let string = format_smolstr!("{}", i_slint_common::FormattedNumber(n));
(!string.contains('.')).then_some(string)
}
#[derive(Clone, Debug)]
pub enum TwoWayBinding {
Property {
property: NamedReference,
field_access: Vec<SmolStr>,
},
ModelData {
repeated_element: ElementWeak,
field_access: Vec<SmolStr>,
},
}
impl TwoWayBinding {
pub fn ty(&self) -> Type {
let (mut ty, field_access) = match self {
Self::Property { property, field_access } => (property.ty(), field_access),
Self::ModelData { repeated_element, field_access } => {
let ty =
Expression::RepeaterModelReference { element: repeated_element.clone() }.ty();
(ty, field_access)
}
};
for x in field_access {
ty = match ty {
Type::InferredProperty | Type::InferredCallback => return ty,
Type::Struct(s) => s.fields.get(x).cloned().unwrap_or_default(),
_ => return Type::Invalid,
};
}
ty
}
pub fn is_constant(&self) -> bool {
match self {
Self::Property { property, .. } => property.is_constant(),
Self::ModelData { .. } => false,
}
}
pub fn property(&self) -> Option<&NamedReference> {
match self {
Self::Property { property, .. } => Some(property),
Self::ModelData { .. } => None,
}
}
}
impl From<NamedReference> for TwoWayBinding {
fn from(nr: NamedReference) -> Self {
Self::Property { property: nr, field_access: Vec::new() }
}
}
#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
pub struct BindingExpression {
#[deref]
#[deref_mut]
pub expression: Expression,
pub span: Option<SourceLocation>,
pub priority: i32,
pub animation: Option<PropertyAnimation>,
pub analysis: Option<BindingAnalysis>,
pub two_way_bindings: Vec<TwoWayBinding>,
}
impl std::convert::From<Expression> for BindingExpression {
fn from(expression: Expression) -> Self {
Self {
expression,
span: None,
priority: 0,
animation: Default::default(),
analysis: Default::default(),
two_way_bindings: Default::default(),
}
}
}
impl BindingExpression {
pub fn new_uncompiled(node: SyntaxNode) -> Self {
Self {
expression: Expression::Uncompiled(node.clone()),
span: Some(node.to_source_location()),
priority: 1,
animation: Default::default(),
analysis: Default::default(),
two_way_bindings: Default::default(),
}
}
pub fn new_with_span(expression: Expression, span: SourceLocation) -> Self {
Self {
expression,
span: Some(span),
priority: 0,
animation: Default::default(),
analysis: Default::default(),
two_way_bindings: Default::default(),
}
}
pub fn new_two_way(other: TwoWayBinding) -> Self {
Self {
expression: Expression::Invalid,
span: None,
priority: 0,
animation: Default::default(),
analysis: Default::default(),
two_way_bindings: vec![other],
}
}
pub fn merge_with(&mut self, other: &Self) -> bool {
if self.animation.is_none() {
self.animation.clone_from(&other.animation);
}
let has_binding = self.has_binding();
self.two_way_bindings.extend_from_slice(&other.two_way_bindings);
if has_binding {
return false;
}
if let Expression::DebugHook { expression, synthetic, .. } = &mut self.expression {
debug_assert!(*synthetic, "has_binding() returned false for a non-synthetic hook");
if !matches!(other.expression, Expression::Invalid)
&& !other.expression.is_synthetic_debug_hook()
{
**expression = other.expression.clone();
*synthetic = false;
self.priority = other.priority;
return true;
}
if self.two_way_bindings.is_empty() {
return false;
}
self.expression = Expression::Invalid;
self.priority = other.priority;
return true;
}
self.priority = other.priority;
self.expression = other.expression.clone();
true
}
pub fn has_binding(&self) -> bool {
(!matches!(self.expression, Expression::Invalid)
&& !self.expression.is_synthetic_debug_hook())
|| !self.two_way_bindings.is_empty()
}
pub fn value_expression(&self) -> &Expression {
self.expression.ignore_debug_hooks()
}
pub fn set_value_expression(&mut self, expr: Expression) {
match &mut self.expression {
Expression::DebugHook { expression, synthetic, .. } if *synthetic => {
**expression = expr;
*synthetic = false;
}
expression => *expression = expr,
}
}
}
impl Spanned for BindingExpression {
fn span(&self) -> crate::diagnostics::Span {
self.span.as_ref().map(|x| x.span()).unwrap_or_default()
}
fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
self.span.as_ref().and_then(|x| x.source_file())
}
}
#[derive(Default, Debug, Clone)]
pub struct BindingAnalysis {
pub is_in_binding_loop: Cell<bool>,
pub is_const: bool,
pub no_external_dependencies: bool,
}
#[derive(Debug, Clone)]
pub enum Path {
Elements(Vec<PathElement>),
Events(Vec<Expression>, Vec<Expression>),
Commands(Box<Expression>), }
#[derive(Debug, Clone)]
pub struct PathElement {
pub element_type: Rc<BuiltinElement>,
pub bindings: BindingsMap,
}
#[derive(Clone, Debug, Default)]
pub enum EasingCurve {
#[default]
Linear,
CubicBezier(f32, f32, f32, f32),
EaseInElastic,
EaseOutElastic,
EaseInOutElastic,
EaseInBounce,
EaseOutBounce,
EaseInOutBounce,
Spring(f32),
}
#[derive(Clone, Debug)]
pub enum MouseCursorInner<E = Expression> {
BuiltIn(Box<E>),
CustomMouseCursor { image: Box<E>, hotspot_x: Box<E>, hotspot_y: Box<E> },
}
impl<E: Default> Default for MouseCursorInner<E> {
fn default() -> Self {
Self::BuiltIn(Box::default())
}
}
#[derive(Clone, Debug)]
pub enum ImageReference {
None,
Path(SmolStr),
Url(url::Url),
DataUri(SmolStr),
EmbeddedData {
resource_id: crate::embedded_resources::EmbeddedResourcesIdx,
extension: String,
},
EmbeddedTexture {
resource_id: crate::embedded_resources::EmbeddedResourcesIdx,
},
}
impl ImageReference {
pub fn from_resolved(reference: SmolStr) -> Self {
if reference.starts_with("data:") {
return Self::DataUri(reference);
}
match url::Url::parse(&reference) {
Ok(url) if url.scheme().len() > 1 => Self::Url(url),
_ => Self::Path(reference),
}
}
pub fn from_mapped_url(url: url::Url) -> Self {
if url.scheme() == "data" { Self::DataUri(url.as_str().into()) } else { Self::Url(url) }
}
pub fn source(&self) -> Option<&str> {
match self {
Self::Path(source) | Self::DataUri(source) => Some(source),
Self::Url(url) => Some(url.as_str()),
Self::None | Self::EmbeddedData { .. } | Self::EmbeddedTexture { .. } => None,
}
}
}
pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std::fmt::Result {
match expression {
Expression::Invalid => write!(f, "<invalid>"),
Expression::Uncompiled(u) => write!(f, "{u:?}"),
Expression::StringLiteral(s) => write!(f, "{s:?}"),
Expression::NumberLiteral(vl, unit) => write!(f, "{vl}{unit}"),
Expression::BoolLiteral(b) => write!(f, "{b:?}"),
Expression::PropertyReference(a) => write!(f, "{a:?}"),
Expression::ElementReference(a) => write!(f, "{a:?}"),
Expression::RepeaterIndexReference { element } => {
crate::namedreference::pretty_print_element_ref(f, element)
}
Expression::RepeaterModelReference { element } => {
crate::namedreference::pretty_print_element_ref(f, element)?;
write!(f, ".@model")
}
Expression::FunctionParameterReference { index, ty: _ } => write!(f, "_arg_{index}"),
Expression::StoreLocalVariable { name, value } => {
write!(f, "{name} = ")?;
pretty_print(f, value)
}
Expression::ReadLocalVariable { name, ty: _ } => write!(f, "{name}"),
Expression::StructFieldAccess { base, name } => {
pretty_print(f, base)?;
write!(f, ".{name}")
}
Expression::ArrayIndex { array, index } => {
pretty_print(f, array)?;
write!(f, "[")?;
pretty_print(f, index)?;
write!(f, "]")
}
Expression::Cast { from, to } => {
write!(f, "(")?;
pretty_print(f, from)?;
write!(f, "/* as {to} */)")
}
Expression::CodeBlock(c) => {
write!(f, "{{ ")?;
for e in c {
pretty_print(f, e)?;
write!(f, "; ")?;
}
write!(f, "}}")
}
Expression::FunctionCall { function, arguments, source_location: _ } => {
match function {
Callable::Builtin(b) => write!(f, "{b:?}")?,
Callable::Callback(nr) | Callable::Function(nr) => write!(f, "{nr:?}")?,
}
write!(f, "(")?;
for e in arguments {
pretty_print(f, e)?;
write!(f, ", ")?;
}
write!(f, ")")
}
Expression::SelfAssignment { lhs, rhs, op, .. } => {
pretty_print(f, lhs)?;
write!(f, " {}= ", if *op == '=' { ' ' } else { *op })?;
pretty_print(f, rhs)
}
Expression::BinaryExpression { lhs, rhs, op, .. } => {
write!(f, "(")?;
pretty_print(f, lhs)?;
match *op {
'=' | '!' => write!(f, " {op}= ")?,
_ => write!(f, " {op} ")?,
};
pretty_print(f, rhs)?;
write!(f, ")")
}
Expression::UnaryOp { sub, op } => {
write!(f, "{op}")?;
pretty_print(f, sub)
}
Expression::ImageReference { resource_ref, .. } => write!(f, "{resource_ref:?}"),
Expression::Condition { condition, true_expr, false_expr, .. } => {
write!(f, "if (")?;
pretty_print(f, condition)?;
write!(f, ") {{ ")?;
pretty_print(f, true_expr)?;
write!(f, " }} else {{ ")?;
pretty_print(f, false_expr)?;
write!(f, " }}")
}
Expression::Array { element_ty: _, values } => {
write!(f, "[")?;
for e in values {
pretty_print(f, e)?;
write!(f, ", ")?;
}
write!(f, "]")
}
Expression::Struct { ty: _, values } => {
write!(f, "{{ ")?;
for (name, e) in values {
write!(f, "{name}: ")?;
pretty_print(f, e)?;
write!(f, ", ")?;
}
write!(f, " }}")
}
Expression::PathData(data) => write!(f, "{data:?}"),
Expression::EmptyDataTransfer => write!(f, "{{ }}"),
Expression::EasingCurve(e) => write!(f, "{e:?}"),
Expression::MouseCursor(m) => write!(f, "{m:?}"),
Expression::LinearGradient { angle, stops } => {
write!(f, "@linear-gradient(")?;
pretty_print(f, angle)?;
for (c, s) in stops {
write!(f, ", ")?;
pretty_print(f, c)?;
write!(f, " ")?;
pretty_print(f, s)?;
}
write!(f, ")")
}
Expression::RadialGradient { center, radius, stops } => {
write!(f, "@radial-gradient(circle")?;
if let Some(r) = radius {
write!(f, " ")?;
pretty_print(f, r)?;
}
if let Some((cx, cy)) = center {
write!(f, " at ")?;
pretty_print(f, cx)?;
write!(f, " ")?;
pretty_print(f, cy)?;
}
for (c, s) in stops {
write!(f, ", ")?;
pretty_print(f, c)?;
write!(f, " ")?;
pretty_print(f, s)?;
}
write!(f, ")")
}
Expression::ConicGradient { from_angle, center, stops } => {
write!(f, "@conic-gradient(from ")?;
pretty_print(f, from_angle)?;
if let Some((cx, cy)) = center {
write!(f, " at ")?;
pretty_print(f, cx)?;
write!(f, " ")?;
pretty_print(f, cy)?;
}
for (c, s) in stops {
write!(f, ", ")?;
pretty_print(f, c)?;
write!(f, " ")?;
pretty_print(f, s)?;
}
write!(f, ")")
}
Expression::EnumerationValue(e) => match e.enumeration.values.get(e.value) {
Some(val) => write!(f, "{}.{}", e.enumeration.name, val),
None => write!(f, "{}.{}", e.enumeration.name, e.value),
},
Expression::Keys(keys) => {
write!(f, "@keys({keys})")
}
Expression::ReturnStatement(e) => {
write!(f, "return ")?;
e.as_ref().map(|e| pretty_print(f, e)).unwrap_or(Ok(()))
}
Expression::LayoutCacheAccess {
layout_cache_prop,
index,
repeater_index,
entries_per_item,
} => {
if repeater_index.is_some() {
write!(
f,
"{:?}[{:?}[{}] + $repeater_index * {}]",
layout_cache_prop, layout_cache_prop, index, entries_per_item
)
} else {
write!(f, "{:?}[{}]", layout_cache_prop, index)
}
}
Expression::GridRepeaterCacheAccess {
layout_cache_prop,
index,
repeater_index: _,
stride: _,
child_offset,
inner_repeater_index,
entries_per_item,
} => {
if inner_repeater_index.is_some() {
write!(
f,
"{0:?}[{0:?}[{1}] + $repeater_index * $stride + {2} + $inner_repeater_index * {3}]",
layout_cache_prop, index, child_offset, entries_per_item
)
} else {
write!(
f,
"{0:?}[{0:?}[{1}] + $repeater_index * $stride + {2}]",
layout_cache_prop, index, child_offset
)
}
}
Expression::OrganizeGridLayout(..) => write!(f, "organize_grid_layout(..)"),
Expression::ComputeBoxLayoutInfo { .. } => write!(f, "layout_info(..)"),
Expression::ComputeGridLayoutInfo { .. } => write!(f, "grid_layout_info(..)"),
Expression::SolveBoxLayout(..) => write!(f, "solve_box_layout(..)"),
Expression::SolveGridLayout { .. } => write!(f, "solve_grid_layout(..)"),
Expression::SolveFlexboxLayout(..) => write!(f, "solve_flexbox_layout(..)"),
Expression::ComputeFlexboxLayoutInfo { .. } => write!(f, "flexbox_layout_info(..)"),
Expression::MinMax { ty: _, op, lhs, rhs } => {
match op {
MinMaxOp::Min => write!(f, "min(")?,
MinMaxOp::Max => write!(f, "max(")?,
}
pretty_print(f, lhs)?;
write!(f, ", ")?;
pretty_print(f, rhs)?;
write!(f, ")")
}
Expression::EmptyComponentFactory => write!(f, "<empty-component-factory>"),
Expression::DebugHook { expression, id, synthetic } => {
write!(f, "debug-hook(")?;
pretty_print(f, expression)?;
if *synthetic {
write!(f, " SYNTHETIC")?;
}
write!(f, "\"{id}\")")
}
Expression::Closure { arg_name, expression } => {
let display_name = arg_name.strip_prefix("local_").unwrap_or(arg_name);
write!(f, "({display_name}) => ")?;
pretty_print(f, expression)
}
}
}