use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
use crate::expression_tree::{
self, BindingExpression, Callable, ConditionLocation, Expression, Unit,
};
use crate::langtype::{
BuiltinElement, Enumeration, EnumerationValue, Function, NativeClass, Struct, StructName, Type,
};
use crate::langtype::{ElementType, PropertyLookupMode, PropertyLookupResult};
use crate::layout::{LayoutConstraints, Orientation};
use crate::namedreference::NamedReference;
use crate::parser::{SyntaxKind, SyntaxNode, syntax_nodes};
use crate::typeloader::{ImportKind, ImportedTypes, LibraryInfo};
use crate::typeregister::TypeRegister;
use crate::{parser, reject_experimental_feature};
use itertools::Either;
use smol_str::{SmolStr, ToSmolStr, format_smolstr};
use std::cell::{Cell, OnceCell, Ref, RefCell, RefMut};
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt::Display;
use std::path::PathBuf;
use std::rc::{Rc, Weak};
use std::sync::Arc;
pub(crate) mod forward_inherited_expression;
mod interfaces;
macro_rules! unwrap_or_continue {
($e:expr ; $diag:expr) => {
match $e {
Some(x) => x,
None => {
debug_assert!($diag.has_errors()); continue;
}
}
};
}
#[derive(Default)]
pub struct Document {
pub node: Option<syntax_nodes::Document>,
pub inner_components: Vec<Rc<Component>>,
pub inner_types: Vec<Type>,
pub local_registry: TypeRegister,
pub custom_fonts: Vec<(SmolStr, crate::parser::SyntaxToken)>,
pub exports: Exports,
pub imports: Vec<ImportedTypes>,
pub library_exports: HashMap<String, LibraryInfo>,
pub embedded_file_resources: RefCell<
typed_index_collections::TiVec<
crate::embedded_resources::EmbeddedResourcesIdx,
crate::embedded_resources::EmbeddedResources,
>,
>,
#[cfg(feature = "bundle-translations")]
pub translation_builder: Option<crate::translations::TranslationsBuilder>,
pub used_types: RefCell<UsedSubTypes>,
pub popup_menu_impl: Option<Rc<Component>>,
}
impl Document {
pub fn from_node(
node: syntax_nodes::Document,
imports: Vec<ImportedTypes>,
reexports: Exports,
diag: &mut BuildDiagnostics,
parent_registry: &Rc<RefCell<TypeRegister>>,
ignore_missing_font_files: bool,
symbol_counters: &Rc<crate::symbol_counters::SymbolCounters>,
) -> Self {
debug_assert_eq!(node.kind(), SyntaxKind::Document);
let mut local_registry = TypeRegister::new(parent_registry);
let mut inner_components = Vec::new();
let mut inner_types = Vec::new();
#[cfg(feature = "slint-sc")]
for import in &imports {
match import.import_kind {
ImportKind::ImportList(_) => {}
ImportKind::FileImport => {
diag.slint_sc_error("File imports are", &import.import_uri_token)
}
ImportKind::ModuleReexport(_) => {
diag.slint_sc_error("Re-exports are", &import.import_uri_token)
}
}
}
let mut process_component =
|n: syntax_nodes::Component,
diag: &mut BuildDiagnostics,
local_registry: &mut TypeRegister| {
let compo = Component::from_node(n, diag, local_registry);
if !local_registry.add(compo.clone()) {
diag.push_warning(format!("Component '{}' is replacing a previously defined component with the same name", compo.id), &compo.node.clone().unwrap().DeclaredIdentifier());
}
inner_components.push(compo);
};
let process_struct = |n: syntax_nodes::StructDeclaration,
diag: &mut BuildDiagnostics,
local_registry: &mut TypeRegister,
inner_types: &mut Vec<Type>| {
let ty = type_struct_from_node(
n.ObjectType(),
diag,
local_registry,
parser::identifier_text(&n.DeclaredIdentifier()),
Some(symbol_counters),
);
assert!(matches!(ty, Type::Struct(_)));
if !local_registry.insert_type(ty.clone()) {
diag.push_warning(
format!(
"Struct '{ty}' is replacing a previously defined type with the same name"
),
&n.DeclaredIdentifier(),
);
}
inner_types.push(ty);
};
let process_enum = |n: syntax_nodes::EnumDeclaration,
diag: &mut BuildDiagnostics,
local_registry: &mut TypeRegister,
inner_types: &mut Vec<Type>| {
let Some(name) = parser::identifier_text(&n.DeclaredIdentifier()) else {
assert!(diag.has_errors());
return;
};
let mut existing_names = HashSet::new();
let values = n
.EnumValue()
.filter_map(|v| {
let value = parser::identifier_text(&v)?;
if value == name {
diag.push_error(
format!("Enum '{value}' can't have a value with the same name"),
&v,
);
None
} else if !existing_names.insert(crate::generator::to_pascal_case(&value)) {
diag.push_error(format!("Duplicated enum value '{value}'"), &v);
None
} else {
Some(value)
}
})
.collect();
let en = Enumeration {
name: name.clone(),
values,
default_value: 0,
node: Some(n.to_source_location()),
rust_attributes: n
.AtRustAttr()
.map(|a| SmolStr::from(a.text().to_string()))
.collect(),
};
if en.values.is_empty() {
diag.push_error("Enums must have at least one value".into(), &n);
}
let ty = Type::Enumeration(Arc::new(en));
if !local_registry.insert_type_with_name(ty.clone(), name.clone()) {
diag.push_warning(
format!(
"Enum '{name}' is replacing a previously defined type with the same name"
),
&n.DeclaredIdentifier(),
);
}
inner_types.push(ty);
};
for n in node.children() {
match n.kind() {
SyntaxKind::Component => {
process_component(n.into(), diag, &mut local_registry);
}
SyntaxKind::StructDeclaration => {
process_struct(n.into(), diag, &mut local_registry, &mut inner_types)
}
SyntaxKind::EnumDeclaration => {
process_enum(n.into(), diag, &mut local_registry, &mut inner_types)
}
SyntaxKind::ExportsList => {
for n in n.children() {
match n.kind() {
SyntaxKind::Component => {
process_component(n.into(), diag, &mut local_registry)
}
SyntaxKind::StructDeclaration => process_struct(
n.into(),
diag,
&mut local_registry,
&mut inner_types,
),
SyntaxKind::EnumDeclaration => {
process_enum(n.into(), diag, &mut local_registry, &mut inner_types)
}
_ => {}
}
}
}
_ => {}
};
}
let mut exports = Exports::from_node(&node, &inner_components, &local_registry, diag);
exports.add_reexports(reexports, diag);
let custom_fonts = imports
.iter()
.filter(|import| matches!(import.import_kind, ImportKind::FileImport))
.filter_map(|import| {
if crate::pathutils::is_font_file(&import.file) {
let token_path = import.import_uri_token.source_file.path();
let import_file_path = PathBuf::from(import.file.clone());
let import_file_path = crate::pathutils::join(token_path, &import_file_path)
.unwrap_or(import_file_path);
if ignore_missing_font_files
|| crate::pathutils::is_url(&import_file_path)
|| crate::fileaccess::load_file(std::path::Path::new(&import_file_path))
.is_some()
{
Some((import_file_path.to_string_lossy().into(), import.import_uri_token.clone()))
} else {
diag.push_error(
format!("File \"{}\" not found", import.file),
&import.import_uri_token,
);
None
}
} else if import.file.ends_with(".slint") {
diag.push_error("Import names are missing. Please specify which types you would like to import".into(), &import.import_uri_token.parent());
None
} else {
diag.push_error(
format!("Unsupported foreign import \"{}\"", import.file),
&import.import_uri_token,
);
None
}
})
.collect();
for local_compo in &inner_components {
if exports
.components_or_types
.iter()
.filter_map(|(_, exported_compo_or_type)| exported_compo_or_type.as_ref().left())
.any(|exported_compo| Rc::ptr_eq(exported_compo, local_compo))
{
continue;
}
if local_compo.is_global() {
continue;
}
if !local_compo.used.get() {
diag.push_warning(
"Component is neither used nor exported".into(),
&local_compo.node.as_ref().map(|n| n.to_source_location()),
)
}
}
Document {
node: Some(node),
inner_components,
inner_types,
local_registry,
custom_fonts,
imports,
exports,
library_exports: Default::default(),
embedded_file_resources: Default::default(),
#[cfg(feature = "bundle-translations")]
translation_builder: None,
used_types: Default::default(),
popup_menu_impl: None,
}
}
pub fn exported_roots(&self) -> impl DoubleEndedIterator<Item = Rc<Component>> + '_ {
self.exports
.iter()
.filter_map(|e| e.1.as_ref().left())
.filter(|c| !c.is_global() && !c.is_interface())
.cloned()
}
pub fn last_exported_component(&self) -> Option<Rc<Component>> {
self.exports
.iter()
.filter_map(|e| Some((&e.0.name_ident, e.1.as_ref().left()?)))
.filter(|(_, c)| !c.is_global())
.max_by_key(|(n, _)| n.text_range().end())
.map(|(_, c)| c.clone())
}
pub fn visit_all_used_components(&self, mut v: impl FnMut(&Rc<Component>)) {
let used_types = self.used_types.borrow();
for c in &used_types.sub_components {
v(c);
}
for c in self.exported_roots() {
v(&c);
}
for c in &used_types.globals {
v(c);
}
if let Some(c) = &self.popup_menu_impl {
v(c);
}
}
}
#[derive(Debug, Clone)]
pub struct PopupWindow {
pub component: Rc<Component>,
pub x: NamedReference,
pub y: NamedReference,
pub close_policy: EnumerationValue,
pub parent_element: ElementRc,
pub is_tooltip: bool,
pub is_open: Option<NamedReference>,
}
#[derive(Debug, Clone)]
pub struct Timer {
pub interval: NamedReference,
pub triggered: NamedReference,
pub running: NamedReference,
pub element: ElementWeak,
}
pub const DEFAULT_SLOT_NAME: &str = "@children";
pub fn slot_error_subject(name: &str) -> String {
if name == DEFAULT_SLOT_NAME {
"The @children placeholder".into()
} else {
format!("The slot '{name}'")
}
}
#[derive(Clone, Debug)]
pub enum ChildInsertionPointNode {
DefaultChildrenPlaceHolder(SyntaxNode),
ChildrenPlaceHolder(syntax_nodes::ChildrenPlaceholder),
SlotPlaceholder(syntax_nodes::SubElement),
SlotForwarding(syntax_nodes::Expression),
}
impl ChildInsertionPointNode {
pub fn syntax_node(&self) -> &SyntaxNode {
match self {
Self::DefaultChildrenPlaceHolder(node) => node,
Self::ChildrenPlaceHolder(node) => node,
Self::SlotPlaceholder(node) => node,
Self::SlotForwarding(node) => node,
}
}
}
impl Spanned for ChildInsertionPointNode {
fn span(&self) -> crate::diagnostics::Span {
self.syntax_node().span()
}
fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
self.syntax_node().source_file()
}
}
#[derive(Clone, Debug)]
pub struct ChildrenInsertionPoint {
pub parent: ElementRc,
pub insertion_index: usize,
pub node: ChildInsertionPointNode,
}
#[derive(Clone, Debug)]
pub struct DeclaredSlot {
pub name: SmolStr,
pub name_node: syntax_nodes::DeclaredIdentifier,
has_rejected_placeholder: bool,
}
#[derive(Clone, Debug)]
pub struct SlotForwarding {
pub target: SmolStr,
pub source: SmolStr,
pub expression_node: syntax_nodes::Expression,
}
#[derive(Debug, Default)]
pub struct UsedSubTypes {
pub globals: Vec<Rc<Component>>,
pub structs_and_enums: Vec<Type>,
pub sub_components: Vec<Rc<Component>>,
pub library_types_imports: Vec<(SmolStr, LibraryInfo)>,
pub library_global_imports: Vec<(SmolStr, LibraryInfo)>,
pub deprecated_type_aliases: Vec<(SmolStr, SmolStr)>,
pub collision_renamed_names: std::collections::BTreeSet<SmolStr>,
}
#[derive(Debug, Default, Clone)]
pub struct InitCode {
pub constructor_code: Vec<Expression>,
pub focus_setting_code: Vec<Expression>,
pub font_registration_code: Vec<Expression>,
pub inlined_init_code: BTreeMap<usize, Expression>,
}
impl InitCode {
pub fn iter(&self) -> impl Iterator<Item = &Expression> {
self.font_registration_code.iter().chain(self.iter_without_font_registration())
}
pub fn iter_without_font_registration(&self) -> impl Iterator<Item = &Expression> {
self.focus_setting_code
.iter()
.chain(self.constructor_code.iter())
.chain(self.inlined_init_code.values())
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Expression> {
self.font_registration_code
.iter_mut()
.chain(self.focus_setting_code.iter_mut())
.chain(self.constructor_code.iter_mut())
.chain(self.inlined_init_code.values_mut())
}
}
#[derive(Default, Debug)]
pub struct Component {
pub node: Option<syntax_nodes::Component>,
pub id: SmolStr,
pub root_element: ElementRc,
pub parent_element: RefCell<ElementWeak>,
pub optimized_elements: RefCell<Vec<ElementRc>>,
pub root_constraints: RefCell<LayoutConstraints>,
pub child_insertion_points: RefCell<BTreeMap<String, ChildrenInsertionPoint>>,
pub declared_slots: RefCell<Vec<DeclaredSlot>>,
pub init_code: RefCell<InitCode>,
pub popup_windows: RefCell<Vec<PopupWindow>>,
pub timers: RefCell<Vec<Timer>>,
pub menu_item_tree: RefCell<Vec<Rc<Component>>>,
pub inherits_popup_window: Cell<bool>,
pub exported_global_names: RefCell<Vec<ExportedName>>,
pub used: Cell<bool>,
pub private_properties: RefCell<Vec<(SmolStr, Type)>>,
pub from_library: Cell<bool>,
}
impl Component {
pub fn from_node(
node: syntax_nodes::Component,
diag: &mut BuildDiagnostics,
tr: &TypeRegister,
) -> Rc<Self> {
let mut child_insertion_points = BTreeMap::new();
let mut declared_slots = Vec::new();
let is_legacy_syntax = node.child_token(SyntaxKind::ColonEqual).is_some();
let c = Component {
node: Some(node.clone()),
id: parser::identifier_text(&node.DeclaredIdentifier()).unwrap_or_default(),
root_element: Element::from_node(
node.Element(),
"root".into(),
match node.child_text(SyntaxKind::Identifier) {
Some(t) if t == "global" => {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Globals are", &node.DeclaredIdentifier());
ElementType::Global
}
Some(t) if t == "interface" => {
if reject_experimental_feature(diag, tr, "interface", &node) {
ElementType::Error
} else {
ElementType::Interface
}
}
_ => ElementType::Error,
},
&mut child_insertion_points,
&mut declared_slots,
is_legacy_syntax,
diag,
tr,
),
child_insertion_points: RefCell::new(child_insertion_points),
declared_slots: RefCell::new(declared_slots),
..Default::default()
};
c.check_slot_validity(diag);
let c = Rc::new(c);
if c.root_element
.borrow()
.builtin_type()
.is_some_and(|b| matches!(b.name.as_str(), "Window" | "Dialog"))
{
for prop in ["x", "y"] {
if let Some(b) = c.root_element.borrow().binding_cell_including_synthetic(prop) {
#[cfg(feature = "slint-sc")]
if diag.slint_sc {
diag.slint_sc_error(&format!("The property '{prop}' is"), &*b.borrow());
continue;
}
diag.push_warning(
format!(
"Setting '{prop}' on a Window is deprecated, it doesn't affect the position of the window"
),
&*b.borrow(),
);
}
}
#[cfg(feature = "slint-sc")]
for prop in ["width", "height"] {
if let Some(b) = c.root_element.borrow().binding_cell_including_synthetic(prop) {
diag.slint_sc_error(
&format!("Binding the '{prop}' of the root element is"),
&*b.borrow(),
);
}
}
}
let weak = Rc::downgrade(&c);
recurse_elem(&c.root_element, &(), &mut |e, _| {
e.borrow_mut().enclosing_component = weak.clone();
if let Some(qualified_id) =
e.borrow_mut().debug.first_mut().and_then(|x| x.qualified_id.as_mut())
{
*qualified_id = format_smolstr!("{}::{}", c.id, qualified_id);
}
});
c
}
fn check_slot_validity(&self, diagnostics: &mut BuildDiagnostics) {
if !diagnostics.enable_experimental {
return;
}
if self.is_global() || self.is_interface() {
return;
}
let mut declared_slot_nodes = BTreeMap::<SmolStr, syntax_nodes::DeclaredIdentifier>::new();
for slot in self.declared_slots.borrow().iter() {
if slot.name == "children" {
diagnostics.push_error(
format!(
"The name '{}' is reserved for the default slot. Use @children instead",
slot.name
),
&slot.name_node,
);
continue;
}
if declared_slot_nodes.insert(slot.name.clone(), slot.name_node.clone()).is_some() {
diagnostics.push_error(
format!("Duplicate slot declaration '{}'", slot.name),
&slot.name_node,
);
}
}
for (name, cip) in self.child_insertion_points.borrow().iter() {
if name == DEFAULT_SLOT_NAME {
continue;
}
if !declared_slot_nodes.contains_key(name.as_str()) {
diagnostics
.push_error(format!("The slot '{name}' is used but not declared"), &cip.node);
}
}
for (name, node) in declared_slot_nodes.iter() {
let has_rejected_placeholder = self
.declared_slots
.borrow()
.iter()
.any(|slot| slot.has_rejected_placeholder && &slot.name == name);
if !self.child_insertion_points.borrow().contains_key(name.as_str())
&& !has_rejected_placeholder
{
diagnostics.push_error(format!("The slot '{name}' is declared but not used"), node);
}
}
}
pub fn is_global(&self) -> bool {
match &self.root_element.borrow().base_type {
ElementType::Global => true,
ElementType::Builtin(c) => c.is_global,
_ => false,
}
}
pub fn is_interface(&self) -> bool {
matches!(&self.root_element.borrow().base_type, ElementType::Interface)
}
pub fn inherits_system_tray_icon(&self) -> bool {
self.root_element
.borrow()
.native_class()
.is_some_and(|n| n.class_name.as_str() == "SystemTrayIcon")
}
pub fn global_aliases(&self) -> Vec<SmolStr> {
self.exported_global_names
.borrow()
.iter()
.filter(|name| name.as_str() != self.root_element.borrow().id)
.map(|name| name.original_name())
.collect()
}
pub fn repeater_count(&self) -> u32 {
let mut count = 0;
recurse_elem(&self.root_element, &(), &mut |element, _| {
let element = element.borrow();
if let Some(sub_component) = element.sub_component() {
count += sub_component.repeater_count();
} else if element.repeated.is_some() {
count += 1;
}
});
count
}
pub fn parent_element(&self) -> Option<ElementRc> {
self.parent_element.borrow().upgrade()
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
pub enum PropertyVisibility {
#[default]
Private,
Input,
Output,
InOut,
Constexpr,
Fake,
Public,
Protected,
}
impl Display for PropertyVisibility {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PropertyVisibility::Private => f.write_str("private"),
PropertyVisibility::Input => f.write_str("in"),
PropertyVisibility::Output => f.write_str("out"),
PropertyVisibility::InOut => f.write_str("in-out"),
PropertyVisibility::Constexpr => f.write_str("constexpr"),
PropertyVisibility::Public => f.write_str("public"),
PropertyVisibility::Protected => f.write_str("protected"),
PropertyVisibility::Fake => f.write_str("fake"),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct PropertyDeclaration {
pub property_type: Type,
pub node: Option<SyntaxNode>,
pub expose_in_public_api: bool,
pub is_alias: Option<NamedReference>,
pub visibility: PropertyVisibility,
pub pure: Option<bool>,
pub shadowed_name: Option<SmolStr>,
pub shadowable: bool,
pub moved_from: Option<SmolStr>,
pub deprecated: Option<SmolStr>,
}
impl PropertyDeclaration {
pub fn type_node(&self) -> Option<SyntaxNode> {
let node = self.node.as_ref()?;
if let Some(x) = syntax_nodes::PropertyDeclaration::new(node.clone()) {
Some(x.Type().map_or_else(|| x.into(), |x| x.into()))
} else {
node.clone().into()
}
}
pub fn declared_name<'a>(&'a self, internal_name: &'a SmolStr) -> &'a SmolStr {
self.shadowed_name.as_ref().unwrap_or(internal_name)
}
pub fn is_private_shadow(&self) -> bool {
self.shadowed_name.is_some() && self.visibility == PropertyVisibility::Private
}
pub fn has_derived_deprecation(&self) -> bool {
self.deprecated.is_some()
&& self
.node
.as_ref()
.and_then(|n| syntax_nodes::PropertyDeclaration::new(n.clone()))
.and_then(|p| p.PropertyDeprecation())
.is_some_and(|d| d.child_token(SyntaxKind::StringLiteral).is_none())
}
}
fn shadowable_attribute(
node: Option<syntax_nodes::ShadowableAttribute>,
tr: &TypeRegister,
diag: &mut BuildDiagnostics,
) -> bool {
node.is_some_and(|node| !reject_experimental_feature(diag, tr, "@shadowable", &node))
}
enum DeprecationHint {
TwoWayBinding(Option<syntax_nodes::QualifiedName>),
MessageRequired,
}
fn member_deprecation(
deprecation: Option<syntax_nodes::PropertyDeprecation>,
hint: DeprecationHint,
tr: &TypeRegister,
diag: &mut BuildDiagnostics,
) -> Option<SmolStr> {
let deprecation = deprecation?;
if reject_experimental_feature(diag, tr, "@deprecated", &deprecation) {
return None;
}
if let Some(message) = deprecation.child_token(SyntaxKind::StringLiteral) {
return crate::literals::unescape_string(message.text());
}
let message = match hint {
DeprecationHint::TwoWayBinding(target) => {
if let Some(qn) = target {
let mut segments = qn
.children_with_tokens()
.filter(|t| t.kind() == SyntaxKind::Identifier)
.map(|t| parser::normalize_identifier(t.as_token().unwrap().text()))
.peekable();
if segments.peek().is_some_and(|s| matches!(s.as_str(), "self" | "root")) {
segments.next();
}
let path = segments.collect::<Vec<_>>().join(".");
if !path.is_empty() {
return Some(format_smolstr!("Please use '{path}' instead"));
}
}
"@deprecated without a message requires a two-way binding to derive the replacement from"
}
DeprecationHint::MessageRequired => "@deprecated on a function requires a message",
};
diag.push_error(message.into(), &deprecation);
None
}
fn from_base(mut r: PropertyLookupResult<'_>) -> PropertyLookupResult<'_> {
r.is_in_direct_base = r.is_local_to_component;
r.is_local_to_component = false;
r
}
fn cannot_override_message(
kind: Option<&str>,
name: &SmolStr,
declared_in: &Option<Rc<Component>>,
) -> String {
let kind = kind.map_or_else(String::new, |kind| format!("{kind} "));
match declared_in {
Some(base) => format!("Cannot override {kind}'{name}' declared in '{}'", base.id),
None => format!("Cannot override {kind}'{name}'"),
}
}
enum MemberDeclaration {
New,
Shadow {
internal_name: SmolStr,
warning: Option<String>,
},
Conflict {
existing_type: Type,
declared_in: Option<Rc<Component>>,
},
}
impl MemberDeclaration {
fn register(
self,
elem: &mut Element,
source_name: &SmolStr,
node: &dyn Spanned,
diag: &mut BuildDiagnostics,
) -> SmolStr {
let Self::Shadow { internal_name, warning } = self else {
return source_name.clone();
};
elem.shadowing_members.insert(source_name.clone(), internal_name.clone());
if let Some(warning) = warning {
diag.push_warning(warning, node);
}
internal_name
}
}
impl From<Type> for PropertyDeclaration {
fn from(ty: Type) -> Self {
PropertyDeclaration { property_type: ty, ..Self::default() }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TransitionDirection {
In,
Out,
InOut,
}
#[derive(Debug, Clone)]
pub struct TransitionPropertyAnimation {
pub state_id: i32,
pub direction: TransitionDirection,
pub animation: ElementRc,
}
impl TransitionPropertyAnimation {
pub fn condition(&self, state: Expression) -> Expression {
match self.direction {
TransitionDirection::In => Expression::BinaryExpression {
lhs: Box::new(Expression::StructFieldAccess {
base: Box::new(state),
name: "current-state".into(),
}),
rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
op: '=',
source_location: None,
},
TransitionDirection::Out => Expression::BinaryExpression {
lhs: Box::new(Expression::StructFieldAccess {
base: Box::new(state),
name: "previous-state".into(),
}),
rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
op: '=',
source_location: None,
},
TransitionDirection::InOut => Expression::BinaryExpression {
lhs: Box::new(Expression::BinaryExpression {
source_location: None,
lhs: Box::new(Expression::StructFieldAccess {
base: Box::new(state.clone()),
name: "current-state".into(),
}),
rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
op: '=',
}),
rhs: Box::new(Expression::BinaryExpression {
source_location: None,
lhs: Box::new(Expression::StructFieldAccess {
base: Box::new(state),
name: "previous-state".into(),
}),
rhs: Box::new(Expression::NumberLiteral(self.state_id as _, Unit::None)),
op: '=',
}),
op: '|',
source_location: None,
},
}
}
}
#[derive(Debug)]
pub enum PropertyAnimation {
Static(ElementRc),
Transition { state_ref: Expression, animations: Vec<TransitionPropertyAnimation> },
}
impl Clone for PropertyAnimation {
fn clone(&self) -> Self {
fn deep_clone(e: &ElementRc) -> ElementRc {
let e = e.borrow();
debug_assert!(e.children.is_empty());
debug_assert!(e.property_declarations.is_empty());
debug_assert!(e.states.is_empty() && e.transitions.is_empty());
Rc::new(RefCell::new(Element {
id: e.id.clone(),
base_type: e.base_type.clone(),
bindings: e.bindings.clone(),
property_analysis: e.property_analysis.clone(),
enclosing_component: e.enclosing_component.clone(),
repeated: None,
debug: e.debug.clone(),
..Default::default()
}))
}
match self {
PropertyAnimation::Static(e) => PropertyAnimation::Static(deep_clone(e)),
PropertyAnimation::Transition { state_ref, animations } => {
PropertyAnimation::Transition {
state_ref: state_ref.clone(),
animations: animations
.iter()
.map(|t| TransitionPropertyAnimation {
state_id: t.state_id,
direction: t.direction,
animation: deep_clone(&t.animation),
})
.collect(),
}
}
}
}
}
#[derive(Default, Clone)]
pub struct AccessibilityProps(pub BTreeMap<String, NamedReference>);
#[derive(Clone, Debug)]
pub struct GeometryProps {
pub x: NamedReference,
pub y: NamedReference,
pub width: NamedReference,
pub height: NamedReference,
}
#[derive(Clone, Debug)]
pub enum ZOrder {
Constant(f32),
Dynamic(NamedReference),
PerInstance(NamedReference),
}
impl GeometryProps {
pub fn new(element: &ElementRc) -> Self {
Self {
x: NamedReference::new(element, SmolStr::new_static("x")),
y: NamedReference::new(element, SmolStr::new_static("y")),
width: NamedReference::new(element, SmolStr::new_static("width")),
height: NamedReference::new(element, SmolStr::new_static("height")),
}
}
}
pub type BindingsMap = BTreeMap<SmolStr, RefCell<BindingExpression>>;
#[derive(Clone, Default)]
pub struct Bindings(BindingsMap);
impl std::iter::FromIterator<(SmolStr, RefCell<BindingExpression>)> for Bindings {
fn from_iter<T: IntoIterator<Item = (SmolStr, RefCell<BindingExpression>)>>(iter: T) -> Self {
Bindings(iter.into_iter().collect())
}
}
impl From<BindingsMap> for Bindings {
fn from(map: BindingsMap) -> Self {
Bindings(map)
}
}
impl Bindings {
pub fn binding_cell_including_synthetic(
&self,
name: &str,
) -> Option<&RefCell<BindingExpression>> {
self.0.get(name)
}
}
#[derive(Clone, Debug)]
pub struct ElementDebugInfo {
pub qualified_id: Option<SmolStr>,
pub type_name: String,
pub element_hash: u64,
pub node: syntax_nodes::Element,
pub layout: Option<crate::layout::Layout>,
pub element_boundary: bool,
}
impl ElementDebugInfo {
fn encoded_element_info(&self) -> String {
let mut info = self.type_name.clone();
info.push(',');
if let Some(id) = self.qualified_id.as_ref() {
info.push_str(id);
}
info.push(',');
if let Some(layout) = &self.layout {
use crate::layout::{Layout, Orientation};
match layout {
Layout::BoxLayout(b) => match b.orientation {
Orientation::Horizontal => info.push_str("h-box"),
Orientation::Vertical => info.push_str("v-box"),
},
Layout::GridLayout(_) => info.push_str("grid"),
Layout::FlexboxLayout(_) => info.push_str("flex-box"),
}
}
info
}
}
#[derive(Default)]
pub struct Element {
pub id: SmolStr,
pub base_type: ElementType,
pub bindings: Bindings,
pub change_callbacks: BTreeMap<SmolStr, RefCell<Vec<Expression>>>,
pub property_analysis: RefCell<BTreeMap<SmolStr, PropertyAnalysis>>,
pub children: Vec<ElementRc>,
pub enclosing_component: Weak<Component>,
pub property_declarations: BTreeMap<SmolStr, PropertyDeclaration>,
pub shadowing_members: BTreeMap<SmolStr, SmolStr>,
pub named_references: crate::namedreference::NamedReferenceContainer,
pub repeated: Option<RepeatedElementInfo>,
pub is_component_placeholder: bool,
pub is_injected_wrapper_element: bool,
pub z_order: Option<ZOrder>,
pub states: Vec<State>,
pub transitions: Vec<Transition>,
pub match_elements: Vec<MatchElementInfo>,
pub child_of_layout: bool,
pub child_of_flexbox: bool,
pub parent_box_layout_orientation: Option<Orientation>,
pub layout_info_prop: Option<(NamedReference, NamedReference)>,
pub layout_info_v_with_constraint: Option<NamedReference>,
pub layout_info_h_at_own_height: Option<NamedReference>,
pub height_is_literal: bool,
pub default_fill_parent: (bool, bool),
pub accessibility_props: AccessibilityProps,
pub geometry_props: Option<GeometryProps>,
pub is_flickable_content: bool,
pub has_popup_child: bool,
pub is_tooltip: bool,
pub item_index: OnceCell<u32>,
pub item_index_of_first_children: OnceCell<u32>,
pub is_legacy_syntax: bool,
pub inline_depth: i32,
pub slot_target: Option<SmolStr>,
pub forwarded_slots: Vec<SlotForwarding>,
pub grid_layout_cell: Option<Rc<RefCell<crate::layout::GridLayoutCell>>>,
pub debug: Vec<ElementDebugInfo>,
}
impl Spanned for Element {
fn span(&self) -> crate::diagnostics::Span {
self.debug
.first()
.map(|n| {
n.node.QualifiedName().as_ref().map(Spanned::span).unwrap_or_else(|| n.node.span())
})
.unwrap_or_default()
}
fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
self.debug.first().map(|n| &n.node.source_file)
}
}
impl core::fmt::Debug for Element {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
pretty_print(f, self, 0)
}
}
pub fn pretty_print(
f: &mut impl std::fmt::Write,
e: &Element,
indentation: usize,
) -> std::fmt::Result {
if let Some(repeated) = &e.repeated {
write!(f, "for {}[{}] in ", repeated.model_data_id, repeated.index_id)?;
expression_tree::pretty_print(f, &repeated.model)?;
write!(f, ":")?;
if let ElementType::Component(base) = &e.base_type {
write!(f, "(base) ")?;
if base.parent_element().is_some() {
pretty_print(f, &base.root_element.borrow(), indentation)?;
return Ok(());
}
}
}
if e.is_component_placeholder {
write!(f, "/* Component Placeholder */ ")?;
}
writeln!(f, "{} := {} {{ /* {} */", e.id, e.base_type, e.element_infos())?;
let mut indentation = indentation + 1;
macro_rules! indent {
() => {
for _ in 0..indentation {
write!(f, " ")?
}
};
}
for (name, ty) in &e.property_declarations {
indent!();
if let Some(alias) = &ty.is_alias {
writeln!(f, "alias<{}> {} <=> {:?};", ty.property_type, name, alias)?
} else {
writeln!(f, "property<{}> {};", ty.property_type, name)?
}
}
for (name, expr) in &e.bindings.0 {
indent!();
write!(f, "{name}: ")?;
let Ok(expr) = expr.try_borrow() else {
writeln!(f, "<borrowed>")?;
continue;
};
expression_tree::pretty_print(f, &expr.expression)?;
if expr.analysis.as_ref().is_some_and(|a| a.is_const) {
write!(f, "/*const*/")?;
}
writeln!(f, ";")?;
if let Some(anim) = &expr.animation {
indent!();
writeln!(f, "animate {name} {anim:?}")?;
}
for nr in &expr.two_way_bindings {
indent!();
writeln!(f, "{name} <=> {nr:?};")?;
}
}
for (name, ch) in &e.change_callbacks {
for ex in &*ch.borrow() {
indent!();
write!(f, "changed {name} => ")?;
expression_tree::pretty_print(f, ex)?;
writeln!(f)?;
}
}
if !e.states.is_empty() {
indent!();
writeln!(f, "states {:?}", e.states)?;
}
if !e.transitions.is_empty() {
indent!();
writeln!(f, "transitions {:?} ", e.transitions)?;
}
for c in &e.children {
indent!();
pretty_print(f, &c.borrow(), indentation)?
}
if let Some(g) = &e.geometry_props {
indent!();
writeln!(f, "geometry {g:?} ")?;
}
indentation -= 1;
indent!();
writeln!(f, "}}")
}
#[derive(Clone, Default, Debug)]
pub struct PropertyAnalysis {
pub is_set: bool,
pub is_set_externally: bool,
pub is_read: bool,
pub is_read_externally: bool,
pub is_linked_to_read_only: bool,
pub is_linked: bool,
}
impl PropertyAnalysis {
pub fn merge_with_base(&mut self, other: &PropertyAnalysis) {
self.is_set |= other.is_set;
self.is_read |= other.is_read;
}
pub fn merge(&mut self, other: &PropertyAnalysis) {
self.is_set |= other.is_set;
self.is_read |= other.is_read;
self.is_read_externally |= other.is_read_externally;
self.is_set_externally |= other.is_set_externally;
}
pub fn is_used(&self) -> bool {
self.is_read || self.is_read_externally || self.is_set || self.is_set_externally
}
}
#[derive(Debug, Clone)]
pub struct ListViewInfo {
pub content_y: NamedReference,
pub content_height: Option<NamedReference>,
pub content_width: Option<NamedReference>,
pub listview_height: NamedReference,
pub listview_width: NamedReference,
}
#[derive(Debug, Clone)]
pub struct RepeatedElementInfo {
pub model: Expression,
pub model_data_id: SmolStr,
pub index_id: SmolStr,
pub is_conditional_element: bool,
pub is_listview: Option<ListViewInfo>,
}
pub struct MatchElementInfo {
pub node: syntax_nodes::MatchElement,
pub subject: Expression,
pub cases: Vec<MatchCaseInfo>,
pub wildcard: WildcardMatchCaseInfo,
}
pub enum WildcardMatchCaseInfo {
None,
Empty,
Element(ElementRc),
}
pub struct MatchCaseInfo {
pub value: Expression,
pub node: syntax_nodes::Expression,
pub element: Option<ElementRc>,
}
impl MatchElementInfo {
pub fn elements(&self) -> impl Iterator<Item = ElementRc> + '_ {
self.cases.iter().filter_map(|case| case.element.clone()).chain(match &self.wildcard {
WildcardMatchCaseInfo::Element(e) => Some(e.clone()),
WildcardMatchCaseInfo::None | WildcardMatchCaseInfo::Empty => None,
})
}
pub fn lower_to_conditional_elements(&self) {
let compare = |value: &Expression, op| Expression::BinaryExpression {
lhs: Box::new(self.subject.clone()),
rhs: Box::new(value.clone()),
op,
source_location: None,
};
let show_when = |element: &ElementRc, condition| {
element.borrow_mut().repeated = Some(RepeatedElementInfo {
model: condition,
model_data_id: SmolStr::default(),
index_id: SmolStr::default(),
is_conditional_element: true,
is_listview: None,
});
};
for case in &self.cases {
if let Some(element) = &case.element {
show_when(element, compare(&case.value, '='));
}
}
if let WildcardMatchCaseInfo::Element(wildcard) = &self.wildcard {
let condition = self
.cases
.iter()
.map(|case| compare(&case.value, '!'))
.reduce(|lhs, rhs| Expression::BinaryExpression {
lhs: Box::new(lhs),
rhs: Box::new(rhs),
op: '&',
source_location: None,
})
.unwrap_or(Expression::BoolLiteral(true));
show_when(wildcard, condition);
}
}
}
pub type ElementRc = Rc<RefCell<Element>>;
pub type ElementWeak = Weak<RefCell<Element>>;
impl Element {
pub fn make_rc(self) -> ElementRc {
let r = ElementRc::new(RefCell::new(self));
let g = GeometryProps::new(&r);
r.borrow_mut().geometry_props = Some(g);
r
}
pub fn from_node(
node: syntax_nodes::Element,
id: SmolStr,
parent_type: ElementType,
component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
declared_slots: &mut Vec<DeclaredSlot>,
is_legacy_syntax: bool,
diag: &mut BuildDiagnostics,
tr: &TypeRegister,
) -> ElementRc {
#[cfg(feature = "slint-sc")]
let is_component_root =
!matches!(parent_type, ElementType::Builtin(_) | ElementType::Component(_));
let base_type = if let Some(base_node) = node.QualifiedName() {
let base = QualifiedTypeName::from_node(base_node.clone());
let base_string = base.to_smolstr();
match parent_type.lookup_type_for_child_element(&base_string, tr) {
Ok(ElementType::Component(c)) if c.is_global() => {
diag.push_error(
"Cannot create an instance of a global component".into(),
&base_node,
);
ElementType::Error
}
Ok(ty) => {
#[cfg(feature = "slint-sc")]
if let ElementType::Builtin(b) = &ty
&& !b.slint_sc
{
diag.slint_sc_error(
&format!("The builtin element '{}' is", b.name),
&base_node,
);
}
ty
}
Err(err) => {
diag.push_error(err, &base_node);
ElementType::Error
}
}
} else if parent_type == ElementType::Global || parent_type == ElementType::Interface {
let mut error_on = |node: &dyn Spanned, what: &str| {
let element_type = match parent_type {
ElementType::Global => "A global component",
ElementType::Interface => "An interface",
_ => "An unexpected type",
};
diag.push_error(format!("{element_type} cannot have {what}"), node);
};
node.SubElement().for_each(|n| error_on(&n, "sub elements"));
node.RepeatedElement().for_each(|n| error_on(&n, "sub elements"));
if let Some(n) = node.ChildrenPlaceholder() {
error_on(&n, "sub elements");
}
node.PropertyAnimation().for_each(|n| error_on(&n, "animations"));
node.States().for_each(|n| error_on(&n, "states"));
node.Transitions().for_each(|n| error_on(&n, "transitions"));
node.CallbackDeclaration().for_each(|cb| {
if parser::identifier_text(&cb.DeclaredIdentifier()).is_some_and(|s| s == "init") {
error_on(&cb, "an 'init' callback")
}
});
node.CallbackConnection().for_each(|cb| {
if parser::identifier_text(&cb).is_some_and(|s| s == "init") {
error_on(&cb, "an 'init' callback")
}
});
node.MatchElement().for_each(|n| error_on(&n, "match elements"));
node.SlotDeclaration().for_each(|n| error_on(&n, "slots"));
if parent_type == ElementType::Interface {
node.Binding().for_each(|n| error_on(&n, "bindings"));
node.TwoWayBinding().for_each(|n| error_on(&n, "two-way bindings"));
node.ImplementStatement().for_each(|stmt| {
diag.push_error("Interfaces cannot implement another interface".into(), &stmt);
});
} else {
node.ImplementStatement().for_each(|stmt| {
diag.push_error("Globals cannot implement an interface".into(), &stmt);
});
}
parent_type
} else if parent_type != ElementType::Error {
assert!(diag.has_errors());
return ElementRc::default();
} else {
tr.empty_type()
};
let is_interface = base_type == ElementType::Interface;
let qualified_id = (!id.is_empty()).then(|| id.clone());
if let ElementType::Component(c) = &base_type {
c.used.set(true);
}
let type_name = base_type
.type_name()
.filter(|_| base_type != tr.empty_type())
.unwrap_or_default()
.to_string();
let mut r = Element {
id,
base_type: base_type.clone(),
debug: vec![ElementDebugInfo {
qualified_id,
element_hash: 0,
type_name,
node: node.clone(),
layout: None,
element_boundary: false,
}],
is_legacy_syntax,
..Default::default()
};
let mut property_bindings: Vec<(
SmolStr,
syntax_nodes::BindingExpression,
syntax_nodes::DeclaredIdentifier,
)> = Vec::new();
let mut two_way_bindings: Vec<(
SmolStr,
syntax_nodes::TwoWayBinding,
syntax_nodes::DeclaredIdentifier,
)> = Vec::new();
for prop_decl in node.PropertyDeclaration() {
#[cfg(feature = "slint-sc")]
if !is_component_root {
diag.slint_sc_error(
"Declaring a property on an element other than the root is",
&prop_decl,
);
}
let prop_type = prop_decl
.Type()
.map(|type_node| type_from_node(type_node, diag, tr))
.unwrap_or(Type::InferredProperty);
let unresolved_prop_name =
unwrap_or_continue!(parser::identifier_text(&prop_decl.DeclaredIdentifier()); diag);
let declaration = r.member_declaration(&unresolved_prop_name);
let name_token =
prop_decl.DeclaredIdentifier().child_token(SyntaxKind::Identifier).unwrap();
if let MemberDeclaration::Conflict { existing_type, declared_in } = &declaration {
match existing_type {
Type::Callback { .. } => diag.push_error(
format!("Cannot declare property '{unresolved_prop_name}' when a callback with the same name exists"),
&name_token,
),
Type::Function { .. } => diag.push_error(
format!("Cannot declare property '{unresolved_prop_name}' when a function with the same name exists"),
&name_token,
),
_ => diag.push_error(
cannot_override_message(Some("property"), &unresolved_prop_name, declared_in),
&name_token,
),
}
continue;
}
let prop_name = declaration.register(&mut r, &unresolved_prop_name, &name_token, diag);
let shadowed_name =
(prop_name != unresolved_prop_name).then(|| unresolved_prop_name.clone());
let mut visibility = None;
for token in prop_decl.children_with_tokens() {
if token.kind() != SyntaxKind::Identifier {
continue;
}
match (token.as_token().unwrap().text(), visibility) {
("in", None) => visibility = Some(PropertyVisibility::Input),
("in", Some(_)) => diag.push_error("Extra 'in' keyword".into(), &token),
("out", None) => visibility = Some(PropertyVisibility::Output),
("out", Some(_)) => diag.push_error("Extra 'out' keyword".into(), &token),
("in-out" | "in_out", None) => visibility = Some(PropertyVisibility::InOut),
("in-out" | "in_out", Some(_)) => {
diag.push_error("Extra 'in-out' keyword".into(), &token)
}
("private", None) => visibility = Some(PropertyVisibility::Private),
("private", Some(_)) => {
diag.push_error("Extra 'private' keyword".into(), &token)
}
_ => (),
}
}
let visibility = visibility.unwrap_or({
if is_legacy_syntax {
PropertyVisibility::InOut
} else {
PropertyVisibility::Private
}
});
if is_interface {
if let Some(binding_expression) = &prop_decl.BindingExpression() {
diag.push_error(
"Interface properties cannot have default values".into(),
binding_expression,
)
}
if let Some(two_way) = &prop_decl.TwoWayBinding() {
diag.push_error(
"Interface properties cannot have default bindings".into(),
two_way,
)
}
if visibility == PropertyVisibility::Private {
diag.push_error(
"'private' properties are inaccessible in an interface".into(),
&prop_decl,
);
}
}
let deprecated = member_deprecation(
prop_decl.PropertyDeprecation(),
DeprecationHint::TwoWayBinding(
prop_decl.TwoWayBinding().and_then(|twb| twb.Expression().QualifiedName()),
),
tr,
diag,
);
r.property_declarations.insert(
prop_name.clone(),
PropertyDeclaration {
property_type: prop_type,
node: Some(prop_decl.clone().into()),
visibility,
shadowed_name,
shadowable: shadowable_attribute(prop_decl.ShadowableAttribute(), tr, diag),
deprecated,
..Default::default()
},
);
if let Some(csn) = prop_decl.BindingExpression() {
property_bindings.push((prop_name.clone(), csn, prop_decl.DeclaredIdentifier()));
}
if let Some(csn) = prop_decl.TwoWayBinding() {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Two-way bindings are", &csn);
two_way_bindings.push((prop_name, csn, prop_decl.DeclaredIdentifier()));
}
}
let (implemented_interfaces, child_implements) =
if matches!(r.base_type, ElementType::Global | ElementType::Interface) {
(Vec::new(), Vec::new())
} else if r.id == "root" {
interfaces::get_implemented_interfaces(&r, &node, tr, diag)
} else {
interfaces::disallow_implement_in_non_root(&node, tr, diag);
(Vec::new(), Vec::new())
};
for (prop_name, csn, source) in property_bindings {
match r.bindings.0.entry(prop_name.clone()) {
Entry::Vacant(e) => {
e.insert(BindingExpression::new_uncompiled(csn.into()).into());
}
Entry::Occupied(_) => {
diag.push_error("Duplicated property binding".into(), &source);
}
}
}
for (prop_name, csn, source) in two_way_bindings {
if r.bindings
.0
.insert(prop_name, BindingExpression::new_uncompiled(csn.into()).into())
.is_some()
{
diag.push_error("Duplicated property binding".into(), &source);
}
}
r.parse_bindings(
node.Binding().filter_map(|b| {
Some((b.child_token(SyntaxKind::Identifier)?, b.BindingExpression().into()))
}),
is_legacy_syntax,
diag,
);
r.parse_bindings(
node.TwoWayBinding()
.filter_map(|b| Some((b.child_token(SyntaxKind::Identifier)?, b.into()))),
is_legacy_syntax,
diag,
);
apply_default_type_properties(&mut r);
for sig_decl in node.CallbackDeclaration() {
let name =
unwrap_or_continue!(parser::identifier_text(&sig_decl.DeclaredIdentifier()); diag);
let pure = Some(
sig_decl.child_token(SyntaxKind::Identifier).is_some_and(|t| t.text() == "pure"),
);
#[cfg(feature = "slint-sc")]
{
if !is_component_root {
diag.slint_sc_error(
"Declaring a callback on an element other than the root is",
&sig_decl,
);
}
if pure == Some(true) {
diag.slint_sc_error("Pure callbacks are", &sig_decl);
}
if let Some(param) = sig_decl.CallbackDeclarationParameter().next() {
diag.slint_sc_error("Callback parameters are", ¶m);
}
if let Some(ret) = sig_decl.ReturnType() {
diag.slint_sc_error("Callback return types are", &ret);
}
}
let declaration = r.member_declaration(&name);
if let MemberDeclaration::Conflict { existing_type, declared_in } = &declaration {
if matches!(existing_type, Type::Callback { .. }) {
if r.declaration(&name).is_some() {
diag.push_error(
"Duplicated callback declaration".into(),
&sig_decl.DeclaredIdentifier(),
);
} else {
diag.push_error(
cannot_override_message(Some("callback"), &name, declared_in),
&sig_decl.DeclaredIdentifier(),
)
}
} else {
diag.push_error(
format!(
"Cannot declare callback '{name}' when a {} with the same name exists",
if matches!(existing_type, Type::Function { .. }) {
"function"
} else {
"property"
}
),
&sig_decl.DeclaredIdentifier(),
);
}
continue;
}
let shadowable = shadowable_attribute(sig_decl.ShadowableAttribute(), tr, diag);
let deprecated = member_deprecation(
sig_decl.PropertyDeprecation(),
DeprecationHint::TwoWayBinding(
sig_decl.TwoWayBinding().and_then(|twb| twb.Expression().QualifiedName()),
),
tr,
diag,
);
let source_name = name;
let name =
declaration.register(&mut r, &source_name, &sig_decl.DeclaredIdentifier(), diag);
let shadowed_name = (name != source_name).then_some(source_name);
if let Some(csn) = sig_decl.TwoWayBinding() {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Callback aliases are", &csn);
r.bindings
.0
.insert(name.clone(), BindingExpression::new_uncompiled(csn.into()).into());
r.property_declarations.insert(
name,
PropertyDeclaration {
property_type: Type::InferredCallback,
node: Some(sig_decl.into()),
visibility: PropertyVisibility::InOut,
pure,
shadowed_name,
shadowable,
deprecated,
..Default::default()
},
);
continue;
}
let args = sig_decl
.CallbackDeclarationParameter()
.map(|p| type_from_node(p.Type(), diag, tr))
.collect();
let return_type = sig_decl
.ReturnType()
.map(|ret_ty| type_from_node(ret_ty.Type(), diag, tr))
.unwrap_or(Type::Void);
let arg_names = sig_decl
.CallbackDeclarationParameter()
.map(|a| {
a.DeclaredIdentifier()
.and_then(|x| parser::identifier_text(&x))
.unwrap_or_default()
})
.collect();
r.property_declarations.insert(
name,
PropertyDeclaration {
property_type: Type::Callback(Arc::new(Function {
return_type,
args,
arg_names,
})),
node: Some(sig_decl.into()),
visibility: PropertyVisibility::InOut,
pure,
shadowed_name,
shadowable,
deprecated,
..Default::default()
},
);
}
for func in node.Function() {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Function declarations are", &func);
let name =
unwrap_or_continue!(parser::identifier_text(&func.DeclaredIdentifier()); diag);
let member_decl = r.member_declaration(&name);
if let MemberDeclaration::Conflict { existing_type, declared_in } = &member_decl {
if matches!(existing_type, Type::Callback { .. } | Type::Function { .. }) {
diag.push_error(
cannot_override_message(None, &name, declared_in),
&func.DeclaredIdentifier(),
)
} else {
diag.push_error(
format!("Cannot declare function '{name}' when a property with the same name exists"),
&func.DeclaredIdentifier(),
);
}
continue;
}
let source_name = name;
let name = member_decl.register(&mut r, &source_name, &func.DeclaredIdentifier(), diag);
let shadowed_name = (name != source_name).then_some(source_name);
let mut args = Vec::new();
let mut arg_names = Vec::new();
for a in func.ArgumentDeclaration() {
args.push(type_from_node(a.Type(), diag, tr));
let name =
unwrap_or_continue!(parser::identifier_text(&a.DeclaredIdentifier()); diag);
if arg_names.contains(&name) {
diag.push_error(
format!("Duplicated argument name '{name}'"),
&a.DeclaredIdentifier(),
);
}
arg_names.push(name);
}
let return_type = func
.ReturnType()
.map_or(Type::Void, |ret_ty| type_from_node(ret_ty.Type(), diag, tr));
let mut visibility = PropertyVisibility::Private;
let mut pure = None;
for token in func.children_with_tokens() {
if token.kind() != SyntaxKind::Identifier {
continue;
}
match token.as_token().unwrap().text() {
"pure" => pure = Some(true),
"public" => {
visibility = PropertyVisibility::Public;
pure = pure.or(Some(false));
}
"protected" => {
visibility = PropertyVisibility::Protected;
pure = pure.or(Some(false));
}
_ => (),
}
}
if is_interface && visibility != PropertyVisibility::Public {
diag.push_error(
"Function declarations in an interface must be public".into(),
&func,
);
}
let declaration = PropertyDeclaration {
property_type: Type::Function(Arc::new(Function { return_type, args, arg_names })),
node: Some(func.clone().into()),
visibility,
pure,
shadowed_name,
shadowable: shadowable_attribute(func.ShadowableAttribute(), tr, diag),
deprecated: member_deprecation(
func.PropertyDeprecation(),
DeprecationHint::MessageRequired,
tr,
diag,
),
..Default::default()
};
match (base_type.clone(), func.CodeBlock()) {
(ElementType::Interface, Some(code_block)) => {
diag.push_error(
"Function declarations in interfaces must not have a body".into(),
&code_block,
);
continue;
}
(ElementType::Interface, None) => {
r.property_declarations.insert(name, declaration);
continue;
}
(_, None) => {
diag.push_error("Functions must have a code block".into(), &func);
}
(_, Some(_)) => {}
}
if r.bindings
.0
.insert(name.clone(), BindingExpression::new_uncompiled(func.clone().into()).into())
.is_some()
{
assert!(diag.has_errors());
}
r.property_declarations.insert(name, declaration);
}
for con_node in node.CallbackConnection() {
let unresolved_name = unwrap_or_continue!(parser::identifier_text(&con_node); diag);
let lookup_result =
r.lookup_property(&unresolved_name, PropertyLookupMode::ComponentLocal);
#[cfg(feature = "slint-sc")]
{
if !r.is_user_declared_member(&unresolved_name) && !lookup_result.is_slint_sc {
diag.slint_sc_error(
&format!("The callback '{unresolved_name}' is"),
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
);
}
if is_component_root
&& r.property_declarations
.get(lookup_result.internal_or_resolved_name().as_str())
.is_some_and(|d| d.node.is_some())
{
diag.slint_sc_error(
"A handler for a callback declared on the root element is",
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
);
}
if let Some(param) = con_node.DeclaredIdentifier().next() {
diag.slint_sc_error("Callback handler parameters are", ¶m);
}
}
let deprecation =
lookup_result.deprecated.clone().filter(|_| !lookup_result.is_local_to_component);
let resolved_name = lookup_result.internal_or_resolved_name();
let property_type = lookup_result.property_type;
if let Type::Callback(callback) = &property_type {
let num_arg = con_node.DeclaredIdentifier().count();
if num_arg > callback.args.len() {
diag.push_error(
format!(
"'{}' only has {} arguments, but {} were provided",
unresolved_name,
callback.args.len(),
num_arg
),
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
);
}
} else if property_type == Type::InferredCallback {
} else {
if r.base_type != ElementType::Error {
diag.push_error(
format!("'{}' is not a callback in {}", unresolved_name, r.base_type),
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
);
}
continue;
}
if let Some(message) = &deprecation {
diag.push_property_deprecation_warning_with_message(
&unresolved_name,
message,
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
);
}
match r.bindings.0.entry(resolved_name) {
Entry::Vacant(e) => {
e.insert(BindingExpression::new_uncompiled(con_node.clone().into()).into());
}
Entry::Occupied(mut e) => {
let is_global_alias = r.base_type == ElementType::Global
&& matches!(
&e.get().borrow().expression,
Expression::Uncompiled(node) if node.kind() == SyntaxKind::TwoWayBinding
);
if is_global_alias {
let mut handler =
BindingExpression::new_uncompiled(con_node.clone().into());
if let Some(name) = con_node.child_token(SyntaxKind::Identifier) {
handler.span = Some(name.to_source_location());
}
e.insert(handler.into());
} else {
diag.push_error(
"Duplicated callback".into(),
&con_node.child_token(SyntaxKind::Identifier).unwrap(),
);
}
}
}
}
for anim in node.PropertyAnimation() {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Animations are", &anim);
if let Some(star) = anim.child_token(SyntaxKind::Star) {
diag.push_error(
"catch-all property is only allowed within transitions".into(),
&star,
)
};
for prop_name_token in anim.QualifiedName() {
match QualifiedTypeName::from_node(prop_name_token.clone()).members.as_slice() {
[unresolved_prop_name] => {
if r.base_type == ElementType::Error {
continue;
};
let lookup_result = r.lookup_property(
unresolved_prop_name,
PropertyLookupMode::ComponentLocal,
);
let valid_assign = lookup_result.is_valid_for_assignment();
let binding_name = lookup_result.internal_or_resolved_name();
if let Some(anim_element) = animation_element_from_node(
&anim,
&prop_name_token,
lookup_result.property_type.clone(),
diag,
tr,
) {
if !valid_assign {
diag.push_error(
format!(
"Cannot animate '{}' property '{}'",
lookup_result.property_visibility, unresolved_prop_name
),
&prop_name_token,
);
}
if unresolved_prop_name != lookup_result.resolved_name.as_ref() {
diag.push_property_deprecation_warning(
unresolved_prop_name,
&lookup_result.resolved_name,
&prop_name_token,
);
} else if let Some(message) = lookup_result
.deprecated
.as_ref()
.filter(|_| !lookup_result.is_local_to_component)
{
diag.push_property_deprecation_warning_with_message(
unresolved_prop_name,
message,
&prop_name_token,
);
}
let expr_binding =
r.bindings.0.entry(binding_name).or_insert_with(|| {
let mut r = BindingExpression::from(Expression::Invalid);
r.priority = 1;
r.span = Some(prop_name_token.to_source_location());
r.into()
});
if expr_binding
.get_mut()
.animation
.replace(PropertyAnimation::Static(anim_element))
.is_some()
{
diag.push_error("Duplicated animation".into(), &prop_name_token)
}
}
}
_ => diag.push_error(
"Can only refer to property in the current element".into(),
&prop_name_token,
),
}
}
}
for ch in node.PropertyChangedCallback() {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Change callbacks are", &ch);
let Some(prop) = parser::identifier_text(&ch.DeclaredIdentifier()) else { continue };
let lookup_result = r.lookup_property(&prop, PropertyLookupMode::ComponentLocal);
if !lookup_result.is_valid() {
if r.base_type != ElementType::Error {
diag.push_error(
format!("Property '{prop}' does not exist"),
&ch.DeclaredIdentifier(),
);
}
} else if !lookup_result.property_type.is_property_type() {
let what = match lookup_result.property_type {
Type::Function { .. } => "a function",
Type::Callback { .. } => "a callback",
_ => "not a property",
};
diag.push_error(
format!(
"Change callback can only be set on properties, and '{prop}' is {what}"
),
&ch.DeclaredIdentifier(),
);
} else if lookup_result.property_visibility == PropertyVisibility::Private
&& !lookup_result.is_local_to_component
{
diag.push_error(
format!("Change callback on a private property '{prop}'"),
&ch.DeclaredIdentifier(),
);
}
let handler = Expression::Uncompiled(ch.clone().into());
match r.change_callbacks.entry(lookup_result.internal_or_resolved_name()) {
Entry::Vacant(e) => {
e.insert(vec![handler].into());
}
Entry::Occupied(mut e) => {
diag.push_error(
format!("Duplicated change callback on '{prop}'"),
&ch.DeclaredIdentifier(),
);
e.get_mut().get_mut().push(handler);
}
}
}
let r = r.make_rc();
for se in node.children() {
if se.kind() != SyntaxKind::SlotForwarding {
continue;
}
if !Self::assert_experimental_slots(diag, &se, "slot forwarding") {
continue;
}
let target_node = se.child_node(SyntaxKind::DeclaredIdentifier).unwrap();
let target = parser::identifier_text(&target_node.clone()).unwrap_or_default();
if target == "children" {
diag.push_error(
format!(
"The name '{target}' is reserved for the default slot. Use @children instead"
),
&target_node,
);
continue;
}
if r.borrow().forwarded_slots.iter().any(|f| f.target == target) {
diag.push_error(format!("Duplicate assignment to slot '{target}'"), &target_node);
continue;
}
match &r.borrow().base_type {
ElementType::Component(component)
if !component
.declared_slots
.borrow()
.iter()
.any(|slot| slot.name == target) =>
{
diag.push_error(
format!("Unknown slot '{target}' in '{}'", component.id),
&target_node,
);
continue;
}
ElementType::Component(_) => {}
_ => {
diag.push_error("Slot forwarding can only be used on components".into(), &se);
continue;
}
}
let Some(expression_node) = se.child_node(SyntaxKind::Expression) else {
diag.push_error(
"Slot forwarding requires a slot identifier on the right-hand side".into(),
&se,
);
continue;
};
let Some(source) = Self::slot_forwarding_expr_identifier(&expression_node) else {
diag.push_error(
"Slot forwarding requires a slot identifier on the right-hand side".into(),
&expression_node,
);
continue;
};
if source == "children" {
diag.push_error(
format!(
"The name '{source}' is reserved for the default slot. Use @children instead"
),
&expression_node,
);
continue;
}
r.borrow_mut().forwarded_slots.push(SlotForwarding {
target,
source,
expression_node: expression_node.into(),
});
}
for forwarding in r.borrow().forwarded_slots.clone() {
let source = forwarding.source.clone();
if let Some(existing_cip) = component_child_insertion_points.get(source.as_str()) {
if matches!(existing_cip.node, ChildInsertionPointNode::SlotPlaceholder(_)) {
diag.push_error(
format!(
"The slot '{source}' cannot be forwarded and used as a placeholder in the same component"
),
&forwarding.expression_node,
);
} else {
diag.push_error(
format!(
"{} can only appear once in an element",
slot_error_subject(&source)
),
&forwarding.expression_node,
);
}
continue;
}
component_child_insertion_points.insert(
source.to_string(),
ChildrenInsertionPoint {
parent: r.clone(),
insertion_index: 0,
node: ChildInsertionPointNode::SlotForwarding(forwarding.expression_node),
},
);
}
let mut assigned_slots = HashSet::new();
for se in node.children() {
if se.kind() == SyntaxKind::SubElement {
if let Some(slot_name) =
Self::sub_element_slot_placeholder_name(&se, declared_slots)
{
Self::register_slot_placeholder(
&se,
slot_name,
&r,
component_child_insertion_points,
diag,
tr,
);
continue;
}
let parent_type = r.borrow().base_type.clone();
r.borrow_mut().children.push(Element::from_sub_element_node(
se.into(),
parent_type,
component_child_insertion_points,
declared_slots,
is_legacy_syntax,
diag,
tr,
));
} else if se.kind() == SyntaxKind::RepeatedElement {
let mut sub_child_insertion_points = BTreeMap::new();
let rep = Element::from_repeated_node(
se.into(),
&r,
&mut sub_child_insertion_points,
declared_slots,
is_legacy_syntax,
diag,
tr,
);
Self::reject_slot_placeholders(
diag,
declared_slots,
sub_child_insertion_points,
"a repeated element",
);
r.borrow_mut().children.push(rep);
} else if se.kind() == SyntaxKind::ConditionalElement {
let mut sub_child_insertion_points = BTreeMap::new();
let rep = Element::from_conditional_node(
se.into(),
r.borrow().base_type.clone(),
&mut sub_child_insertion_points,
declared_slots,
is_legacy_syntax,
diag,
tr,
);
Self::reject_slot_placeholders(
diag,
declared_slots,
sub_child_insertion_points,
"a conditional element",
);
r.borrow_mut().children.push(rep);
} else if se.kind() == SyntaxKind::MatchElement {
let mut sub_child_insertion_points = BTreeMap::new();
let match_element = Element::from_match_node(
se.into(),
r.borrow().base_type.clone(),
&mut sub_child_insertion_points,
declared_slots,
is_legacy_syntax,
diag,
tr,
);
Self::reject_slot_placeholders(
diag,
declared_slots,
sub_child_insertion_points,
"a match element",
);
let mut r = r.borrow_mut();
r.children.extend(match_element.elements());
r.match_elements.push(match_element);
} else if se.kind() == SyntaxKind::ChildrenPlaceholder {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("The @children placeholder is", &se);
if component_child_insertion_points.contains_key(DEFAULT_SLOT_NAME) {
diag.push_error(
format!(
"{} can only appear once in an element",
slot_error_subject(DEFAULT_SLOT_NAME)
),
&se,
);
} else {
component_child_insertion_points.insert(
DEFAULT_SLOT_NAME.into(),
ChildrenInsertionPoint {
parent: r.clone(),
insertion_index: r.borrow().children.len(),
node: ChildInsertionPointNode::ChildrenPlaceHolder(se.into()),
},
);
}
} else if se.kind() == SyntaxKind::SlotDeclaration {
Self::assert_experimental_slots(diag, &se, "named slots");
let decl: syntax_nodes::SlotDeclaration = se.into();
let name_node = decl.DeclaredIdentifier();
let name = parser::identifier_text(&name_node).unwrap_or_default();
declared_slots.push(DeclaredSlot {
name,
name_node,
has_rejected_placeholder: false,
});
} else if se.kind() == SyntaxKind::SlotAssignment {
if !Self::assert_experimental_slots(diag, &se, "named slots") {
continue;
}
let name_node = se.child_node(SyntaxKind::DeclaredIdentifier).unwrap();
let name = parser::identifier_text(&name_node).unwrap_or_default();
if name == "children" {
diag.push_error(
format!(
"The name '{name}' is reserved for the default slot. Use @children instead"
),
&name_node,
);
}
if !assigned_slots.insert(name.clone()) {
diag.push_error(format!("Duplicate assignment to slot '{name}'"), &name_node);
}
if r.borrow().forwarded_slots.iter().any(|f| f.target == name) {
diag.push_error(format!("Duplicate assignment to slot '{name}'"), &name_node);
}
let sub_element_node = se.child_node(SyntaxKind::SubElement).unwrap();
let parent_type = r.borrow().base_type.clone();
match &parent_type {
ElementType::Component(component)
if !component
.declared_slots
.borrow()
.iter()
.any(|slot| slot.name == name) =>
{
diag.push_error(
format!("Unknown slot '{name}' in '{}'", component.id),
&name_node,
);
}
ElementType::Component(_) => {}
_ => {
diag.push_error(
"Slot assignments can only be used on components".to_string(),
&se,
);
}
}
let element = Element::from_sub_element_node(
sub_element_node.into(),
parent_type,
component_child_insertion_points,
declared_slots,
is_legacy_syntax,
diag,
tr,
);
element.borrow_mut().slot_target = Some(name);
r.borrow_mut().children.push(element);
}
}
for state in node.States().flat_map(|s| s.State()) {
let condition = state.Expression();
let when = state.child_token(SyntaxKind::Identifier).filter(|t| t.text() == "when");
#[cfg(feature = "slint-sc")]
if condition.is_none() {
diag.slint_sc_error(
"A state without a 'when' condition is",
&state.DeclaredIdentifier(),
);
}
let s = State {
id: parser::identifier_text(&state.DeclaredIdentifier()).unwrap_or_default(),
condition: condition.map(|e| Expression::Uncompiled(e.into())),
property_changes: state
.StatePropertyChange()
.filter_map(|s| {
lookup_property_from_qualified_name_for_state(s.QualifiedName(), &r, diag)
.map(|(ne, _)| {
(ne, Expression::Uncompiled(s.BindingExpression().into()), s)
})
})
.collect(),
selection: when.map(|when| ConditionLocation::StateSelection {
name: state.DeclaredIdentifier().to_source_location(),
when: when.to_source_location(),
}),
};
for trs in state.Transition() {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Transitions are", &trs);
let mut t = Transition::from_node(trs, &r, tr, diag);
t.state_id.clone_from(&s.id);
r.borrow_mut().transitions.push(t);
}
r.borrow_mut().states.push(s);
}
for ts in node.Transitions() {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Transitions are", &ts);
if !is_legacy_syntax {
diag.push_error("'transitions' block are no longer supported. Use 'in {...}' and 'out {...}' directly in the state definition".into(), &ts);
}
for trs in ts.Transition() {
let trans = Transition::from_node(trs, &r, tr, diag);
r.borrow_mut().transitions.push(trans);
}
}
if r.borrow().base_type.to_smolstr() == "ListView" {
let mut seen_for = false;
for se in node.children() {
if se.kind() == SyntaxKind::RepeatedElement && !seen_for {
seen_for = true;
} else if matches!(
se.kind(),
SyntaxKind::SubElement
| SyntaxKind::ConditionalElement
| SyntaxKind::RepeatedElement
| SyntaxKind::ChildrenPlaceholder
) {
diag.push_error("A ListView can just have a single 'for' as children. Anything else is not supported".into(), &se)
}
}
}
interfaces::validate_self_implement_statements(&r.borrow(), &implemented_interfaces, diag);
interfaces::apply_child_implement_statements(&r, child_implements, diag);
r
}
fn from_sub_element_node(
node: syntax_nodes::SubElement,
parent_type: ElementType,
component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
declared_slots: &mut Vec<DeclaredSlot>,
is_in_legacy_component: bool,
diag: &mut BuildDiagnostics,
tr: &TypeRegister,
) -> ElementRc {
let mut id = parser::identifier_text(&node).unwrap_or_default();
if matches!(id.as_ref(), "parent" | "self" | "root") {
diag.push_error(
format!("'{id}' is a reserved id"),
&node.child_token(SyntaxKind::Identifier).unwrap(),
);
id = SmolStr::default();
}
Element::from_node(
node.Element(),
id,
parent_type,
component_child_insertion_points,
declared_slots,
is_in_legacy_component,
diag,
tr,
)
}
fn assert_experimental_slots(
diagnostics: &mut BuildDiagnostics,
node: &SyntaxNode,
what: &str,
) -> bool {
if diagnostics.enable_experimental {
return true;
}
diagnostics.push_error(format!("'{what}' is an experimental feature"), node);
false
}
fn sub_element_slot_placeholder_name(
node: &SyntaxNode,
declared_slots: &[DeclaredSlot],
) -> Option<SmolStr> {
if node.child_token(SyntaxKind::ColonEqual).is_some() {
return None;
}
let element = node.child_node(SyntaxKind::Element)?;
if element.children().any(|c| c.kind() != SyntaxKind::QualifiedName) {
return None;
}
let qualified_name = element.child_node(SyntaxKind::QualifiedName)?;
if qualified_name.child_token(SyntaxKind::Dot).is_some() {
return None;
}
let name = parser::identifier_text(&qualified_name)?;
declared_slots.iter().any(|slot| slot.name == name).then_some(name)
}
fn mark_placeholder_rejected(declared_slots: &mut [DeclaredSlot], name: &str) {
if let Some(slot) = declared_slots.iter_mut().find(|slot| slot.name.as_str() == name) {
slot.has_rejected_placeholder = true;
}
}
fn reject_slot_placeholders(
diagnostics: &mut BuildDiagnostics,
declared_slots: &mut [DeclaredSlot],
insertion_points: BTreeMap<String, ChildrenInsertionPoint>,
context: &str,
) {
for (name, ChildrenInsertionPoint { node, .. }) in insertion_points {
Self::mark_placeholder_rejected(declared_slots, &name);
diagnostics.push_error(
format!("{} cannot appear in {context}", slot_error_subject(&name)),
&node,
);
}
}
fn register_slot_placeholder(
node: &SyntaxNode,
slot_name: SmolStr,
parent: &ElementRc,
component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
diagnostics: &mut BuildDiagnostics,
type_register: &TypeRegister,
) {
Self::assert_experimental_slots(diagnostics, node, "named slots");
if let Some(existing) = component_child_insertion_points.get(slot_name.as_str()) {
if matches!(existing.node, ChildInsertionPointNode::SlotForwarding(_)) {
diagnostics.push_error(
format!(
"The slot '{slot_name}' cannot be forwarded and used as a placeholder in the same component"
),
node,
);
} else {
diagnostics.push_error(
format!(
"{} can only appear once in an element",
slot_error_subject(&slot_name)
),
node,
);
}
return;
}
if type_register.lookup_element(slot_name.as_str()).is_ok() {
diagnostics.push_warning(
format!(
"{} shadows an element type of the same name. This element is a slot placeholder, not an instance of '{slot_name}'",
slot_error_subject(&slot_name)
),
node,
);
}
let insertion_index = parent.borrow().children.len();
component_child_insertion_points.insert(
slot_name.to_string(),
ChildrenInsertionPoint {
parent: parent.clone(),
insertion_index,
node: ChildInsertionPointNode::SlotPlaceholder(node.clone().into()),
},
);
}
fn from_repeated_node(
node: syntax_nodes::RepeatedElement,
parent: &ElementRc,
component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
declared_slots: &mut Vec<DeclaredSlot>,
is_in_legacy_component: bool,
diag: &mut BuildDiagnostics,
tr: &TypeRegister,
) -> ElementRc {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Repeated elements (for-in) are", &node);
let e = Element::from_sub_element_node(
node.SubElement(),
parent.borrow().base_type.clone(),
component_child_insertion_points,
declared_slots,
is_in_legacy_component,
diag,
tr,
);
let parent_is_listview = {
let parent = parent.borrow();
parent.base_type.to_string() == "ListView"
&& [
"content-y",
"content-height",
"content-width",
"visible-height",
"visible-width",
]
.iter()
.all(|p| parent.lookup_property(p, PropertyLookupMode::InternalName).property_type == Type::LogicalLength)
};
let is_listview = if parent_is_listview
&& let Some(geometry_props) = e.borrow().geometry_props.as_ref()
{
let parent_elem = parent.borrow();
let (content_width_is_explicitly_set, content_height_is_explicitly_set) = {
let has_binding = |name| parent_elem.binding(name).is_some_and(|b| b.has_binding());
(
has_binding("content-width") || has_binding("viewport-width"),
has_binding("content-height") || has_binding("viewport-height"),
)
};
drop(parent_elem);
let lvi = ListViewInfo {
content_y: NamedReference::new(parent, SmolStr::new_static("content-y")),
content_height: (!content_height_is_explicitly_set)
.then(|| NamedReference::new(parent, SmolStr::new_static("content-height"))),
content_width: (!content_width_is_explicitly_set)
.then(|| NamedReference::new(parent, SmolStr::new_static("content-width"))),
listview_height: NamedReference::new(parent, SmolStr::new_static("visible-height")),
listview_width: NamedReference::new(parent, SmolStr::new_static("visible-width")),
};
if let Some(content_height) = &lvi.content_height {
content_height.mark_as_set();
}
if let Some(content_width) = &lvi.content_width {
content_width.mark_as_set();
}
geometry_props.y.mark_as_set();
Some(lvi)
} else {
None
};
let rei = RepeatedElementInfo {
model: Expression::Uncompiled(node.Expression().into()),
model_data_id: node
.DeclaredIdentifier()
.and_then(|n| parser::identifier_text(&n))
.unwrap_or_default(),
index_id: node
.RepeatedIndex()
.and_then(|r| parser::identifier_text(&r))
.unwrap_or_default(),
is_conditional_element: false,
is_listview,
};
e.borrow_mut().repeated = Some(rei);
e
}
fn from_conditional_node(
node: syntax_nodes::ConditionalElement,
parent_type: ElementType,
component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
declared_slots: &mut Vec<DeclaredSlot>,
is_in_legacy_component: bool,
diag: &mut BuildDiagnostics,
tr: &TypeRegister,
) -> ElementRc {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Conditional elements (if) are", &node);
let rei = RepeatedElementInfo {
model: Expression::Uncompiled(node.Expression().into()),
model_data_id: SmolStr::default(),
index_id: SmolStr::default(),
is_conditional_element: true,
is_listview: None,
};
let e = Element::from_sub_element_node(
node.SubElement(),
parent_type,
component_child_insertion_points,
declared_slots,
is_in_legacy_component,
diag,
tr,
);
e.borrow_mut().repeated = Some(rei);
e
}
fn from_match_node(
node: syntax_nodes::MatchElement,
parent_type: ElementType,
component_child_insertion_points: &mut BTreeMap<String, ChildrenInsertionPoint>,
declared_slots: &mut Vec<DeclaredSlot>,
is_in_legacy_component: bool,
diag: &mut BuildDiagnostics,
tr: &TypeRegister,
) -> MatchElementInfo {
if !diag.enable_experimental {
diag.push_error("match elements are an experimental feature".into(), &node);
}
if node.MatchCase().next().is_none() && node.WildcardMatchCase().is_none() {
diag.push_error("Expected at least one case".into(), &node);
}
if let Some(wildcard) = node.WildcardMatchCase()
&& node.MatchCase().next().is_none()
{
diag.push_warning(
"Unnecessary match statement always matches the '*' case".into(),
&wildcard,
);
}
let mut element_of = |sub_element| {
Element::from_sub_element_node(
sub_element,
parent_type.clone(),
component_child_insertion_points,
declared_slots,
is_in_legacy_component,
diag,
tr,
)
};
let cases = node
.MatchCase()
.map(|case| {
let node = case.Expression();
MatchCaseInfo {
value: Expression::Uncompiled(node.clone().into()),
node,
element: case.SubElement().map(&mut element_of),
}
})
.collect();
let wildcard = match node.WildcardMatchCase() {
None => WildcardMatchCaseInfo::None,
Some(w) => match w.SubElement().map(&mut element_of) {
None => WildcardMatchCaseInfo::Empty,
Some(element) => WildcardMatchCaseInfo::Element(element),
},
};
MatchElementInfo {
subject: Expression::Uncompiled(node.Expression().into()),
node,
cases,
wildcard,
}
}
#[cfg(feature = "slint-sc")]
pub fn is_user_declared_member(&self, name: &str) -> bool {
match self.declaration(name) {
Some((_, declaration)) => declaration.node.is_some(),
None => match &self.base_type {
ElementType::Component(c) => c.root_element.borrow().is_user_declared_member(name),
_ => false,
},
}
}
pub fn lookup_property<'a>(
&self,
name: &'a str,
mode: PropertyLookupMode,
) -> PropertyLookupResult<'a> {
let declaration = match mode {
PropertyLookupMode::InternalName => self.property_declarations.get_key_value(name),
PropertyLookupMode::ComponentLocal | PropertyLookupMode::FromOutside => {
self.declaration(name)
}
};
if let Some((internal_name, decl)) = declaration {
if mode == PropertyLookupMode::FromOutside && decl.is_private_shadow() {
return from_base(
self.base_type.lookup_property(name, PropertyLookupMode::FromOutside),
);
}
let mut r = self.lookup_result_for_declaration(name.into(), decl);
if internal_name != name {
r.internal_name = Some(internal_name.clone());
}
return r;
}
let base_mode = match mode {
PropertyLookupMode::InternalName => PropertyLookupMode::InternalName,
_ => PropertyLookupMode::FromOutside,
};
from_base(self.base_type.lookup_property(name, base_mode))
}
pub fn declaration(&self, name: &str) -> Option<(&SmolStr, &PropertyDeclaration)> {
if let Some(internal_name) = self.shadowing_members.get(name) {
return self.property_declarations.get_key_value(internal_name);
}
self.property_declarations.get_key_value(name).filter(|(_, d)| d.shadowed_name.is_none())
}
pub fn visible_shadowing_members(&self) -> impl Iterator<Item = &SmolStr> {
self.shadowing_members.iter().filter_map(|(source, internal)| {
self.property_declarations
.get(internal)
.filter(|d| !d.is_private_shadow())
.map(|_| source)
})
}
fn member_declaration(&self, name: &SmolStr) -> MemberDeclaration {
if self.property_declarations.get(name.as_str()).is_some_and(|d| d.shadowed_name.is_some())
{
return MemberDeclaration::Shadow {
internal_name: self.unique_member_name(name),
warning: None,
};
}
let existing = self.lookup_property(name, PropertyLookupMode::ComponentLocal);
if !existing.is_valid() {
return MemberDeclaration::New;
}
if existing.is_local_to_component {
return MemberDeclaration::Conflict {
existing_type: existing.property_type,
declared_in: None,
};
}
let declared_in = self.declaring_base_component(name);
let private =
declared_in.is_some() && existing.property_visibility == PropertyVisibility::Private;
if !private && !existing.is_shadowable {
return MemberDeclaration::Conflict {
existing_type: existing.property_type,
declared_in,
};
}
let origin = if declared_in.is_some() { "inherited" } else { "builtin" };
MemberDeclaration::Shadow {
internal_name: self.unique_member_name(name),
warning: (!private).then(|| {
let kind = match existing.property_type {
Type::Callback { .. } => "callback",
Type::Function { .. } => "function",
_ => "property",
};
format!("'{name}' shadows the {origin} {kind} of the same name")
}),
}
}
pub fn unique_member_name(&self, base: &str) -> SmolStr {
(1..)
.map(|counter| format_smolstr!("{base}-{counter}"))
.find(|n| !self.lookup_property(n, PropertyLookupMode::InternalName).is_valid())
.unwrap()
}
fn declaring_base_component(&self, name: &str) -> Option<Rc<Component>> {
let mut base = self.base_type.clone();
loop {
let ElementType::Component(c) = base else { return None };
let declares = {
let root = c.root_element.borrow();
root.shadowing_members.contains_key(name)
|| root.property_declarations.contains_key(name)
};
if declares {
return Some(c);
}
base = c.root_element.borrow().base_type.clone();
}
}
fn lookup_result_for_declaration<'a>(
&self,
resolved_name: std::borrow::Cow<'a, str>,
p: &PropertyDeclaration,
) -> PropertyLookupResult<'a> {
PropertyLookupResult {
resolved_name,
property_type: p.property_type.clone(),
property_visibility: p.visibility,
declared_pure: p.pure,
is_local_to_component: true,
is_in_direct_base: false,
is_shadowable: p.shadowable,
builtin_function: None,
is_slint_sc: true,
deprecated: p.deprecated.clone(),
internal_name: None,
}
}
fn parse_bindings(
&mut self,
bindings: impl Iterator<Item = (crate::parser::SyntaxToken, SyntaxNode)>,
is_in_legacy_component: bool,
diag: &mut BuildDiagnostics,
) {
for (name_token, b) in bindings {
let unresolved_name = crate::parser::normalize_identifier(name_token.text());
let lookup_result =
self.lookup_property(&unresolved_name, PropertyLookupMode::ComponentLocal);
#[cfg(feature = "slint-sc")]
if b.kind() == SyntaxKind::TwoWayBinding {
diag.slint_sc_error("Two-way bindings are", &b);
} else {
lookup_result.check_slint_sc(&unresolved_name, &name_token, diag);
}
if !lookup_result.property_type.is_property_type() {
match lookup_result.property_type {
Type::Invalid => {
if self.base_type != ElementType::Error {
let msg = if let Some(suggestion) = css_property_suggestion(&unresolved_name, &self.base_type) {
suggestion
} else if self.base_type.to_smolstr() == "Empty" {
format!( "Unknown property {unresolved_name}")
} else {
format!( "Unknown property {unresolved_name} in {}", self.base_type)
};
diag.push_error(msg, &name_token);
}
}
Type::Callback { .. } => {
diag.push_error(format!("'{unresolved_name}' is a callback. Use `=>` to connect"),
&name_token)
}
_ => diag.push_error(format!(
"Cannot assign to {} in {} because it does not have a valid property type",
unresolved_name, self.base_type,
),
&name_token),
}
} else if !lookup_result.is_local_to_component
&& (lookup_result.property_visibility == PropertyVisibility::Private
|| lookup_result.property_visibility == PropertyVisibility::Output)
{
if is_in_legacy_component
&& lookup_result.property_visibility == PropertyVisibility::Output
{
diag.push_warning(
format!(
"Assigning to '{}' property '{unresolved_name}' is deprecated",
PropertyVisibility::Output
),
&name_token,
);
} else {
diag.push_error(
format!(
"Cannot assign to '{}' property '{}'",
lookup_result.property_visibility, unresolved_name
),
&name_token,
);
}
}
if *lookup_result.resolved_name != *unresolved_name {
diag.push_property_deprecation_warning(
&unresolved_name,
&lookup_result.resolved_name,
&name_token,
);
} else if let Some(message) =
lookup_result.deprecated.as_ref().filter(|_| !lookup_result.is_local_to_component)
{
diag.push_property_deprecation_warning_with_message(
&unresolved_name,
message,
&name_token,
);
}
match self.bindings.0.entry(lookup_result.internal_or_resolved_name()) {
Entry::Occupied(_) => {
diag.push_error("Duplicated property binding".into(), &name_token);
}
Entry::Vacant(entry) => {
entry.insert(BindingExpression::new_uncompiled(b).into());
}
};
}
}
pub fn property_declaration_node(&self, name: &str) -> Option<SyntaxNode> {
self.property_declarations
.get(name)
.and_then(|declaration| declaration.node.clone())
.or_else(|| self.base_type.property_declaration_node(name))
}
fn slot_forwarding_expr_identifier(expression: &SyntaxNode) -> Option<SmolStr> {
if expression.kind() != SyntaxKind::Expression {
return None;
}
let mut expr_children = expression.children();
let qualified_name = expr_children.find(|n| n.kind() == SyntaxKind::QualifiedName)?;
if expr_children.next().is_some() {
return None;
}
let mut identifiers = qualified_name
.children_with_tokens()
.filter(|n| n.kind() == SyntaxKind::Identifier)
.filter_map(|n| n.into_token());
let identifier = identifiers.next()?;
if identifiers.next().is_some() {
return None;
}
Some(crate::parser::normalize_identifier(identifier.text()))
}
pub fn callback_alias_declaration_node(
&self,
name: &str,
) -> Option<syntax_nodes::TwoWayBinding> {
self.property_declarations
.get(name)
.and_then(|d| d.node.clone())
.and_then(syntax_nodes::CallbackDeclaration::new)
.and_then(|cb| cb.TwoWayBinding())
}
pub fn two_way_binding_node(&self, name: &str) -> Option<syntax_nodes::TwoWayBinding> {
if let Some(binding) = self.bindings.0.get(name)
&& let Ok(b) = binding.try_borrow()
&& let Expression::Uncompiled(node) = b.value_expression()
&& let Some(twb) = syntax_nodes::TwoWayBinding::new(node.clone())
{
return Some(twb);
}
self.callback_alias_declaration_node(name)
}
pub fn native_class(&self) -> Option<Arc<NativeClass>> {
let mut base_type = self.base_type.clone();
loop {
match &base_type {
ElementType::Component(component) => {
base_type = component.root_element.clone().borrow().base_type.clone();
}
ElementType::Builtin(builtin) => break Some(builtin.native_class.clone()),
ElementType::Native(native) => break Some(native.clone()),
_ => break None,
}
}
}
pub fn builtin_type(&self) -> Option<Rc<BuiltinElement>> {
let mut base_type = self.base_type.clone();
loop {
match &base_type {
ElementType::Component(component) => {
base_type = component.root_element.clone().borrow().base_type.clone();
}
ElementType::Builtin(builtin) => break Some(builtin.clone()),
_ => break None,
}
}
}
pub(crate) fn effective_layout_info_prop(
&self,
orientation: Orientation,
) -> Option<&NamedReference> {
let prop = self.layout_info_prop.as_ref()?;
match orientation {
Orientation::Horizontal => Some(
self.layout_info_h_at_own_height
.as_ref()
.filter(|_| self.height_is_literal)
.unwrap_or(&prop.0),
),
Orientation::Vertical => Some(&prop.1),
}
}
pub fn is_builtin_height_for_width(&self) -> bool {
let Some(builtin) = self.builtin_type() else { return false };
match builtin.name.as_str() {
"Text" | "TextInput" => self.is_binding_set("wrap", false),
"Image" | "ClippedImage" => !self.is_binding_set("height", true),
"StyledText" => true,
_ => false,
}
}
pub fn inherited_layout_info_v_with_constraint(&self) -> Option<NamedReference> {
if let Some(nr) = &self.layout_info_v_with_constraint {
return Some(nr.clone());
}
let mut base = self.base_type.clone();
while let ElementType::Component(base_comp) = base {
let root = base_comp.root_element.borrow();
if let Some(nr) = &root.layout_info_v_with_constraint {
return Some(nr.clone());
}
base = root.base_type.clone();
}
None
}
pub fn has_inherited_layout_info_v_with_constraint(&self) -> bool {
if self.layout_info_v_with_constraint.is_some() {
return true;
}
let mut base = self.base_type.clone();
while let ElementType::Component(base_comp) = base {
let root = base_comp.root_element.borrow();
if root.layout_info_v_with_constraint.is_some() {
return true;
}
base = root.base_type.clone();
}
false
}
pub fn layout_info_includes_own_constraints(&self, orientation: Orientation) -> bool {
self.effective_layout_info_prop(orientation).is_some()
|| (orientation == Orientation::Vertical
&& self.has_inherited_layout_info_v_with_constraint())
}
pub fn original_name(&self) -> SmolStr {
self.debug
.first()
.and_then(|n| n.node.child_token(parser::SyntaxKind::Identifier))
.map(|n| n.to_smolstr())
.unwrap_or_else(|| self.id.clone())
}
pub fn has_dynamic_z_order(&self) -> bool {
self.children.iter().any(|c| c.borrow().z_order.is_some())
}
pub fn is_binding_set(self: &Element, property_name: &str, need_explicit: bool) -> bool {
self.any_in_inheritance_chain(|element| {
element.bindings.0.get(property_name).is_some_and(|binding| {
let binding = binding.borrow();
binding.has_binding() && (!need_explicit || binding.priority > 0)
})
})
}
pub(crate) fn base_layout_info_prop(
&self,
orientation: Orientation,
height_settled: bool,
) -> Option<NamedReference> {
let ElementType::Component(base) = &self.base_type else { return None };
let root = base.root_element.borrow();
root.layout_info_h_at_own_height
.clone()
.filter(|_| orientation == Orientation::Horizontal && height_settled)
.or_else(|| root.effective_layout_info_prop(orientation).cloned())
}
pub(crate) fn compute_height_is_literal(elem: &ElementRc) -> bool {
let overridable_root = elem.borrow().enclosing_component.upgrade().is_some_and(|c| {
Rc::ptr_eq(&c.root_element, elem)
&& c.used.get()
&& c.parent_element.borrow().upgrade().is_none()
});
if overridable_root {
return false;
}
crate::layout::find_binding(elem, "height", |b, _, _| {
matches!(b.value_expression(), Expression::NumberLiteral(_, unit) if *unit != Unit::Percent)
})
.unwrap_or(false)
}
pub fn is_property_set(self: &Element, property_name: &str) -> bool {
self.any_in_inheritance_chain(|element| {
element
.bindings
.0
.get(property_name)
.is_some_and(|binding| !binding.borrow().expression.is_synthetic_debug_hook())
|| element
.property_analysis
.borrow()
.get(property_name)
.is_some_and(|analysis| analysis.is_set || analysis.is_linked)
})
}
pub(crate) fn is_property_target_of_two_way_binding(&self, property_name: &str) -> bool {
self.any_in_inheritance_chain(|element| {
element
.property_analysis
.borrow()
.get(property_name)
.is_some_and(|analysis| analysis.is_linked)
})
}
pub fn any_in_inheritance_chain(&self, predicate: impl Fn(&Element) -> bool + Copy) -> bool {
predicate(self)
|| matches!(
&self.base_type,
ElementType::Component(base)
if base.root_element.borrow().any_in_inheritance_chain(predicate)
)
}
pub fn binding(&self, property_name: &str) -> Option<Ref<'_, BindingExpression>> {
self.bindings
.0
.get(property_name)
.filter(|binding| !binding.borrow().expression.is_synthetic_debug_hook())
.map(|binding| binding.borrow())
}
pub fn binding_mut(&self, property_name: &str) -> Option<RefMut<'_, BindingExpression>> {
self.bindings
.0
.get(property_name)
.filter(|binding| !binding.borrow().expression.is_synthetic_debug_hook())
.map(|binding| binding.borrow_mut())
}
pub fn real_bindings(&self) -> impl Iterator<Item = (&SmolStr, &RefCell<BindingExpression>)> {
self.bindings
.0
.iter()
.filter(|(_, binding)| !binding.borrow().expression.is_synthetic_debug_hook())
}
pub fn bindings_including_synthetic(
&self,
) -> impl Iterator<Item = (&SmolStr, &RefCell<BindingExpression>)> {
self.bindings.0.iter()
}
pub fn binding_cell_including_synthetic(
&self,
property_name: &str,
) -> Option<&RefCell<BindingExpression>> {
self.bindings.0.get(property_name)
}
pub fn set_binding_if_not_set(
&mut self,
property_name: SmolStr,
expression_fn: impl FnOnce() -> Expression,
) -> bool {
if self.is_binding_set(&property_name, false) {
return false;
}
match self.bindings.0.entry(property_name) {
Entry::Vacant(vacant_entry) => {
let mut binding: BindingExpression = expression_fn().into();
binding.priority = i32::MAX;
vacant_entry.insert(binding.into());
}
Entry::Occupied(mut existing_entry) => {
let inner = existing_entry.get_mut().get_mut();
let mut binding: BindingExpression = expression_fn().into();
binding.priority = i32::MAX;
inner.merge_with(&binding);
}
};
true
}
pub fn set_binding(
&mut self,
property_name: SmolStr,
mut new_binding: BindingExpression,
) -> Option<BindingExpression> {
match self.bindings.0.entry(property_name) {
Entry::Vacant(v) => {
v.insert(RefCell::new(new_binding));
None
}
Entry::Occupied(mut e) => {
let existing = e.get_mut().get_mut();
if let expression_tree::Expression::DebugHook { expression: _, synthetic, id } =
&mut existing.expression
&& *synthetic
{
let new_debug_hook = expression_tree::Expression::DebugHook {
expression: Box::new(new_binding.expression),
id: id.clone(),
synthetic: false,
};
new_binding.expression = new_debug_hook;
*existing = new_binding;
return None;
}
Some(std::mem::replace(e.get_mut().get_mut(), new_binding))
}
}
}
pub fn take_binding(&mut self, property_name: &str) -> Option<BindingExpression> {
self.take_binding_including_synthetic(property_name)
.filter(|binding| !binding.expression.is_synthetic_debug_hook())
}
pub fn take_binding_including_synthetic(
&mut self,
property_name: &str,
) -> Option<BindingExpression> {
self.bindings.0.remove(property_name).map(RefCell::into_inner)
}
pub(crate) fn take_bindings_including_synthetic(&mut self) -> BindingsMap {
std::mem::take(&mut self.bindings.0)
}
pub(crate) fn extend_bindings_including_synthetic(
&mut self,
bindings: impl IntoIterator<Item = (SmolStr, RefCell<BindingExpression>)>,
) {
self.bindings.0.extend(bindings);
}
pub fn sub_component(&self) -> Option<&Rc<Component>> {
if self.repeated.is_some() {
None
} else if let ElementType::Component(sub_component) = &self.base_type {
Some(sub_component)
} else {
None
}
}
pub fn element_infos(&self) -> String {
let mut debug_infos = self.debug.clone();
let mut base = self.base_type.clone();
while let ElementType::Component(b) = base {
let elem = b.root_element.borrow();
base = elem.base_type.clone();
debug_infos.extend(elem.debug.iter().cloned());
}
let (infos, _, _) = debug_infos.into_iter().fold(
(String::new(), false, true),
|(mut infos, elem_boundary, first), debug_info| {
if elem_boundary {
infos.push('/');
} else if !first {
infos.push(';');
}
infos.push_str(&debug_info.encoded_element_info());
(infos, debug_info.element_boundary, false)
},
);
infos
}
}
fn css_property_suggestion(property_name: &str, base_type: &ElementType) -> Option<String> {
let base_name = base_type.to_smolstr();
if base_name != "FlexboxLayout" {
return None;
}
match property_name {
"gap" => Some("Use spacing instead of gap".into()),
"row-gap" => Some("Use spacing-vertical instead of row-gap".into()),
"column-gap" => Some("Use spacing-horizontal instead of column-gap".into()),
"justify-content" => Some("Use alignment instead of justify-content".into()),
_ => None,
}
}
pub(crate) fn apply_default_type_properties(element: &mut Element) {
if let ElementType::Builtin(builtin_base) = &element.base_type {
for (prop, info) in &builtin_base.properties {
if element.property_declarations.contains_key(prop) {
continue;
}
if let Some(expr) = info.default_value.expr_without_element() {
element.bindings.0.entry(prop.clone()).or_insert_with(|| {
let mut binding = BindingExpression::from(expr);
binding.priority = i32::MAX;
RefCell::new(binding)
});
}
}
}
}
pub fn type_from_node(
node: syntax_nodes::Type,
diag: &mut BuildDiagnostics,
tr: &TypeRegister,
) -> Type {
if let Some(qualified_type_node) = node.QualifiedName() {
let qualified_type = QualifiedTypeName::from_node(qualified_type_node.clone());
let prop_type = tr.lookup_qualified(&qualified_type.members);
#[cfg(feature = "slint-sc")]
if !prop_type.is_slint_sc() {
diag.slint_sc_error(&format!("The type '{qualified_type}' is"), &qualified_type_node);
}
if prop_type == Type::Invalid && tr.lookup_element(&qualified_type.to_smolstr()).is_err() {
diag.push_error(format!("Unknown type '{qualified_type}'"), &qualified_type_node);
} else if !prop_type.is_property_type() {
diag.push_error(
format!("'{qualified_type}' is not a valid type"),
&qualified_type_node,
);
}
prop_type
} else if let Some(object_node) = node.ObjectType() {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Inline struct types are", &object_node);
type_struct_from_node(object_node, diag, tr, None, None)
} else if let Some(array_node) = node.ArrayType() {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Array types are", &array_node);
Type::Array(Arc::new(type_from_node(array_node.Type(), diag, tr)))
} else {
assert!(diag.has_errors());
Type::Invalid
}
}
pub fn type_struct_from_node(
object_node: syntax_nodes::ObjectType,
diag: &mut BuildDiagnostics,
tr: &TypeRegister,
name: Option<SmolStr>,
symbol_counters: Option<&Rc<crate::symbol_counters::SymbolCounters>>,
) -> Type {
let mut field_defaults = BTreeMap::default();
let mut field_order = Vec::new();
let fields: BTreeMap<SmolStr, Type> = object_node
.ObjectTypeMember()
.map(|member| {
let field_name = parser::identifier_text(&member).unwrap_or_default();
field_order.push(field_name.clone());
let field_ty = type_from_node(member.Type(), diag, tr);
if let Some(default_value_node) = member.Expression() {
if name.is_none() {
diag.push_error(
"Field default values are only supported in named struct declarations"
.into(),
&default_value_node,
);
} else if let Some(expr) = resolve_struct_field_default_value(
default_value_node,
&field_ty,
diag,
tr,
symbol_counters.expect("named struct declarations have symbol counters"),
) {
field_defaults.insert(field_name.clone(), expr);
}
}
(field_name, field_ty)
})
.collect();
let struct_decl = object_node.parent();
Type::Struct(Arc::new(Struct {
fields,
field_defaults,
name: name.map_or(StructName::None, |name| {
let rust_attributes = struct_decl
.as_ref()
.and_then(|p| syntax_nodes::StructDeclaration::new(p.clone()))
.map(|d| d.AtRustAttr().map(|a| SmolStr::from(a.text().to_string())).collect())
.unwrap_or_default();
let node = struct_decl.as_ref().unwrap_or(&object_node).to_source_location();
StructName::User { name, node, rust_attributes, field_order }
}),
}))
}
fn resolve_struct_field_default_value(
node: syntax_nodes::Expression,
field_ty: &Type,
diag: &mut BuildDiagnostics,
tr: &TypeRegister,
symbol_counters: &Rc<crate::symbol_counters::SymbolCounters>,
) -> Option<crate::langtype::ConstantExpression> {
#[cfg(feature = "slint-sc")]
diag.slint_sc_error("Struct field default values are", &node);
let mut expr = {
let mut ctx = crate::lookup::LookupCtx::empty_context(tr, diag, symbol_counters.clone());
ctx.property_type = field_ty.clone();
Expression::from_expression_node(node.clone(), &mut ctx).maybe_convert_to(
field_ty.clone(),
&node,
ctx.diag,
&ctx.symbol_counters,
)
};
crate::passes::const_propagation::fold_const_expression(&mut expr);
let mut has_invalid = false;
expr.visit_recursive(&mut |e| has_invalid |= matches!(e, Expression::Invalid));
if has_invalid {
return None;
}
let constant = crate::langtype::ConstantExpression::from_expression(&expr);
if constant.is_none() {
let reason = non_constant_expression_reason(&expr)
.map_or_else(Default::default, |reason| format!(": {reason}"));
diag.push_error(
format!("The default value of a struct field must be a constant expression{reason}"),
&node,
);
}
constant
}
fn non_constant_expression_reason(expr: &Expression) -> Option<String> {
use crate::expression_tree::{BuiltinFunction, Callable};
let mut reason = None;
expr.visit_recursive(&mut |e| {
if reason.is_some() {
return;
}
reason = match e {
Expression::PropertyReference(nr) => {
Some(format!("it references the property '{}'", nr.name()))
}
Expression::FunctionCall { function, .. } => match function {
Callable::Function(nr) => Some(format!("it calls the function '{}'", nr.name())),
Callable::Callback(nr) => Some(format!("it calls the callback '{}'", nr.name())),
Callable::Builtin(BuiltinFunction::GetWindowScaleFactor) => Some(
"the conversion to logical pixels depends on the window's scale factor".into(),
),
Callable::Builtin(BuiltinFunction::GetWindowDefaultFontSize) => Some(
"the conversion from 'rem' depends on the window's default font size".into(),
),
Callable::Builtin(BuiltinFunction::Translate) => {
Some("the translation is selected at run-time".into())
}
Callable::Builtin(_) => Some("functions are not evaluated at compile time".into()),
},
Expression::Cast { to: Type::String, .. } => {
Some("the conversion from a number to a string depends on the locale".into())
}
_ => None,
};
});
reason
}
fn animation_element_from_node(
anim: &syntax_nodes::PropertyAnimation,
prop_name: &syntax_nodes::QualifiedName,
prop_type: Type,
diag: &mut BuildDiagnostics,
tr: &TypeRegister,
) -> Option<ElementRc> {
let anim_type = tr.property_animation_type_for_property(prop_type);
if !matches!(anim_type, ElementType::Builtin(..)) {
diag.push_error(
format!(
"'{}' is not a property that can be animated",
prop_name.text().to_string().trim()
),
prop_name,
);
None
} else {
let mut anim_element =
Element { id: "".into(), base_type: anim_type, ..Default::default() };
anim_element.parse_bindings(
anim.Binding().filter_map(|b| {
Some((b.child_token(SyntaxKind::Identifier)?, b.BindingExpression().into()))
}),
false,
diag,
);
apply_default_type_properties(&mut anim_element);
Some(Rc::new(RefCell::new(anim_element)))
}
}
#[derive(Default, Debug, Clone)]
pub struct QualifiedTypeName {
pub members: Vec<SmolStr>,
}
impl QualifiedTypeName {
pub fn from_node(node: syntax_nodes::QualifiedName) -> Self {
debug_assert_eq!(node.kind(), SyntaxKind::QualifiedName);
let members = node
.children_with_tokens()
.filter(|n| n.kind() == SyntaxKind::Identifier)
.filter_map(|x| x.as_token().map(|x| crate::parser::normalize_identifier(x.text())))
.collect();
Self { members }
}
pub fn to_smolstr(&self) -> SmolStr {
self.members.join(".").into()
}
}
impl Display for QualifiedTypeName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.members.join("."))
}
}
fn lookup_property_from_qualified_name_for_state(
node: syntax_nodes::QualifiedName,
r: &ElementRc,
diag: &mut BuildDiagnostics,
) -> Option<(NamedReference, Type)> {
let qualname = QualifiedTypeName::from_node(node.clone());
let check = |lookup: &PropertyLookupResult<'_>, diag: &mut BuildDiagnostics| {
#[cfg(feature = "slint-sc")]
lookup.check_slint_sc(&qualname, &node, diag);
if !lookup.property_type.is_property_type() {
diag.push_error(format!("'{qualname}' is not a valid property"), &node);
} else if !lookup.is_valid_for_assignment() {
diag.push_error(
format!(
"'{}' cannot be set in a state because it is '{}'",
qualname, lookup.property_visibility
),
&node,
);
}
};
match qualname.members.as_slice() {
[unresolved_prop_name] => {
let lookup_result = r
.borrow()
.lookup_property(unresolved_prop_name.as_ref(), PropertyLookupMode::ComponentLocal);
check(&lookup_result, diag);
Some((
NamedReference::new(r, lookup_result.internal_or_resolved_name()),
lookup_result.property_type,
))
}
[elem_id, unresolved_prop_name] => {
if let Some(element) = find_element_by_id(r, elem_id.as_ref()) {
let lookup_result = element.borrow().lookup_property(
unresolved_prop_name.as_ref(),
PropertyLookupMode::ComponentLocal,
);
if !lookup_result.is_valid() {
diag.push_error(
format!("'{unresolved_prop_name}' not found in '{elem_id}'"),
&node,
);
} else {
check(&lookup_result, diag);
}
Some((
NamedReference::new(&element, lookup_result.internal_or_resolved_name()),
lookup_result.property_type,
))
} else {
diag.push_error(format!("'{elem_id}' is not a valid element id"), &node);
None
}
}
_ => {
diag.push_error(format!("'{qualname}' is not a valid property"), &node);
None
}
}
}
fn find_element_by_id(e: &ElementRc, name: &str) -> Option<ElementRc> {
if e.borrow().id == name {
return Some(e.clone());
}
for x in &e.borrow().children {
if x.borrow().repeated.is_some() {
continue;
}
if let Some(x) = find_element_by_id(x, name) {
return Some(x);
}
}
None
}
pub fn find_parent_element(e: &ElementRc) -> Option<ElementRc> {
fn recurse(base: &ElementRc, e: &ElementRc) -> Option<ElementRc> {
for child in &base.borrow().children {
if Rc::ptr_eq(child, e) {
return Some(base.clone());
}
if let Some(x) = recurse(child, e) {
return Some(x);
}
}
None
}
let root = e.borrow().enclosing_component.upgrade().unwrap().root_element.clone();
if Rc::ptr_eq(&root, e) {
return None;
}
recurse(&root, e)
}
pub fn recurse_elem<State>(
elem: &ElementRc,
state: &State,
vis: &mut impl FnMut(&ElementRc, &State) -> State,
) {
recurse_elem_dyn(elem, state, vis)
}
fn recurse_elem_dyn<State>(
elem: &ElementRc,
state: &State,
vis: &mut dyn FnMut(&ElementRc, &State) -> State,
) {
let state = vis(elem, state);
for sub in &elem.borrow().children {
recurse_elem_dyn(sub, &state, vis);
}
}
pub fn recurse_elem_including_sub_components<State>(
component: &Component,
state: &State,
vis: &mut impl FnMut(&ElementRc, &State) -> State,
) {
recurse_elem_including_sub_components_dyn(component, state, vis)
}
fn recurse_elem_including_sub_components_dyn<State>(
component: &Component,
state: &State,
vis: &mut dyn FnMut(&ElementRc, &State) -> State,
) {
recurse_elem_dyn(&component.root_element, state, &mut |elem, state| {
debug_assert!(std::ptr::eq(
component as *const Component,
(&*elem.borrow().enclosing_component.upgrade().unwrap()) as *const Component
));
if elem.borrow().repeated.is_some()
&& let ElementType::Component(base) = &elem.borrow().base_type
&& base.parent_element().is_some()
{
recurse_elem_including_sub_components_dyn(base, state, vis);
}
vis(elem, state)
});
component
.popup_windows
.borrow()
.iter()
.for_each(|p| recurse_elem_including_sub_components_dyn(&p.component, state, vis));
component
.menu_item_tree
.borrow()
.iter()
.for_each(|c| recurse_elem_including_sub_components_dyn(c, state, vis));
}
pub fn recurse_elem_no_borrow<State>(
elem: &ElementRc,
state: &State,
vis: &mut impl FnMut(&ElementRc, &State) -> State,
) {
recurse_elem_no_borrow_dyn(elem, state, vis)
}
fn recurse_elem_no_borrow_dyn<State>(
elem: &ElementRc,
state: &State,
vis: &mut dyn FnMut(&ElementRc, &State) -> State,
) {
let state = vis(elem, state);
let children = elem.borrow().children.clone();
for sub in &children {
recurse_elem_no_borrow_dyn(sub, &state, vis);
}
}
pub fn recurse_elem_including_sub_components_no_borrow<State>(
component: &Component,
state: &State,
vis: &mut impl FnMut(&ElementRc, &State) -> State,
) {
recurse_elem_including_sub_components_no_borrow_dyn(component, state, vis)
}
fn recurse_elem_including_sub_components_no_borrow_dyn<State>(
component: &Component,
state: &State,
vis: &mut dyn FnMut(&ElementRc, &State) -> State,
) {
recurse_elem_no_borrow_dyn(&component.root_element, state, &mut |elem, state| {
let base = if elem.borrow().repeated.is_some() {
if let ElementType::Component(base) = &elem.borrow().base_type {
if base.parent_element().is_some() {
Some(base.clone())
} else {
None
}
} else {
None
}
} else {
None
};
if let Some(base) = base {
recurse_elem_including_sub_components_no_borrow_dyn(&base, state, vis);
}
vis(elem, state)
});
component.popup_windows.borrow().iter().for_each(|p| {
recurse_elem_including_sub_components_no_borrow_dyn(&p.component, state, vis)
});
component
.menu_item_tree
.borrow()
.iter()
.for_each(|c| recurse_elem_including_sub_components_no_borrow_dyn(c, state, vis));
}
pub fn visit_repeater_model_expression(
elem: &ElementRc,
mut vis: impl FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
) {
let repeated = elem
.borrow_mut()
.repeated
.as_mut()
.map(|r| (std::mem::take(&mut r.model), r.is_conditional_element));
if let Some((mut model, is_cond)) = repeated {
vis(&mut model, None, &|| if is_cond { Type::Bool } else { Type::Model });
elem.borrow_mut().repeated.as_mut().unwrap().model = model;
}
}
pub fn visit_element_expressions_excluding_repeater_model(
elem: &ElementRc,
mut vis: impl FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
) {
visit_element_expressions_excluding_repeater_model_dyn(elem, &mut vis)
}
fn visit_element_expressions_excluding_repeater_model_dyn(
elem: &ElementRc,
vis: &mut dyn FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
) {
fn visit_element_expressions_simple(
elem: &ElementRc,
vis: &mut dyn FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
) {
for (name, expr) in elem.borrow().bindings_including_synthetic() {
vis(&mut expr.borrow_mut(), Some(name.as_str()), &|| {
elem.borrow().lookup_property(name, PropertyLookupMode::InternalName).property_type
});
for twb in &mut expr.borrow_mut().two_way_bindings {
if let expression_tree::TwoWayBinding::ModelData { repeated_element, .. } = twb {
let mut e =
Expression::RepeaterModelReference { element: repeated_element.clone() };
vis(&mut e, None, &|| Type::Invalid);
if let Expression::RepeaterModelReference { element } = e {
*repeated_element = element;
}
}
}
match &mut expr.borrow_mut().animation {
Some(PropertyAnimation::Static(e)) => visit_element_expressions_simple(e, vis),
Some(PropertyAnimation::Transition { animations, state_ref }) => {
vis(state_ref, None, &|| Type::Int32);
for a in animations {
visit_element_expressions_simple(&a.animation, vis)
}
}
None => (),
}
}
}
visit_element_expressions_simple(elem, vis);
for expr in elem.borrow().change_callbacks.values() {
for expr in expr.borrow_mut().iter_mut() {
vis(expr, Some("$change callback$"), &|| Type::Void);
}
}
let mut states = std::mem::take(&mut elem.borrow_mut().states);
for s in &mut states {
if let Some(cond) = s.condition.as_mut() {
vis(cond, None, &|| Type::Bool)
}
for (ne, e, _) in &mut s.property_changes {
vis(e, Some(ne.name()), &|| {
ne.element()
.borrow()
.lookup_property(ne.name(), PropertyLookupMode::InternalName)
.property_type
});
}
}
elem.borrow_mut().states = states;
let mut transitions = std::mem::take(&mut elem.borrow_mut().transitions);
for t in &mut transitions {
for (_, _, a) in &mut t.property_animations {
visit_element_expressions_simple(a, vis);
}
}
elem.borrow_mut().transitions = transitions;
let component = elem.borrow().enclosing_component.upgrade().unwrap();
if Rc::ptr_eq(&component.root_element, elem) {
for e in component.init_code.borrow_mut().iter_mut() {
vis(e, None, &|| Type::Void);
}
}
}
pub fn visit_element_expressions(
elem: &ElementRc,
mut vis: impl FnMut(&mut Expression, Option<&str>, &dyn Fn() -> Type),
) {
visit_repeater_model_expression(elem, &mut vis);
visit_element_expressions_excluding_repeater_model(elem, &mut vis);
}
pub fn visit_named_references_in_expression(
expr: &mut Expression,
vis: &mut impl FnMut(&mut NamedReference),
) {
visit_named_references_in_expression_dyn(expr, vis)
}
fn visit_named_references_in_expression_dyn(
expr: &mut Expression,
vis: &mut dyn FnMut(&mut NamedReference),
) {
expr.visit_mut(|sub| visit_named_references_in_expression_dyn(sub, vis));
match expr {
Expression::PropertyReference(r) => vis(r),
Expression::FunctionCall {
function: Callable::Callback(r) | Callable::Function(r),
..
} => vis(r),
Expression::LayoutCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop),
Expression::GridRepeaterCacheAccess { layout_cache_prop, .. } => vis(layout_cache_prop),
Expression::OrganizeGridLayout(l) => l.visit_named_references(vis),
Expression::ComputeBoxLayoutInfo { layout, .. } => layout.visit_named_references(vis),
Expression::ComputeFlexboxLayoutInfo { layout, .. } => layout.visit_named_references(vis),
Expression::ComputeGridLayoutInfo { layout_organized_data_prop, layout, .. } => {
vis(layout_organized_data_prop);
layout.visit_named_references(vis);
}
Expression::SolveBoxLayout(l, _) => l.visit_named_references(vis),
Expression::SolveFlexboxLayout(l) => l.visit_named_references(vis),
Expression::SolveGridLayout { layout_organized_data_prop, layout, .. } => {
vis(layout_organized_data_prop);
layout.visit_named_references(vis);
}
Expression::RepeaterModelReference { element }
| Expression::RepeaterIndexReference { element } => {
let mut nc =
NamedReference::new(&element.upgrade().unwrap(), SmolStr::new_static("$model"));
vis(&mut nc);
debug_assert!(nc.element().borrow().repeated.is_some());
*element = Rc::downgrade(&nc.element());
}
_ => {}
}
}
pub fn visit_all_named_references_in_element(
elem: &ElementRc,
mut vis: impl FnMut(&mut NamedReference),
) {
visit_all_named_references_in_element_dyn(elem, &mut vis)
}
fn visit_all_named_references_in_element_dyn(
elem: &ElementRc,
mut vis: &mut dyn FnMut(&mut NamedReference),
) {
visit_element_expressions(elem, |expr, _, _| {
visit_named_references_in_expression_dyn(expr, vis)
});
let mut states = std::mem::take(&mut elem.borrow_mut().states);
for s in &mut states {
for (r, _, _) in &mut s.property_changes {
vis(r);
}
}
elem.borrow_mut().states = states;
let mut transitions = std::mem::take(&mut elem.borrow_mut().transitions);
for t in &mut transitions {
for (r, _, _) in &mut t.property_animations {
vis(r)
}
}
elem.borrow_mut().transitions = transitions;
let mut repeated = std::mem::take(&mut elem.borrow_mut().repeated);
if let Some(r) = &mut repeated
&& let Some(lv) = &mut r.is_listview
{
vis(&mut lv.content_y);
if let Some(content_height) = &mut lv.content_height {
vis(content_height);
}
if let Some(content_width) = &mut lv.content_width {
vis(content_width);
}
vis(&mut lv.listview_height);
vis(&mut lv.listview_width);
}
elem.borrow_mut().repeated = repeated;
let mut layout_info_prop = std::mem::take(&mut elem.borrow_mut().layout_info_prop);
layout_info_prop.as_mut().map(|(h, b)| (vis(h), vis(b)));
elem.borrow_mut().layout_info_prop = layout_info_prop;
let mut constrained_v = std::mem::take(&mut elem.borrow_mut().layout_info_v_with_constraint);
if let Some(nr) = constrained_v.as_mut() {
vis(nr);
}
elem.borrow_mut().layout_info_v_with_constraint = constrained_v;
let mut at_own_height = std::mem::take(&mut elem.borrow_mut().layout_info_h_at_own_height);
if let Some(nr) = at_own_height.as_mut() {
vis(nr);
}
elem.borrow_mut().layout_info_h_at_own_height = at_own_height;
let mut debug = std::mem::take(&mut elem.borrow_mut().debug);
for d in debug.iter_mut() {
if let Some(l) = d.layout.as_mut() {
l.visit_named_references(vis)
}
}
elem.borrow_mut().debug = debug;
let mut accessibility_props = std::mem::take(&mut elem.borrow_mut().accessibility_props);
accessibility_props.0.iter_mut().for_each(|(_, x)| vis(x));
elem.borrow_mut().accessibility_props = accessibility_props;
let geometry_props = elem.borrow_mut().geometry_props.take();
if let Some(mut geometry_props) = geometry_props {
vis(&mut geometry_props.x);
vis(&mut geometry_props.y);
vis(&mut geometry_props.width);
vis(&mut geometry_props.height);
elem.borrow_mut().geometry_props = Some(geometry_props);
}
let z_order = elem.borrow_mut().z_order.take();
if let Some(mut zo) = z_order {
if let ZOrder::Dynamic(ref mut nr) | ZOrder::PerInstance(ref mut nr) = zo {
vis(nr);
}
elem.borrow_mut().z_order = Some(zo);
}
for (_, expr) in elem.borrow().real_bindings() {
for twb in &mut expr.borrow_mut().two_way_bindings {
if let expression_tree::TwoWayBinding::Property { property, .. } = twb {
vis(property);
}
}
}
let mut property_declarations = std::mem::take(&mut elem.borrow_mut().property_declarations);
for pd in property_declarations.values_mut() {
pd.is_alias.as_mut().map(&mut vis);
}
elem.borrow_mut().property_declarations = property_declarations;
let grid_layout_cell = elem.borrow_mut().grid_layout_cell.take();
if let Some(grid_layout_cell) = grid_layout_cell {
grid_layout_cell.borrow_mut().visit_named_references(&mut vis);
elem.borrow_mut().grid_layout_cell = Some(grid_layout_cell);
}
}
pub fn visit_all_named_references(
component: &Component,
vis: &mut impl FnMut(&mut NamedReference),
) {
visit_all_named_references_dyn(component, vis)
}
fn visit_all_named_references_dyn(component: &Component, vis: &mut dyn FnMut(&mut NamedReference)) {
recurse_elem_including_sub_components_no_borrow_dyn(
component,
&Weak::new(),
&mut |elem, parent_compo| {
visit_all_named_references_in_element_dyn(elem, vis);
let compo = elem.borrow().enclosing_component.clone();
if !Weak::ptr_eq(parent_compo, &compo) {
let compo = compo.upgrade().unwrap();
compo.root_constraints.borrow_mut().visit_named_references(vis);
compo.popup_windows.borrow_mut().iter_mut().for_each(|p| {
vis(&mut p.x);
vis(&mut p.y);
if let Some(is_open) = &mut p.is_open {
vis(is_open);
}
});
compo.timers.borrow_mut().iter_mut().for_each(|t| {
vis(&mut t.interval);
vis(&mut t.triggered);
vis(&mut t.running);
});
for o in compo.optimized_elements.borrow().iter() {
visit_element_expressions(o, |expr, _, _| {
visit_named_references_in_expression_dyn(expr, vis)
});
}
}
compo
},
);
}
pub fn visit_all_expressions(
component: &Component,
mut vis: impl FnMut(&mut Expression, &dyn Fn() -> Type),
) {
visit_all_expressions_dyn(component, &mut vis)
}
fn visit_all_expressions_dyn(
component: &Component,
vis: &mut dyn FnMut(&mut Expression, &dyn Fn() -> Type),
) {
recurse_elem_including_sub_components_dyn(component, &Weak::new(), &mut |elem, parent_compo| {
visit_element_expressions(elem, |expr, _, ty| vis(expr, ty));
let compo = elem.borrow().enclosing_component.clone();
if !Weak::ptr_eq(parent_compo, &compo) {
let compo = compo.upgrade().unwrap();
for o in compo.optimized_elements.borrow().iter() {
visit_element_expressions(o, |expr, _, ty| vis(expr, ty));
}
}
compo
})
}
#[derive(Debug, Clone)]
pub struct State {
pub id: SmolStr,
pub condition: Option<Expression>,
pub property_changes: Vec<(NamedReference, Expression, syntax_nodes::StatePropertyChange)>,
pub selection: Option<ConditionLocation>,
}
#[derive(Debug, Clone)]
pub struct Transition {
pub direction: TransitionDirection,
pub state_id: SmolStr,
pub property_animations: Vec<(NamedReference, SourceLocation, ElementRc)>,
pub node: syntax_nodes::Transition,
}
impl Transition {
fn from_node(
trs: syntax_nodes::Transition,
r: &ElementRc,
tr: &TypeRegister,
diag: &mut BuildDiagnostics,
) -> Transition {
if let Some(star) = trs.child_token(SyntaxKind::Star) {
diag.push_error("catch-all not yet implemented".into(), &star);
};
let direction_text = trs
.first_child_or_token()
.and_then(|t| t.as_token().map(|tok| tok.text().to_string()))
.unwrap_or_default();
Transition {
direction: match direction_text.as_str() {
"in" => TransitionDirection::In,
"out" => TransitionDirection::Out,
"in-out" => TransitionDirection::InOut,
"in_out" => TransitionDirection::InOut,
_ => {
unreachable!("Unknown transition direction: '{}'", direction_text);
}
},
state_id: trs
.DeclaredIdentifier()
.and_then(|x| parser::identifier_text(&x))
.unwrap_or_default(),
property_animations: trs
.PropertyAnimation()
.flat_map(|pa| pa.QualifiedName().map(move |qn| (pa.clone(), qn)))
.filter_map(|(pa, qn)| {
lookup_property_from_qualified_name_for_state(qn.clone(), r, diag).and_then(
|(ne, prop_type)| {
animation_element_from_node(&pa, &qn, prop_type, diag, tr)
.map(|anim_element| (ne, qn.to_source_location(), anim_element))
},
)
})
.collect(),
node: trs.clone(),
}
}
}
#[derive(Clone, Debug, derive_more::Deref)]
pub struct ExportedName {
#[deref]
pub name: SmolStr, pub name_ident: SyntaxNode,
}
impl ExportedName {
pub fn original_name(&self) -> SmolStr {
self.name_ident
.child_token(parser::SyntaxKind::Identifier)
.map(|n| n.to_smolstr())
.unwrap_or_else(|| self.name.clone())
}
pub fn from_export_specifier(
export_specifier: &syntax_nodes::ExportSpecifier,
) -> (SmolStr, ExportedName) {
let internal_name =
parser::identifier_text(&export_specifier.ExportIdentifier()).unwrap_or_default();
let (name, name_ident): (SmolStr, SyntaxNode) = export_specifier
.ExportName()
.and_then(|ident| {
parser::identifier_text(&ident).map(|text| (text, ident.clone().into()))
})
.unwrap_or_else(|| (internal_name.clone(), export_specifier.ExportIdentifier().into()));
(internal_name, ExportedName { name, name_ident })
}
}
#[derive(Default, Debug, derive_more::Deref)]
pub struct Exports {
#[deref]
components_or_types: Vec<(ExportedName, Either<Rc<Component>, Type>)>,
}
impl Exports {
pub fn from_node(
doc: &syntax_nodes::Document,
inner_components: &[Rc<Component>],
type_registry: &TypeRegister,
diag: &mut BuildDiagnostics,
) -> Self {
let resolve_export_to_inner_component_or_import =
|internal_name: &str, internal_name_node: &dyn Spanned, diag: &mut BuildDiagnostics| {
if let Ok(ElementType::Component(c)) = type_registry.lookup_element(internal_name) {
Some(Either::Left(c))
} else if let ty @ Type::Struct { .. } | ty @ Type::Enumeration(_) =
type_registry.lookup(internal_name)
{
Some(Either::Right(ty))
} else if type_registry.lookup_element(internal_name).is_ok()
|| type_registry.lookup(internal_name) != Type::Invalid
{
diag.push_error(
format!("Cannot export '{internal_name}' because it is not a component",),
internal_name_node,
);
None
} else {
diag.push_error(format!("'{internal_name}' not found",), internal_name_node);
None
}
};
let mut exports_with_duplicates: Vec<(ExportedName, Either<Rc<Component>, Type>)> =
Vec::new();
exports_with_duplicates.extend(
doc.ExportsList()
.filter(|exports| exports.ExportModule().is_none())
.flat_map(|exports| exports.ExportSpecifier())
.filter_map(|export_specifier| {
let (internal_name, exported_name) =
ExportedName::from_export_specifier(&export_specifier);
Some((
exported_name,
resolve_export_to_inner_component_or_import(
&internal_name,
&export_specifier.ExportIdentifier(),
diag,
)?,
))
}),
);
exports_with_duplicates.extend(
doc.ExportsList().flat_map(|exports| exports.Component()).filter_map(|component| {
let name_ident: SyntaxNode = component.DeclaredIdentifier().into();
let name =
parser::identifier_text(&component.DeclaredIdentifier()).unwrap_or_else(|| {
debug_assert!(diag.has_errors());
SmolStr::default()
});
let compo_or_type =
resolve_export_to_inner_component_or_import(&name, &name_ident, diag)?;
Some((ExportedName { name, name_ident }, compo_or_type))
}),
);
exports_with_duplicates.extend(
doc.ExportsList()
.flat_map(|exports| {
exports
.StructDeclaration()
.map(|st| st.DeclaredIdentifier())
.chain(exports.EnumDeclaration().map(|en| en.DeclaredIdentifier()))
})
.filter_map(|name_ident| {
let name = parser::identifier_text(&name_ident).unwrap_or_else(|| {
debug_assert!(diag.has_errors());
SmolStr::default()
});
let name_ident = name_ident.into();
let compo_or_type =
resolve_export_to_inner_component_or_import(&name, &name_ident, diag)?;
Some((ExportedName { name, name_ident }, compo_or_type))
}),
);
exports_with_duplicates.sort_by(|(a, _), (b, _)| a.name.cmp(&b.name));
let mut sorted_deduped_exports = Vec::with_capacity(exports_with_duplicates.len());
let mut it = exports_with_duplicates.into_iter().peekable();
while let Some((exported_name, compo_or_type)) = it.next() {
let mut warning_issued_on_first_occurrence = false;
while it.peek().map(|(name, _)| &name.name) == Some(&exported_name.name) {
let message = format!("Duplicated export '{}'", exported_name.name);
if !warning_issued_on_first_occurrence {
diag.push_error(message.clone(), &exported_name.name_ident);
warning_issued_on_first_occurrence = true;
}
let duplicate_loc = it.next().unwrap().0.name_ident;
diag.push_error(message.clone(), &duplicate_loc);
}
sorted_deduped_exports.push((exported_name, compo_or_type));
}
if let Some(last_compo) = inner_components.last() {
let name = last_compo.id.clone();
if last_compo.is_global() {
if sorted_deduped_exports.is_empty() {
diag.push_warning("Global singleton is implicitly marked for export. This is deprecated and it should be explicitly exported".into(), &last_compo.node.as_ref().map(|n| n.to_source_location()));
sorted_deduped_exports.push((
ExportedName { name, name_ident: doc.clone().into() },
Either::Left(last_compo.clone()),
))
}
} else if !sorted_deduped_exports
.iter()
.any(|e| e.1.as_ref().left().is_some_and(|c| !c.is_global()))
{
diag.push_warning("Component is implicitly marked for export. This is deprecated and it should be explicitly exported".into(), &last_compo.node.as_ref().map(|n| n.to_source_location()));
let insert_pos = sorted_deduped_exports
.partition_point(|(existing_export, _)| existing_export.name <= name);
sorted_deduped_exports.insert(
insert_pos,
(
ExportedName { name, name_ident: doc.clone().into() },
Either::Left(last_compo.clone()),
),
)
}
}
Self { components_or_types: sorted_deduped_exports }
}
pub fn add_reexports(
&mut self,
other_exports: impl IntoIterator<Item = (ExportedName, Either<Rc<Component>, Type>)>,
diag: &mut BuildDiagnostics,
) {
for export in other_exports {
match self.components_or_types.binary_search_by(|entry| entry.0.cmp(&export.0)) {
Ok(_) => {
diag.push_warning(
format!(
"'{}' is already exported in this file; it will not be re-exported",
*export.0
),
&export.0.name_ident,
);
}
Err(insert_pos) => {
self.components_or_types.insert(insert_pos, export);
}
}
}
}
pub fn find(&self, name: &str) -> Option<Either<Rc<Component>, Type>> {
self.components_or_types
.binary_search_by(|(exported_name, _)| exported_name.as_str().cmp(name))
.ok()
.map(|index| self.components_or_types[index].1.clone())
}
pub fn named_type_aliases(&self) -> Vec<(SmolStr, SmolStr)> {
self.iter()
.filter_map(|(exported, item)| match item {
Either::Left(component) if !component.is_global() => {
Some((component.id.clone(), exported.name.clone()))
}
Either::Right(ty) => match ty {
Type::Struct(s) if s.node().is_some() => match &s.name {
StructName::User { name, .. } => {
Some((name.clone(), exported.name.clone()))
}
_ => None,
},
Type::Enumeration(en) => Some((en.name.clone(), exported.name.clone())),
_ => None,
},
_ => None,
})
.filter(|(original, alias)| original != alias)
.collect()
}
pub fn retain(
&mut self,
func: impl FnMut(&mut (ExportedName, Either<Rc<Component>, Type>)) -> bool,
) {
self.components_or_types.retain_mut(func)
}
pub(crate) fn snapshot(&self, snapshotter: &mut crate::typeloader::Snapshotter) -> Self {
let components_or_types = self
.components_or_types
.iter()
.map(|(en, either)| {
let en = en.clone();
let either = match either {
itertools::Either::Left(l) => itertools::Either::Left({
Weak::upgrade(&snapshotter.use_component(l))
.expect("Component should cleanly upgrade here")
}),
itertools::Either::Right(r) => itertools::Either::Right(r.clone()),
};
(en, either)
})
.collect();
Self { components_or_types }
}
}
impl std::iter::IntoIterator for Exports {
type Item = (ExportedName, Either<Rc<Component>, Type>);
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.components_or_types.into_iter()
}
}
fn forward_layout_info_with_constraint(new_root: &ElementRc, old_root: &ElementRc) {
let width = Expression::FunctionParameterReference { index: 0, ty: Type::LogicalLength };
let body = if let Some(nr) = old_root.borrow().inherited_layout_info_v_with_constraint() {
Some(Expression::FunctionCall {
function: Callable::Function(NamedReference::new(old_root, nr.name().clone())),
arguments: vec![width],
source_location: None,
})
} else if old_root.borrow().is_builtin_height_for_width() {
crate::layout::implicit_layout_info_call(
old_root,
Orientation::Vertical,
crate::layout::BuiltinFilter::All,
Some(width),
)
} else {
None
};
if let Some(body) = body {
crate::passes::lower_layout::synthesize_layoutinfo_v_with_constraint_on(
new_root,
old_root.borrow().to_source_location(),
body,
);
}
}
pub fn inject_element_as_repeated_element(repeated_element: &ElementRc, new_root: ElementRc) {
let component = repeated_element.borrow().base_type.as_component().clone();
debug_assert_eq!(Rc::strong_count(&component), 2);
let old_root = &component.root_element;
adjust_geometry_for_injected_parent(&new_root, old_root);
let mut elements_with_enclosing_component_reference = Vec::new();
recurse_elem(old_root, &(), &mut |element: &ElementRc, _| {
if let Some(enclosing_component) = element.borrow().enclosing_component.upgrade()
&& Rc::ptr_eq(&enclosing_component, &component)
{
elements_with_enclosing_component_reference.push(element.clone());
}
});
elements_with_enclosing_component_reference
.extend_from_slice(component.optimized_elements.borrow().as_slice());
elements_with_enclosing_component_reference.push(new_root.clone());
new_root.borrow_mut().child_of_layout =
std::mem::replace(&mut old_root.borrow_mut().child_of_layout, false);
new_root.borrow_mut().grid_layout_cell = old_root.borrow_mut().grid_layout_cell.take();
if old_root.borrow().child_of_flexbox {
new_root.borrow_mut().child_of_flexbox = true;
}
new_root.borrow_mut().parent_box_layout_orientation =
old_root.borrow().parent_box_layout_orientation;
for prop in ["layout-order", "cross-axis-self-alignment"].iter() {
if old_root.borrow().binding(prop).is_some() {
new_root.borrow_mut().set_binding(
SmolStr::new_static(prop),
BindingExpression::new_two_way(
NamedReference::new(old_root, SmolStr::new_static(prop)).into(),
),
);
}
}
let layout_info_prop = {
let old = old_root.borrow();
old.effective_layout_info_prop(Orientation::Horizontal)
.cloned()
.zip(old.effective_layout_info_prop(Orientation::Vertical).cloned())
}
.or_else(|| {
let li_v = crate::layout::create_new_prop(
&new_root,
SmolStr::new_static("layoutinfo-v"),
crate::typeregister::layout_info_type().into(),
);
let li_h = crate::layout::create_new_prop(
&new_root,
SmolStr::new_static("layoutinfo-h"),
crate::typeregister::layout_info_type().into(),
);
let expr_h = crate::layout::implicit_layout_info_call(
old_root,
Orientation::Horizontal,
crate::layout::BuiltinFilter::All,
None,
)
.unwrap();
let expr_v = crate::layout::implicit_layout_info_call(
old_root,
Orientation::Vertical,
crate::layout::BuiltinFilter::All,
None,
)
.unwrap();
let expr_v =
BindingExpression::new_with_span(expr_v, old_root.borrow().to_source_location());
li_v.element().borrow_mut().set_binding(li_v.name().clone(), expr_v);
let expr_h =
BindingExpression::new_with_span(expr_h, old_root.borrow().to_source_location());
li_h.element().borrow_mut().set_binding(li_h.name().clone(), expr_h);
Some((li_h.clone(), li_v.clone()))
});
new_root.borrow_mut().layout_info_prop = layout_info_prop;
forward_layout_info_with_constraint(&new_root, old_root);
drop(std::mem::take(&mut repeated_element.borrow_mut().base_type));
debug_assert_eq!(Rc::strong_count(&component), 1);
let mut component = Rc::try_unwrap(component).expect("internal compiler error: more than one strong reference left to repeated component when lowering shadow properties");
let old_root = std::mem::replace(&mut component.root_element, new_root.clone());
new_root.borrow_mut().children.push(old_root);
let component = Rc::new(component);
repeated_element.borrow_mut().base_type = ElementType::Component(component.clone());
for elem in elements_with_enclosing_component_reference {
elem.borrow_mut().enclosing_component = Rc::downgrade(&component);
}
}
pub fn adjust_geometry_for_injected_parent(injected_parent: &ElementRc, old_elem: &ElementRc) {
let mut injected_parent_mut = injected_parent.borrow_mut();
injected_parent_mut.set_binding(
"z".into(),
BindingExpression::new_two_way(
NamedReference::new(old_elem, SmolStr::new_static("z")).into(),
),
);
injected_parent_mut.property_declarations.insert(
"dummy".into(),
PropertyDeclaration { property_type: Type::LogicalLength, ..Default::default() },
);
let mut old_elem_mut = old_elem.borrow_mut();
injected_parent_mut.default_fill_parent = std::mem::take(&mut old_elem_mut.default_fill_parent);
injected_parent_mut.geometry_props.clone_from(&old_elem_mut.geometry_props);
injected_parent_mut.z_order = old_elem_mut.z_order.take();
drop(injected_parent_mut);
old_elem_mut.geometry_props.as_mut().unwrap().x =
NamedReference::new(injected_parent, SmolStr::new_static("dummy"));
old_elem_mut.geometry_props.as_mut().unwrap().y =
NamedReference::new(injected_parent, SmolStr::new_static("dummy"));
}