From 2262ca867b31fd3dbcd56031e0da2a765ce9a329 Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Mon, 13 Apr 2026 01:10:06 +0200 Subject: [PATCH 01/16] Add `push()`, `remove()` and `insert()` methods Fully works on Rust. Add basic implementation in interpreter (not final). All three methods are not implemented yet in C++. Add tests for the rust, c++ and js interpreter drivers. --- internal/compiler/builtin_macros.rs | 113 ++++++++++++++++++ internal/compiler/expression_tree.rs | 15 +++ internal/compiler/generator/rust.rs | 58 ++++++++- .../llr/optim_passes/inline_expressions.rs | 6 +- internal/compiler/lookup.rs | 12 +- internal/core/model.rs | 23 ++++ internal/interpreter/eval.rs | 59 +++++++++ tests/cases/models/array.slint | 45 +++++++ tools/lsp/preview/eval.rs | 63 ++++++++++ 9 files changed, 388 insertions(+), 6 deletions(-) diff --git a/internal/compiler/builtin_macros.rs b/internal/compiler/builtin_macros.rs index 7d08bc0a41c..5219374c7fc 100644 --- a/internal/compiler/builtin_macros.rs +++ b/internal/compiler/builtin_macros.rs @@ -88,6 +88,9 @@ pub fn lower_macro( BuiltinMacroFunction::Rgb => rgb_macro(n, sub_expr.collect(), diag), BuiltinMacroFunction::Hsv => hsv_macro(n, sub_expr.collect(), diag), BuiltinMacroFunction::Oklch => oklch_macro(n, sub_expr.collect(), diag), + BuiltinMacroFunction::ArrayPush => array_push_macro(n, sub_expr.collect(), diag), + BuiltinMacroFunction::ArrayRemove => array_remove_macro(n, sub_expr.collect(), diag), + BuiltinMacroFunction::ArrayInsert => array_insert_macro(n, sub_expr.collect(), diag), } } @@ -379,6 +382,116 @@ fn debug_macro( } } +fn array_push_macro( + node: &dyn Spanned, + mut args: Vec<(Expression, Option)>, + diag: &mut BuildDiagnostics, +) -> Expression { + if args.len() != 2 { + diag.push_error( + format!( + "This method needs 1 argument but {} were provided", + if args.len() == 1 { 0 } else { args.len() - 1 } // Avoid counting the model argument. + ), + node + ); + return Expression::Invalid; + } + + let element_type = match args[0].0.ty() { + Type::Array(t) => (*t).clone(), + _ => { + diag.push_error( + format!("push() was called on a non-array: {:?}", args[0].0), + node + ); + return Expression::Invalid; + } + }; + + let (model_expr, _) = args.remove(0); + let (value_expr, value_node) = args.remove(0); + let value = value_expr.maybe_convert_to(element_type, &value_node, diag); + Expression::FunctionCall { + function: Callable::Builtin(BuiltinFunction::ArrayPush), + arguments: vec![model_expr, value], + source_location: Some(node.to_source_location()), + } +} + +fn array_remove_macro( + node: &dyn Spanned, + mut args: Vec<(Expression, Option)>, + diag: &mut BuildDiagnostics, +) -> Expression { + if args.len() != 2 { + diag.push_error( + format!( + "This method needs 1 argument but {} were provided", + if args.len() == 1 { 0 } else { args.len() - 1 } // Avoid counting the model argument. + ), + node + ); + return Expression::Invalid; + } + + if !matches!(args[0].0.ty(), Type::Array(_)) { + diag.push_error( + format!("remove() was called on a non-array: {:?}", args[0].0), + node + ); + return Expression::Invalid; + } + + let (model_expr, _) = args.remove(0); + let (index_expr, index_node) = args.remove(0); + let index = index_expr.maybe_convert_to(Type::Int32, &index_node, diag); + Expression::FunctionCall { + function: Callable::Builtin(BuiltinFunction::ArrayRemove), + arguments: vec![model_expr, index], + source_location: Some(node.to_source_location()), + } +} + +fn array_insert_macro( + node: &dyn Spanned, + mut args: Vec<(Expression, Option)>, + diag: &mut BuildDiagnostics, +) -> Expression { + if args.len() != 3 { + diag.push_error( + format!( + "This method needs 2 argument but {} were provided", + if args.len() == 1 { 0 } else { args.len() - 1 } // Avoid counting the model argument. + ), + node + ); + return Expression::Invalid; + } + + let element_type = match args[0].0.ty() { + Type::Array(t) => (*t).clone(), + _ => { + diag.push_error( + format!("push() was called on a non-array: {:?}", args[0].0), + node + ); + return Expression::Invalid; + } + }; + + let (model_expr, _) = args.remove(0); + let (index_expr, index_node) = args.remove(0); + let (value_expr, value_node) = args.remove(0); + let index = index_expr.maybe_convert_to(Type::Int32, &index_node, diag); + let value = value_expr.maybe_convert_to(element_type, &value_node, diag); + Expression::FunctionCall { + function: Callable::Builtin(BuiltinFunction::ArrayInsert), + arguments: vec![model_expr, index, value], + source_location: Some(node.to_source_location()), + } +} + fn to_debug_string( expr: Expression, node: &dyn Spanned, diff --git a/internal/compiler/expression_tree.rs b/internal/compiler/expression_tree.rs index 9c14a69e6a7..d172c69e39e 100644 --- a/internal/compiler/expression_tree.rs +++ b/internal/compiler/expression_tree.rs @@ -84,6 +84,9 @@ pub enum BuiltinFunction { ColorWithAlpha, ImageSize, ArrayLength, + ArrayPush, + ArrayRemove, + ArrayInsert, Rgb, Hsv, Oklch, @@ -149,6 +152,9 @@ pub enum BuiltinMacroFunction { Oklch, /// transform `debug(a, b, c)` into debug `a + " " + b + " " + c` Debug, + ArrayPush, + ArrayRemove, + ArrayInsert, } macro_rules! declare_builtin_function_types { @@ -261,6 +267,9 @@ declare_builtin_function_types!( name: crate::langtype::BuiltinPrivateStruct::Size.into(), })), ArrayLength: (Type::Model) -> Type::Int32, + ArrayPush: (Type::Model) -> Type::Void, + ArrayRemove: (Type::Model, Type::Int32) -> Type::Void, + ArrayInsert: (Type::Model, Type::Int32) -> Type::Void, Rgb: (Type::Int32, Type::Int32, Type::Int32, Type::Float32) -> Type::Color, Hsv: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color, Oklch: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color, @@ -384,6 +393,9 @@ impl BuiltinFunction { #[cfg(target_arch = "wasm32")] BuiltinFunction::ImageSize => false, BuiltinFunction::ArrayLength => true, + BuiltinFunction::ArrayPush + | BuiltinFunction::ArrayRemove + | BuiltinFunction::ArrayInsert => false, BuiltinFunction::Rgb => true, BuiltinFunction::Hsv => true, BuiltinFunction::Oklch => true, @@ -468,6 +480,9 @@ impl BuiltinFunction { | BuiltinFunction::ColorWithAlpha => true, BuiltinFunction::ImageSize => true, BuiltinFunction::ArrayLength => true, + BuiltinFunction::ArrayPush + | BuiltinFunction::ArrayRemove + | BuiltinFunction::ArrayInsert => false, BuiltinFunction::Rgb => true, BuiltinFunction::Hsv => true, BuiltinFunction::Oklch => true, diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index a3d44473eae..5e25cabaaa8 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -3753,7 +3753,63 @@ fn compile_builtin_function_call( x.row_count() as i32 }}) } - + BuiltinFunction::ArrayPush => { + let inner_ty = match arguments[0].ty(ctx) { + Type::Array(elem) => rust_primitive_type(&elem).unwrap(), + _ => panic!("internal error: ArrayPush first argument is not an array: {:?}", arguments[0]), + }; + let model = a.next().unwrap(); + let value = a.next().unwrap(); + quote!({ + let model = &#model; + let value = #value; + model + .as_any() + .downcast_ref::>() + .unwrap() + .push(value); + }) + } + BuiltinFunction::ArrayRemove => { + let inner_ty = match arguments[0].ty(ctx) { + Type::Array(elem) => rust_primitive_type(&elem).unwrap(), + _ => panic!("internal error: ArrayRemove first argument is not an array: {:?}", arguments[0]), + }; + let model = a.next().unwrap(); + let index = a.next().unwrap(); + quote!({ + let model = &#model; + let index = #index; + + // Check for index out of bound errors to avoid runtime crash ? + model + .as_any() + .downcast_ref::>() + .unwrap() + .remove(index as usize); + }) + } + BuiltinFunction::ArrayInsert => { + let inner_ty = match arguments[0].ty(ctx) { + Type::Array(elem) => rust_primitive_type(&elem).unwrap(), + _ => panic!("internal error: ArrayInsert first argument is not an array: {:?}", arguments[0]), + }; + let model = a.next().unwrap(); + let index = a.next().unwrap(); + let value = a.next().unwrap(); + quote!({ + let model = &#model; + let index = #index; + let value = #value; + + // Check for index out of bound errors to avoid runtime crash ? + model + .as_any() + .downcast_ref::>() + .unwrap() + .insert(index as usize, value); + }) + } BuiltinFunction::Rgb => { let (r, g, b, a) = (a.next().unwrap(), a.next().unwrap(), a.next().unwrap(), a.next().unwrap()); diff --git a/internal/compiler/llr/optim_passes/inline_expressions.rs b/internal/compiler/llr/optim_passes/inline_expressions.rs index bd71a0a779a..91740e57a46 100644 --- a/internal/compiler/llr/optim_passes/inline_expressions.rs +++ b/internal/compiler/llr/optim_passes/inline_expressions.rs @@ -135,7 +135,11 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize { BuiltinFunction::ColorMix => 50, BuiltinFunction::ColorWithAlpha => 50, BuiltinFunction::ImageSize => 50, - BuiltinFunction::ArrayLength => 50, + // TODO I have currently no idea is the weight of the new functions is correct. + BuiltinFunction::ArrayLength + | BuiltinFunction::ArrayPush + | BuiltinFunction::ArrayRemove + | BuiltinFunction::ArrayInsert => 50, BuiltinFunction::Rgb => 50, BuiltinFunction::Hsv => 50, BuiltinFunction::Oklch => 50, diff --git a/internal/compiler/lookup.rs b/internal/compiler/lookup.rs index 74910abff2c..a1fe3223c46 100644 --- a/internal/compiler/lookup.rs +++ b/internal/compiler/lookup.rs @@ -1120,16 +1120,20 @@ impl LookupObject for ArrayExpression<'_> { ctx: &LookupCtx, f: &mut impl FnMut(&SmolStr, LookupResult) -> Option, ) -> Option { - let member_function = |f: BuiltinFunction| { + let function_call = |f: BuiltinFunction| { LookupResult::from(Expression::FunctionCall { function: Callable::Builtin(f), source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()), arguments: vec![self.0.clone()], }) }; - None.or_else(|| { - f(&SmolStr::new_static("length"), member_function(BuiltinFunction::ArrayLength)) - }) + let mut member_macro = member_macro_generator(self.0.clone(), ctx.current_token.clone()); + + let mut f = |s, res| f(&SmolStr::new_static(s), res); + None.or_else(|| f("length", function_call(BuiltinFunction::ArrayLength))) + .or_else(|| f("push", member_macro(BuiltinMacroFunction::ArrayPush))) + .or_else(|| f("remove", member_macro(BuiltinMacroFunction::ArrayRemove))) + .or_else(|| f("insert", member_macro(BuiltinMacroFunction::ArrayInsert))) } } diff --git a/internal/core/model.rs b/internal/core/model.rs index b85c846d0fe..f91a4c4fa4e 100644 --- a/internal/core/model.rs +++ b/internal/core/model.rs @@ -144,6 +144,29 @@ pub trait Model { ); } + /// Add a new row to the model. + /// + /// This function should be called on a model that supports multiple rows of data, otherwise + /// it might panic. + fn push_row(&self, data: Self::Data) { + self.set_row_data(self.row_count(), data); + } + + /// Insert a new row at the specified index and move the next rows by 1 step to the right. + /// + /// This function should be called on a model that supports multiple rows of data, otherwise + /// it might panic. + fn insert_row(&self, row: usize, data: Self::Data) { + if row < self.row_count() { + for i in (row..self.row_count()).rev() { + if let Some(d) = self.row_data(i) { + self.set_row_data(i + 1, d); + } + } + } + self.set_row_data(row, data); + } + /// The implementation should return a reference to its [`ModelNotify`] field. /// /// You can return `&()` if you your `Model` is constant and does not have a ModelNotify field. diff --git a/internal/interpreter/eval.rs b/internal/interpreter/eval.rs index f8e600ac56e..2c0a46ccde6 100644 --- a/internal/interpreter/eval.rs +++ b/internal/interpreter/eval.rs @@ -1392,6 +1392,65 @@ fn call_builtin_function( } } } + BuiltinFunction::ArrayPush => { + if arguments.len() != 2 { + panic!("internal error: incorrect argument count to ArrayPush") + } + + let model = match eval_expression(&arguments[0], local_context) { + Value::Model(m) => m, + _ => panic!("First argument not an array: {:?}", arguments[0]), + }; + let value = eval_expression(&arguments[1], local_context); + + model.push_row(value); + + Value::Void + } + BuiltinFunction::ArrayRemove => { + if arguments.len() != 2 { + panic!("internal error: incorrect argument count to ArrayRemove") + } + + let model = match eval_expression(&arguments[0], local_context) { + Value::Model(m) => m, + _ => panic!("First argument not an array: {:?}", arguments[0]), + }; + let index = match eval_expression(&arguments[1], local_context) { + Value::Number(i) => i as usize, + _ => panic!("Second argument not an integer: {:?}", arguments[0]), + }; + + model.remove_row(index); + + model.iter().for_each(|item| println!("{item:?}")); + + model.as_any() + .downcast_ref::>() + .expect("Couldn't get the array from the model in ArrayRemove"); + + Value::Void + } + + BuiltinFunction::ArrayInsert => { + if arguments.len() != 3 { + panic!("internal error: incorrect argument count to ArrayInsert") + } + + let model = match eval_expression(&arguments[0], local_context) { + Value::Model(m) => m, + _ => panic!("First argument not an array: {:?}", arguments[0]), + }; + let index = match eval_expression(&arguments[1], local_context) { + Value::Number(i) => i as usize, + _ => panic!("Second argument not an integer: {:?}", arguments[0]), + }; + let value = eval_expression(&arguments[2], local_context); + + model.insert_row(index, value); + + Value::Void + } BuiltinFunction::Rgb => { let r: i32 = eval_expression(&arguments[0], local_context).try_into().unwrap(); let g: i32 = eval_expression(&arguments[1], local_context).try_into().unwrap(); diff --git a/tests/cases/models/array.slint b/tests/cases/models/array.slint index 55ce716f740..b5b760904cb 100644 --- a/tests/cases/models/array.slint +++ b/tests/cases/models/array.slint @@ -20,6 +20,10 @@ export component TestCase { out property decimal_check: ints[-0.5] == 1 && ints[2.9] == 3 && ints[-1.3] == 0; out property hello_world: [{t: "hello"}, {t: "world"}][1].t; + public function push_one(val: int) { ints.push(val) } + public function remove_one(index: int) { ints.remove(index) } + public function insert_one(index: int, value: int) { ints.insert(index, value) } + for xxx in (third_int == 0) ? ints : [] : Rectangle {} } @@ -51,6 +55,19 @@ assert_eq(instance.get_ninth_int(), 9); assert_eq(instance.get_hello_world(), "world"); + +model->set_vector(std::vector{1, 2, 3}); +instance.invoke_push_one(42); +assert_eq(model->row_count(), 4); +assert_eq(model->row_data(3).unwrap(), 42); + +instance.invoke_remove_one(2); +assert_eq(model->row_count(), 3); +assert_eq(model->row_data(2).unwrap(), 42); + +instance.invoke_insert_one(1, 10); +assert_eq(model->row_count(), 4); +assert_eq(model->row_data(1).unwrap(), 10); ``` @@ -81,6 +98,19 @@ assert_eq!(instance.get_ninth_int(), 9); assert_eq!(instance.get_hello_world(), slint::SharedString::from("world")); + +model.set_vec([1, 2, 3]); +instance.invoke_push_one(42); +assert_eq!(model.row_count(), 4); +assert_eq!(model.row_data(3).unwrap(), 42); + +instance.invoke_remove_one(2); +assert_eq!(model.row_count(), 3); +assert_eq!(model.row_data(2).unwrap(), 42); + +instance.invoke_insert_one(1, 10); +assert_eq!(model.row_count(), 4); +assert_eq!(model.row_data(1).unwrap(), 10); ``` ```js @@ -108,5 +138,20 @@ model.push(9); assert.equal(instance.ninth_int, 9); assert.equal(instance.hello_world, "world"); + +model = new slintlib.ArrayModel([1, 2, 3]); +instance.ints = model; + +instance.push_one(42); +assert.equal(model.rowCount(), 4); +assert.equal(model.rowData(3), 42); + +//instance.remove_one(2); +//assert.equal(model.rowCount(), 3); +//assert.equal(model.rowData(2), 42); + +instance.insert_one(1, 10); +assert.equal(model.rowCount(), 5); +assert.equal(model.rowData(1), 10); ``` */ diff --git a/tools/lsp/preview/eval.rs b/tools/lsp/preview/eval.rs index 02864f0ec83..0b230299d88 100644 --- a/tools/lsp/preview/eval.rs +++ b/tools/lsp/preview/eval.rs @@ -621,6 +621,69 @@ fn handle_builtin_function( _ => Value::Void, } } + BuiltinFunction::ArrayPush => { + if arguments.len() != 2 { + panic!("internal error: incorrect argument count to ArrayPush") + } + + let model = match eval_expression(&arguments[0], local_context, None) { + Value::Model(m) => m, + _ => panic!("First argument not an array: {:?}", arguments[0]), + }; + let value = eval_expression(&arguments[1], local_context, None); + + + model.as_any() + .downcast_ref::>() + .expect("ArrayPush only works on mutable arrays") + .push(value); + + Value::Void + } + BuiltinFunction::ArrayRemove => { + if arguments.len() != 2 { + panic!("internal error: incorrect argument count to ArrayRemove") + } + + let model = match eval_expression(&arguments[0], local_context, None) { + Value::Model(m) => m, + _ => panic!("First argument not an array: {:?}", arguments[0]), + }; + + let index = match eval_expression(&arguments[1], local_context, None) { + Value::Number(i) => i as usize, + _ => panic!("Second argument not an integer: {:?}", arguments[0]), + }; + + model.as_any() + .downcast_ref::>() + .expect("ArrayRemove only works on mutable arrays") + .remove(index); + + Value::Void + } + BuiltinFunction::ArrayRemove => { + if arguments.len() != 3 { + panic!("internal error: incorrect argument count to ArrayRemove") + } + + let model = match eval_expression(&arguments[0], local_context, None) { + Value::Model(m) => m, + _ => panic!("First argument not an array: {:?}", arguments[0]), + }; + let index = match eval_expression(&arguments[1], local_context, None) { + Value::Number(i) => i as usize, + _ => panic!("Second argument not an integer: {:?}", arguments[0]), + }; + let value= eval_expression(&arguments[2], local_context, None); + + model.as_any() + .downcast_ref::>() + .expect("ArrayRemove only works on mutable arrays") + .insert(index, value); + + Value::Void + } BuiltinFunction::Rgb => { let r: i32 = eval_expression(&arguments[0], local_context, None).try_into().unwrap_or_default(); From b11e564c7737f24b8f45b8bd4e867b4d05a044ea Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Thu, 16 Apr 2026 17:35:08 +0200 Subject: [PATCH 02/16] A proper implementation for new methods for Javascript Add new methods in the Model trait and implements where necessary. This introduce duplicated methods in the Javascript API (pushRow -> push, etc), this should be fixed. --- api/node/rust/types/model.rs | 103 +++++++++++++++++++++++++++++++++ api/node/typescript/models.ts | 60 +++++++++++++++++++ internal/core/model.rs | 70 +++++++++++++++++----- internal/interpreter/eval.rs | 6 -- tests/cases/models/array.slint | 36 ++++++------ 5 files changed, 236 insertions(+), 39 deletions(-) diff --git a/api/node/rust/types/model.rs b/api/node/rust/types/model.rs index aff8889f658..64faee716db 100644 --- a/api/node/rust/types/model.rs +++ b/api/node/rust/types/model.rs @@ -203,6 +203,109 @@ impl Model for JsModel { } } + fn push_row(&self, data: Self::Data) { + let Ok(model_unknown) = self.js_impl.get_unknown() else { + eprintln!("Node.js: JavaScript Model's pushRow threw an exception"); + return; + }; + + let Ok(model) = model_unknown.coerce_to_object() else { + eprintln!("Node.js: JavaScript Model is not an object"); + return; + }; + + let push_row_fn: Function, Unknown> = + match model.get_named_property("pushRow") { + Ok(f) => f, + Err(e) => { + eprintln!("{}", e.to_string()); + eprintln!( + "Node.js: JavaScript Model implementation is missing pushRow property" + ); + return; + } + }; + + let Ok(js_data) = to_js_unknown(&self.env, &data) else { + eprintln!( + "Node.js: Model's push_row called by Rust with data type that can't be represented in JavaScript" + ); + return; + }; + + if let Err(exception) = push_row_fn.apply(model,js_data) { + eprintln!( + "Node.js: JavaScript Model's pushRow function threw an exception: {exception}" + ); + } + } + + fn remove_row(&self, row: usize) { + let Ok(model_unknown) = self.js_impl.get_unknown() else { + eprintln!("Node.js: JavaScript Model's removeRow threw an exception"); + return; + }; + + let Ok(model) = model_unknown.coerce_to_object() else { + eprintln!("Node.js: JavaScript Model is not an object"); + return; + }; + + let remove_row_fn: Function = + match model.get_named_property("removeRow") { + Ok(f) => f, + Err(e) => { + eprintln!("{}", e.to_string()); + eprintln!( + "Node.js: JavaScript Model implementation is missing removeRow property" + ); + return; + } + }; + + if let Err(exception) = remove_row_fn.apply(model, row as f64) { + eprintln!( + "Node.js: JavaScript Model's removeRow function threw an exception: {exception}" + ); + } + } + + fn insert_row(&self, row: usize, data: Self::Data) { + let Ok(model_unknown) = self.js_impl.get_unknown() else { + eprintln!("Node.js: JavaScript Model's insertRow threw an exception"); + return; + }; + + let Ok(model) = model_unknown.coerce_to_object() else { + eprintln!("Node.js: JavaScript Model is not an object"); + return; + }; + + let insert_row_fn: Function)>, Unknown> = + match model.get_named_property("insertRow") { + Ok(f) => f, + Err(_) => { + eprintln!( + "Node.js: JavaScript Model implementation is missing insertRow property" + ); + return; + } + }; + + let Ok(js_data) = to_js_unknown(&self.env, &data) else { + eprintln!( + "Node.js: Model's insert_row called by Rust with data type that can't be represented in JavaScript" + ); + return; + }; + + if let Err(exception) = insert_row_fn.apply(model, FnArgs::from((row as f64, js_data))) { + eprintln!( + "Node.js: JavaScript Model's insertRow function threw an exception: {exception}" + ); + } + } + fn model_tracker(&self) -> &dyn i_slint_core::model::ModelTracker { &**self.shared_model_notify } diff --git a/api/node/typescript/models.ts b/api/node/typescript/models.ts index f13a53977f5..81e707bfc4a 100644 --- a/api/node/typescript/models.ts +++ b/api/node/typescript/models.ts @@ -135,6 +135,38 @@ export abstract class Model implements Iterable { ); } + /** + * Implementations of this function must add a line to the model with the provided data. + * @param _data new data item to store in a new row. + */ + pushRow(_data: T): void { + console.log( + "pushRow called on a model which does not re-implement this method. This happens when trying to modify a read-only model", + ); + } + + /** + * Implementations of this function must remove the row at the specified index. + * @param _index index of the row to remove. + */ + removeRow(_index: number): void { + console.log( + "removeRow called on a model which does not re-implement this method. This happens when trying to modify a read-only model", + ); + } + + /** + * Implementations of this function must add a row at the specified index, pushing all next + * rows to the right. + * @param _index index of the row to insert. + * @param _data new data item to store in a new row. + */ + insertRow(_index: number, _data: T): void { + console.log( + "insertRow called on a model which does not re-implement this method. This happens when trying to modify a read-only model", + ); + } + [Symbol.iterator](): Iterator { return new ModelIterator(this); } @@ -226,6 +258,34 @@ export class ArrayModel extends Model { this.notifyRowDataChanged(row); } + /** + * Add a new row to the array backing the model and notifies run-time about the added row. + * @param data new data item to store in a new row. + */ + pushRow(data: T) { + this.#array.push(data); + this.notifyRowAdded(this.#array.length - 1, 1); + } + + /** + * Remove a row from the array backing the model and notifies run-time about the removed row. + * @param _index index of the row to remove. + */ + removeRow(_index: number) { + this.#array.splice(_index, 1); + this.notifyRowRemoved(_index, 1); + } + + /** + * Insert a new row into the array backing the model at the specified index and notifies run-time about the added row. + * @param _index index at which to insert the new row. + * @param _data data item to store in the new row. + */ + insertRow(_index: number, _data: T) { + this.#array.splice(_index, 0, _data); + this.notifyRowAdded(_index, 1); + } + /** * Pushes new values to the array that's backing the model and notifies * the run-time about the added rows. diff --git a/internal/core/model.rs b/internal/core/model.rs index f91a4c4fa4e..1a81605c16b 100644 --- a/internal/core/model.rs +++ b/internal/core/model.rs @@ -146,25 +146,47 @@ pub trait Model { /// Add a new row to the model. /// - /// This function should be called on a model that supports multiple rows of data, otherwise - /// it might panic. - fn push_row(&self, data: Self::Data) { - self.set_row_data(self.row_count(), data); + /// If the model cannot support data changes, then it is ok to do nothing. + /// The default implementation will print a warning to stderr. + /// + /// If the model can update the data, it should also call [`ModelNotify::row_changed`] on its + /// internal [`ModelNotify`]. + fn push_row(&self, _data: Self::Data) { + #[cfg(feature = "std")] + crate::debug_log!( + "Model::push_row called on a model of type {} which does not re-implement this method. \ + This happens when trying to modify a read-only model", + core::any::type_name::()); + } + + /// Remove a row from the model at the specified index. + /// + /// If the model cannot support data changes, then it is ok to do nothing. + /// The default implementation will print a warning to stderr. + /// + /// If the model can update the data, it should also call [`ModelNotify::row_changed`] on its + /// internal [`ModelNotify`]. + fn remove_row(&self, _row: usize) { + #[cfg(feature = "std")] + crate::debug_log!( + "Model::remove_row called on a model of type {} which does not re-implement this method. \ + This happens when trying to modify a read-only model", + core::any::type_name::()); } /// Insert a new row at the specified index and move the next rows by 1 step to the right. /// - /// This function should be called on a model that supports multiple rows of data, otherwise - /// it might panic. - fn insert_row(&self, row: usize, data: Self::Data) { - if row < self.row_count() { - for i in (row..self.row_count()).rev() { - if let Some(d) = self.row_data(i) { - self.set_row_data(i + 1, d); - } - } - } - self.set_row_data(row, data); + /// If the model cannot support data changes, then it is ok to do nothing. + /// The default implementation will print a warning to stderr. + /// + /// If the model can update the data, it should also call [`ModelNotify::row_changed`] on its + /// internal [`ModelNotify`]. + fn insert_row(&self, _row: usize, _data: Self::Data) { + #[cfg(feature = "std")] + crate::debug_log!( + "Model::insert_row called on a model of type {} which does not re-implement this method. \ + This happens when trying to modify a read-only model", + core::any::type_name::()); } /// The implementation should return a reference to its [`ModelNotify`] field. @@ -843,6 +865,24 @@ impl Model for ModelRc { } } + fn push_row(&self, data: Self::Data) { + if let Some(model) = self.0.as_ref() { + model.push_row(data); + } + } + + fn remove_row(&self, row: usize) { + if let Some(model) = self.0.as_ref() { + model.remove_row(row); + } + } + + fn insert_row(&self, row: usize, data: Self::Data) { + if let Some(model) = self.0.as_ref() { + model.insert_row(row, data); + } + } + fn model_tracker(&self) -> &dyn ModelTracker { self.0.as_ref().map_or(&(), |model| model.model_tracker()) } diff --git a/internal/interpreter/eval.rs b/internal/interpreter/eval.rs index 2c0a46ccde6..569a1f36235 100644 --- a/internal/interpreter/eval.rs +++ b/internal/interpreter/eval.rs @@ -1423,12 +1423,6 @@ fn call_builtin_function( model.remove_row(index); - model.iter().for_each(|item| println!("{item:?}")); - - model.as_any() - .downcast_ref::>() - .expect("Couldn't get the array from the model in ArrayRemove"); - Value::Void } diff --git a/tests/cases/models/array.slint b/tests/cases/models/array.slint index b5b760904cb..7a9983e50ee 100644 --- a/tests/cases/models/array.slint +++ b/tests/cases/models/array.slint @@ -57,17 +57,17 @@ assert_eq(instance.get_ninth_int(), 9); assert_eq(instance.get_hello_world(), "world"); model->set_vector(std::vector{1, 2, 3}); -instance.invoke_push_one(42); +instance.invoke_push_one(10); assert_eq(model->row_count(), 4); -assert_eq(model->row_data(3).unwrap(), 42); +assert_eq(model->row_data(3).unwrap(), 10); instance.invoke_remove_one(2); assert_eq(model->row_count(), 3); -assert_eq(model->row_data(2).unwrap(), 42); +assert_eq(model->row_data(2).unwrap(), 10); -instance.invoke_insert_one(1, 10); +instance.invoke_insert_one(1, 20); assert_eq(model->row_count(), 4); -assert_eq(model->row_data(1).unwrap(), 10); +assert_eq(model->row_data(1).unwrap(), 20); ``` @@ -100,17 +100,17 @@ assert_eq!(instance.get_ninth_int(), 9); assert_eq!(instance.get_hello_world(), slint::SharedString::from("world")); model.set_vec([1, 2, 3]); -instance.invoke_push_one(42); +instance.invoke_push_one(10); assert_eq!(model.row_count(), 4); -assert_eq!(model.row_data(3).unwrap(), 42); +assert_eq!(model.row_data(3).unwrap(), 10); instance.invoke_remove_one(2); assert_eq!(model.row_count(), 3); -assert_eq!(model.row_data(2).unwrap(), 42); +assert_eq!(model.row_data(2).unwrap(), 10); -instance.invoke_insert_one(1, 10); +instance.invoke_insert_one(1, 20); assert_eq!(model.row_count(), 4); -assert_eq!(model.row_data(1).unwrap(), 10); +assert_eq!(model.row_data(1).unwrap(), 20); ``` ```js @@ -142,16 +142,16 @@ assert.equal(instance.hello_world, "world"); model = new slintlib.ArrayModel([1, 2, 3]); instance.ints = model; -instance.push_one(42); +instance.push_one(10); assert.equal(model.rowCount(), 4); -assert.equal(model.rowData(3), 42); +assert.equal(model.rowData(3), 10); -//instance.remove_one(2); -//assert.equal(model.rowCount(), 3); -//assert.equal(model.rowData(2), 42); +instance.remove_one(2); +assert.equal(model.rowCount(), 3); +assert.equal(model.rowData(2), 10); -instance.insert_one(1, 10); -assert.equal(model.rowCount(), 5); -assert.equal(model.rowData(1), 10); +instance.insert_one(1, 20); +assert.equal(model.rowCount(), 4); +assert.equal(model.rowData(1), 20); ``` */ From f48df48bfbf8470d56ecca77cce192e642fd2abb Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Mon, 20 Apr 2026 12:54:20 +0200 Subject: [PATCH 03/16] Add Python implementation --- api/python/slint/models.rs | 81 +++++++++++++++++++++++++++ api/python/slint/slint/models.py | 27 +++++++++ api/python/slint/tests/test_models.py | 37 ++++++++++++ 3 files changed, 145 insertions(+) diff --git a/api/python/slint/models.rs b/api/python/slint/models.rs index a4522c46ed3..7a66a7a022f 100644 --- a/api/python/slint/models.rs +++ b/api/python/slint/models.rs @@ -210,6 +210,87 @@ impl i_slint_core::model::Model for PyModelShared { }); } + fn push_row(&self, data: Self::Data) { + Python::try_attach(|py| { + let obj = self.self_ref.borrow(); + let Some(obj) = obj.as_ref() else { + eprintln!("Python: Model implementation is lacking self object (in push_row)"); + return; + }; + + let Some(type_collection) = self.type_collection.borrow().as_ref().cloned() else { + eprintln!( + "Python: Model implementation is lacking type collection (in push_row)" + ); + return; + }; + + if let Err(err) = + obj.call_method1(py, "push_row", (type_collection.to_py_value(data),)) + { + crate::handle_unraisable( + py, + "Python: Model implementation of push_row() threw an exception".into(), + err, + ); + }; + }); + } + + fn remove_row(&self, row: usize) { + Python::try_attach(|py| { + let obj = self.self_ref.borrow(); + let Some(obj) = obj.as_ref() else { + eprintln!("Python: Model implementation is lacking self object (in remove_row)"); + return; + }; + + let Some(type_collection) = self.type_collection.borrow().as_ref().cloned() else { + eprintln!( + "Python: Model implementation is lacking type collection (in remove_row)" + ); + return; + }; + + if let Err(err) = + obj.call_method1(py, "remove_row", (row,)) + { + crate::handle_unraisable( + py, + "Python: Model implementation of remove_row() threw an exception".into(), + err, + ); + }; + }); + } + + fn insert_row(&self, row: usize, data: Self::Data) { + Python::try_attach(|py| { + let obj = self.self_ref.borrow(); + let Some(obj) = obj.as_ref() else { + eprintln!("Python: Model implementation is lacking self object (in insert_row)"); + return; + }; + + let Some(type_collection) = self.type_collection.borrow().as_ref().cloned() else { + eprintln!( + "Python: Model implementation is lacking type collection (in insert_row)" + ); + return; + }; + + if let Err(err) = + obj.call_method1(py, "insert_row", (row, type_collection.to_py_value(data),)) + { + crate::handle_unraisable( + py, + "Python: Model implementation of insert_row() threw an exception".into(), + err, + ); + }; + }); + } + fn model_tracker(&self) -> &dyn i_slint_core::model::ModelTracker { &self.notify } diff --git a/api/python/slint/slint/models.py b/api/python/slint/slint/models.py index 61b55c896bb..9ac014e470d 100644 --- a/api/python/slint/slint/models.py +++ b/api/python/slint/slint/models.py @@ -45,6 +45,21 @@ def row_data(self, row: int) -> typing.Optional[T]: Re-implement this method in a sub-class to provide the data.""" return cast(T, super().row_data(row)) + def push_row(self, data: T) -> None: + """Add a new row to the model with the provided data. + Re-implement this method in a sub-class to handle the change.""" + super().push_row(data) + + def remove_row(self, row: int) -> None: + """Remove the row at the given index. + Re-implement this method in a sub-class to handle the change.""" + super().remove_row(row) + + def insert_row(self, row: int, data: T) -> None: + """Insert a new row at the given index. + Re-implement this method in a sub-class to handle the change.""" + super().insert_row(row, data) + def notify_row_changed(self, row: int) -> None: """Call this method from a sub-class to notify the views that a row has changed.""" super().notify_row_changed(row) @@ -91,6 +106,18 @@ def set_row_data(self, row: int, value: T) -> None: self.list[row] = value super().notify_row_changed(row) + def push_row(self, data: T) -> None: + self.list.append(data) + super().notify_row_added(len(self.list) - 1, 1) + + def remove_row(self, row: int) -> None: + del self.list[row] + super().notify_row_removed(row, 1) + + def insert_row(self, row: int, data: T) -> None: + self.list.insert(row, data) + self.notify_row_added(row, 1) + def __delitem__(self, key: int | slice) -> None: if isinstance(key, slice): start, stop, step = key.indices(len(self.list)) diff --git a/api/python/slint/tests/test_models.py b/api/python/slint/tests/test_models.py index 7b3c2e7a60f..d89a3a60b2e 100644 --- a/api/python/slint/tests/test_models.py +++ b/api/python/slint/tests/test_models.py @@ -157,3 +157,40 @@ def test_model_writeback() -> None: assert list(instance.get_property("model")) == [100, 42] instance.invoke("write-to-model", 0, 25) assert list(instance.get_property("model")) == [25, 42] + + +def test_model_modify() -> None: + compiler = native.Compiler() + compdef = compiler.build_from_source( + """ + export component App { + in-out property<[int]> ints; + public function push-one(value: int) { ints.push(value) } + public function remove-one(index: int) { ints.remove(index) } + public function insert-one(index: int, value: int) { ints.insert(index, value) } + } + """, + Path("") + ).component("App") + + assert compdef is not None + + instance =compdef.create() + assert instance is not None + + model = models.ListModel([1, 2, 3]) + instance.set_property("ints", model) + + assert instance.get_property("ints").row_count() == 3 + + instance.invoke("push-one", 10) + assert instance.get_property("ints").row_count() == 4 + assert instance.get_property("ints").row_data(3) == 10 + + instance.invoke("remove-one", 1) + assert instance.get_property("ints").row_count() == 3 + assert instance.get_property("ints").row_data(2) == 10 + + instance.invoke("insert-one", 1, 20) + assert instance.get_property("ints").row_count() == 4 + assert instance.get_property("ints").row_data(1) == 20 From ce641b7f160f4ca94f5b9f9aebb6c1c35505b1b9 Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Tue, 21 Apr 2026 15:24:12 +0200 Subject: [PATCH 04/16] Add live preview implementation and add error management Improves Rust code generation to use directly the new Model methods. Implements the methods in VecModel. --- internal/compiler/generator/rust.rs | 42 +++++++---------------------- internal/core/model.rs | 34 +++++++++++++++++++++++ internal/interpreter/eval.rs | 10 +++++-- tools/lsp/preview/eval.rs | 28 +++++++++---------- 4 files changed, 64 insertions(+), 50 deletions(-) diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index 5e25cabaaa8..df26ac7fbf1 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -3754,60 +3754,38 @@ fn compile_builtin_function_call( }}) } BuiltinFunction::ArrayPush => { - let inner_ty = match arguments[0].ty(ctx) { - Type::Array(elem) => rust_primitive_type(&elem).unwrap(), - _ => panic!("internal error: ArrayPush first argument is not an array: {:?}", arguments[0]), - }; let model = a.next().unwrap(); let value = a.next().unwrap(); quote!({ let model = &#model; let value = #value; - model - .as_any() - .downcast_ref::>() - .unwrap() - .push(value); + model.push_row(value); }) } BuiltinFunction::ArrayRemove => { - let inner_ty = match arguments[0].ty(ctx) { - Type::Array(elem) => rust_primitive_type(&elem).unwrap(), - _ => panic!("internal error: ArrayRemove first argument is not an array: {:?}", arguments[0]), - }; let model = a.next().unwrap(); let index = a.next().unwrap(); quote!({ let model = &#model; - let index = #index; - - // Check for index out of bound errors to avoid runtime crash ? - model - .as_any() - .downcast_ref::>() - .unwrap() - .remove(index as usize); + let index = #index as usize; + + if index < model.row_count() { + model.remove_row(index); + } }) } BuiltinFunction::ArrayInsert => { - let inner_ty = match arguments[0].ty(ctx) { - Type::Array(elem) => rust_primitive_type(&elem).unwrap(), - _ => panic!("internal error: ArrayInsert first argument is not an array: {:?}", arguments[0]), - }; let model = a.next().unwrap(); let index = a.next().unwrap(); let value = a.next().unwrap(); quote!({ let model = &#model; - let index = #index; + let index = #index as usize; let value = #value; - // Check for index out of bound errors to avoid runtime crash ? - model - .as_any() - .downcast_ref::>() - .unwrap() - .insert(index as usize, value); + if index <= model.row_count() { + model.insert_row(index, value); + } }) } BuiltinFunction::Rgb => { diff --git a/internal/core/model.rs b/internal/core/model.rs index 1a81605c16b..d4e42d7ea6b 100644 --- a/internal/core/model.rs +++ b/internal/core/model.rs @@ -527,6 +527,21 @@ impl Model for VecModel { } } + fn push_row(&self, data: Self::Data) { + self.array.borrow_mut().push(data); + self.notify.row_added(self.array.borrow().len() - 1, 1); + } + + fn remove_row(&self, row: usize) { + self.array.borrow_mut().remove(row); + self.notify.row_removed(row, 1); + } + + fn insert_row(&self, row: usize, data: Self::Data) { + self.array.borrow_mut().insert(row, data); + self.notify.row_added(row, 1); + } + fn model_tracker(&self) -> &dyn ModelTracker { &self.notify } @@ -580,6 +595,25 @@ impl Model for SharedVectorModel { self.notify.row_changed(row); } + fn push_row(&self, data: Self::Data) { + self.array.borrow_mut().push(data); + self.notify.row_added(self.array.borrow().len() - 1, 1); + } + + fn remove_row(&self, row: usize) { + let mut array = self.array.borrow_mut(); + array.make_mut_slice()[row..].rotate_left(1); + array.pop(); + self.notify.row_removed(row, 1); + } + + fn insert_row(&self, row: usize, data: Self::Data) { + let mut array = self.array.borrow_mut(); + array.push(data); + array.make_mut_slice()[row..].rotate_right(1); + self.notify.row_added(row, 1); + } + fn model_tracker(&self) -> &dyn ModelTracker { &self.notify } diff --git a/internal/interpreter/eval.rs b/internal/interpreter/eval.rs index 569a1f36235..4cf215aa6a0 100644 --- a/internal/interpreter/eval.rs +++ b/internal/interpreter/eval.rs @@ -1421,7 +1421,9 @@ fn call_builtin_function( _ => panic!("Second argument not an integer: {:?}", arguments[0]), }; - model.remove_row(index); + if index < model.row_count() { + model.remove_row(index); + } Value::Void } @@ -1439,8 +1441,12 @@ fn call_builtin_function( Value::Number(i) => i as usize, _ => panic!("Second argument not an integer: {:?}", arguments[0]), }; - let value = eval_expression(&arguments[2], local_context); + if index > model.row_count() { + return Value::Void; + } + + let value = eval_expression(&arguments[2], local_context); model.insert_row(index, value); Value::Void diff --git a/tools/lsp/preview/eval.rs b/tools/lsp/preview/eval.rs index 0b230299d88..f8f99c7e278 100644 --- a/tools/lsp/preview/eval.rs +++ b/tools/lsp/preview/eval.rs @@ -632,11 +632,7 @@ fn handle_builtin_function( }; let value = eval_expression(&arguments[1], local_context, None); - - model.as_any() - .downcast_ref::>() - .expect("ArrayPush only works on mutable arrays") - .push(value); + model.push_row(value); Value::Void } @@ -655,16 +651,15 @@ fn handle_builtin_function( _ => panic!("Second argument not an integer: {:?}", arguments[0]), }; - model.as_any() - .downcast_ref::>() - .expect("ArrayRemove only works on mutable arrays") - .remove(index); + if index < model.row_count() { + model.remove_row(index); + } Value::Void } - BuiltinFunction::ArrayRemove => { + BuiltinFunction::ArrayInsert => { if arguments.len() != 3 { - panic!("internal error: incorrect argument count to ArrayRemove") + panic!("internal error: incorrect argument count to ArrayInsert") } let model = match eval_expression(&arguments[0], local_context, None) { @@ -675,12 +670,13 @@ fn handle_builtin_function( Value::Number(i) => i as usize, _ => panic!("Second argument not an integer: {:?}", arguments[0]), }; - let value= eval_expression(&arguments[2], local_context, None); - model.as_any() - .downcast_ref::>() - .expect("ArrayRemove only works on mutable arrays") - .insert(index, value); + if index > model.row_count() { + return Value::Void; + } + + let value= eval_expression(&arguments[2], local_context, None); + model.insert_row(index, value); Value::Void } From 3fd590f907317c2c6f15c462ccf66b0b9b194a3b Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Mon, 4 May 2026 17:15:57 +0200 Subject: [PATCH 05/16] Rename python tests for better clarity --- api/python/slint/tests/test_models.py | 2 +- internal/compiler/llr/optim_passes/inline_expressions.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/api/python/slint/tests/test_models.py b/api/python/slint/tests/test_models.py index d89a3a60b2e..e382616dce9 100644 --- a/api/python/slint/tests/test_models.py +++ b/api/python/slint/tests/test_models.py @@ -159,7 +159,7 @@ def test_model_writeback() -> None: assert list(instance.get_property("model")) == [25, 42] -def test_model_modify() -> None: +def test_model_modifications() -> None: compiler = native.Compiler() compdef = compiler.build_from_source( """ diff --git a/internal/compiler/llr/optim_passes/inline_expressions.rs b/internal/compiler/llr/optim_passes/inline_expressions.rs index 91740e57a46..d874319685f 100644 --- a/internal/compiler/llr/optim_passes/inline_expressions.rs +++ b/internal/compiler/llr/optim_passes/inline_expressions.rs @@ -135,7 +135,6 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize { BuiltinFunction::ColorMix => 50, BuiltinFunction::ColorWithAlpha => 50, BuiltinFunction::ImageSize => 50, - // TODO I have currently no idea is the weight of the new functions is correct. BuiltinFunction::ArrayLength | BuiltinFunction::ArrayPush | BuiltinFunction::ArrayRemove From 3c938593bbe05d9026a0a1050fb278eeb247607b Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Mon, 4 May 2026 17:20:47 +0200 Subject: [PATCH 06/16] Fix PyModelShared implementation of `push_row` and `insert_row` --- api/python/slint/models.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/api/python/slint/models.rs b/api/python/slint/models.rs index 7a66a7a022f..0554c1f15f6 100644 --- a/api/python/slint/models.rs +++ b/api/python/slint/models.rs @@ -225,9 +225,12 @@ impl i_slint_core::model::Model for PyModelShared { return; }; - if let Err(err) = - obj.call_method1(py, "push_row", (type_collection.to_py_value(data),)) - { + let element_type = self.element_type.borrow().clone(); + if let Err(err) = obj.call_method1( + py, + "push_row", + (type_collection.to_py_value(data, element_type),), + ) { crate::handle_unraisable( py, "Python: Model implementation of push_row() threw an exception".into(), @@ -279,9 +282,12 @@ impl i_slint_core::model::Model for PyModelShared { return; }; - if let Err(err) = - obj.call_method1(py, "insert_row", (row, type_collection.to_py_value(data),)) - { + let element_type = self.element_type.borrow().clone(); + if let Err(err) = obj.call_method1( + py, + "insert_row", + (row, type_collection.to_py_value(data, element_type)), + ) { crate::handle_unraisable( py, "Python: Model implementation of insert_row() threw an exception".into(), From fd5cafd192b37844ab9df5f7d026b758a7f6051e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 16:17:22 +0000 Subject: [PATCH 07/16] [autofix.ci] apply automated fixes --- api/node/rust/types/model.rs | 47 +++++++++++++-------------- api/python/slint/models.rs | 24 +++++--------- api/python/slint/tests/test_models.py | 4 +-- internal/compiler/builtin_macros.rs | 21 ++++-------- internal/core/model.rs | 11 ++++--- internal/interpreter/eval.rs | 2 +- tools/lsp/preview/eval.rs | 2 +- 7 files changed, 49 insertions(+), 62 deletions(-) diff --git a/api/node/rust/types/model.rs b/api/node/rust/types/model.rs index 64faee716db..eea3d930e77 100644 --- a/api/node/rust/types/model.rs +++ b/api/node/rust/types/model.rs @@ -214,17 +214,17 @@ impl Model for JsModel { return; }; - let push_row_fn: Function, Unknown> = - match model.get_named_property("pushRow") { - Ok(f) => f, - Err(e) => { - eprintln!("{}", e.to_string()); - eprintln!( - "Node.js: JavaScript Model implementation is missing pushRow property" - ); - return; - } - }; + let push_row_fn: Function, Unknown> = match model.get_named_property("pushRow") + { + Ok(f) => f, + Err(e) => { + eprintln!("{}", e.to_string()); + eprintln!( + "Node.js: JavaScript Model implementation is missing pushRow property" + ); + return; + } + }; let Ok(js_data) = to_js_unknown(&self.env, &data) else { eprintln!( @@ -233,7 +233,7 @@ impl Model for JsModel { return; }; - if let Err(exception) = push_row_fn.apply(model,js_data) { + if let Err(exception) = push_row_fn.apply(model, js_data) { eprintln!( "Node.js: JavaScript Model's pushRow function threw an exception: {exception}" ); @@ -251,17 +251,16 @@ impl Model for JsModel { return; }; - let remove_row_fn: Function = - match model.get_named_property("removeRow") { - Ok(f) => f, - Err(e) => { - eprintln!("{}", e.to_string()); - eprintln!( - "Node.js: JavaScript Model implementation is missing removeRow property" - ); - return; - } - }; + let remove_row_fn: Function = match model.get_named_property("removeRow") { + Ok(f) => f, + Err(e) => { + eprintln!("{}", e.to_string()); + eprintln!( + "Node.js: JavaScript Model implementation is missing removeRow property" + ); + return; + } + }; if let Err(exception) = remove_row_fn.apply(model, row as f64) { eprintln!( @@ -269,7 +268,7 @@ impl Model for JsModel { ); } } - + fn insert_row(&self, row: usize, data: Self::Data) { let Ok(model_unknown) = self.js_impl.get_unknown() else { eprintln!("Node.js: JavaScript Model's insertRow threw an exception"); diff --git a/api/python/slint/models.rs b/api/python/slint/models.rs index 0554c1f15f6..445513db350 100644 --- a/api/python/slint/models.rs +++ b/api/python/slint/models.rs @@ -219,18 +219,14 @@ impl i_slint_core::model::Model for PyModelShared { }; let Some(type_collection) = self.type_collection.borrow().as_ref().cloned() else { - eprintln!( - "Python: Model implementation is lacking type collection (in push_row)" - ); + eprintln!("Python: Model implementation is lacking type collection (in push_row)"); return; }; let element_type = self.element_type.borrow().clone(); - if let Err(err) = obj.call_method1( - py, - "push_row", - (type_collection.to_py_value(data, element_type),), - ) { + if let Err(err) = + obj.call_method1(py, "push_row", (type_collection.to_py_value(data, element_type),)) + { crate::handle_unraisable( py, "Python: Model implementation of push_row() threw an exception".into(), @@ -239,7 +235,7 @@ impl i_slint_core::model::Model for PyModelShared { }; }); } - + fn remove_row(&self, row: usize) { Python::try_attach(|py| { let obj = self.self_ref.borrow(); @@ -255,9 +251,7 @@ impl i_slint_core::model::Model for PyModelShared { return; }; - if let Err(err) = - obj.call_method1(py, "remove_row", (row,)) - { + if let Err(err) = obj.call_method1(py, "remove_row", (row,)) { crate::handle_unraisable( py, "Python: Model implementation of remove_row() threw an exception".into(), @@ -266,7 +260,7 @@ impl i_slint_core::model::Model for PyModelShared { }; }); } - + fn insert_row(&self, row: usize, data: Self::Data) { Python::try_attach(|py| { let obj = self.self_ref.borrow(); @@ -284,8 +278,8 @@ impl i_slint_core::model::Model for PyModelShared { let element_type = self.element_type.borrow().clone(); if let Err(err) = obj.call_method1( - py, - "insert_row", + py, + "insert_row", (row, type_collection.to_py_value(data, element_type)), ) { crate::handle_unraisable( diff --git a/api/python/slint/tests/test_models.py b/api/python/slint/tests/test_models.py index e382616dce9..b0a029f7bc8 100644 --- a/api/python/slint/tests/test_models.py +++ b/api/python/slint/tests/test_models.py @@ -170,12 +170,12 @@ def test_model_modifications() -> None: public function insert-one(index: int, value: int) { ints.insert(index, value) } } """, - Path("") + Path(""), ).component("App") assert compdef is not None - instance =compdef.create() + instance = compdef.create() assert instance is not None model = models.ListModel([1, 2, 3]) diff --git a/internal/compiler/builtin_macros.rs b/internal/compiler/builtin_macros.rs index 5219374c7fc..9a2c783611b 100644 --- a/internal/compiler/builtin_macros.rs +++ b/internal/compiler/builtin_macros.rs @@ -393,7 +393,7 @@ fn array_push_macro( "This method needs 1 argument but {} were provided", if args.len() == 1 { 0 } else { args.len() - 1 } // Avoid counting the model argument. ), - node + node, ); return Expression::Invalid; } @@ -401,10 +401,7 @@ fn array_push_macro( let element_type = match args[0].0.ty() { Type::Array(t) => (*t).clone(), _ => { - diag.push_error( - format!("push() was called on a non-array: {:?}", args[0].0), - node - ); + diag.push_error(format!("push() was called on a non-array: {:?}", args[0].0), node); return Expression::Invalid; } }; @@ -430,16 +427,13 @@ fn array_remove_macro( "This method needs 1 argument but {} were provided", if args.len() == 1 { 0 } else { args.len() - 1 } // Avoid counting the model argument. ), - node + node, ); return Expression::Invalid; } if !matches!(args[0].0.ty(), Type::Array(_)) { - diag.push_error( - format!("remove() was called on a non-array: {:?}", args[0].0), - node - ); + diag.push_error(format!("remove() was called on a non-array: {:?}", args[0].0), node); return Expression::Invalid; } @@ -464,7 +458,7 @@ fn array_insert_macro( "This method needs 2 argument but {} were provided", if args.len() == 1 { 0 } else { args.len() - 1 } // Avoid counting the model argument. ), - node + node, ); return Expression::Invalid; } @@ -472,10 +466,7 @@ fn array_insert_macro( let element_type = match args[0].0.ty() { Type::Array(t) => (*t).clone(), _ => { - diag.push_error( - format!("push() was called on a non-array: {:?}", args[0].0), - node - ); + diag.push_error(format!("push() was called on a non-array: {:?}", args[0].0), node); return Expression::Invalid; } }; diff --git a/internal/core/model.rs b/internal/core/model.rs index d4e42d7ea6b..3e13866b3e7 100644 --- a/internal/core/model.rs +++ b/internal/core/model.rs @@ -156,7 +156,8 @@ pub trait Model { crate::debug_log!( "Model::push_row called on a model of type {} which does not re-implement this method. \ This happens when trying to modify a read-only model", - core::any::type_name::()); + core::any::type_name::() + ); } /// Remove a row from the model at the specified index. @@ -171,7 +172,8 @@ pub trait Model { crate::debug_log!( "Model::remove_row called on a model of type {} which does not re-implement this method. \ This happens when trying to modify a read-only model", - core::any::type_name::()); + core::any::type_name::() + ); } /// Insert a new row at the specified index and move the next rows by 1 step to the right. @@ -186,7 +188,8 @@ pub trait Model { crate::debug_log!( "Model::insert_row called on a model of type {} which does not re-implement this method. \ This happens when trying to modify a read-only model", - core::any::type_name::()); + core::any::type_name::() + ); } /// The implementation should return a reference to its [`ModelNotify`] field. @@ -914,7 +917,7 @@ impl Model for ModelRc { fn insert_row(&self, row: usize, data: Self::Data) { if let Some(model) = self.0.as_ref() { model.insert_row(row, data); - } + } } fn model_tracker(&self) -> &dyn ModelTracker { diff --git a/internal/interpreter/eval.rs b/internal/interpreter/eval.rs index 4cf215aa6a0..737488cb2e3 100644 --- a/internal/interpreter/eval.rs +++ b/internal/interpreter/eval.rs @@ -1416,7 +1416,7 @@ fn call_builtin_function( Value::Model(m) => m, _ => panic!("First argument not an array: {:?}", arguments[0]), }; - let index = match eval_expression(&arguments[1], local_context) { + let index = match eval_expression(&arguments[1], local_context) { Value::Number(i) => i as usize, _ => panic!("Second argument not an integer: {:?}", arguments[0]), }; diff --git a/tools/lsp/preview/eval.rs b/tools/lsp/preview/eval.rs index f8f99c7e278..038b83c8186 100644 --- a/tools/lsp/preview/eval.rs +++ b/tools/lsp/preview/eval.rs @@ -675,7 +675,7 @@ fn handle_builtin_function( return Value::Void; } - let value= eval_expression(&arguments[2], local_context, None); + let value = eval_expression(&arguments[2], local_context, None); model.insert_row(index, value); Value::Void From deab2e119d5b54e8ae4be64e8b0cb0c69b280d49 Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Mon, 4 May 2026 18:55:39 +0200 Subject: [PATCH 08/16] Remove unused variable Add missing branch in C++ generator with todo!(). --- api/python/slint/models.rs | 7 ------- internal/compiler/generator/cpp.rs | 9 +++++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/api/python/slint/models.rs b/api/python/slint/models.rs index 445513db350..67264625569 100644 --- a/api/python/slint/models.rs +++ b/api/python/slint/models.rs @@ -244,13 +244,6 @@ impl i_slint_core::model::Model for PyModelShared { return; }; - let Some(type_collection) = self.type_collection.borrow().as_ref().cloned() else { - eprintln!( - "Python: Model implementation is lacking type collection (in remove_row)" - ); - return; - }; - if let Err(err) = obj.call_method1(py, "remove_row", (row,)) { crate::handle_unraisable( py, diff --git a/internal/compiler/generator/cpp.rs b/internal/compiler/generator/cpp.rs index b98c729ce56..edd4d8e9441 100644 --- a/internal/compiler/generator/cpp.rs +++ b/internal/compiler/generator/cpp.rs @@ -4415,6 +4415,15 @@ fn compile_builtin_function_call( BuiltinFunction::ArrayLength => { format!("slint::private_api::model_length({})", a.next().unwrap()) } + BuiltinFunction::ArrayPush => { + todo!() + } + BuiltinFunction::ArrayRemove => { + todo!() + } + BuiltinFunction::ArrayInsert => { + todo!() + } BuiltinFunction::Rgb => { format!("slint::Color::from_argb_uint8(std::clamp(static_cast({a}) * 255., 0., 255.), std::clamp(static_cast({r}), 0, 255), std::clamp(static_cast({g}), 0, 255), std::clamp(static_cast({b}), 0, 255))", r = a.next().unwrap(), From 72167377a7fe4537f7755eb60b3938b3abd96beb Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Mon, 4 May 2026 19:31:51 +0200 Subject: [PATCH 09/16] Add new methods in PyModelBase --- api/python/slint/slint/models.py | 18 +++++++++--------- api/python/slint/slint/slint.pyi | 3 +++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/api/python/slint/slint/models.py b/api/python/slint/slint/models.py index 9ac014e470d..0e7ff709c29 100644 --- a/api/python/slint/slint/models.py +++ b/api/python/slint/slint/models.py @@ -45,20 +45,20 @@ def row_data(self, row: int) -> typing.Optional[T]: Re-implement this method in a sub-class to provide the data.""" return cast(T, super().row_data(row)) - def push_row(self, data: T) -> None: - """Add a new row to the model with the provided data. + def push_row(self, value: T) -> None: + """Add a new row to the model with the provided value. Re-implement this method in a sub-class to handle the change.""" - super().push_row(data) + super().push_row(value) def remove_row(self, row: int) -> None: """Remove the row at the given index. Re-implement this method in a sub-class to handle the change.""" super().remove_row(row) - def insert_row(self, row: int, data: T) -> None: + def insert_row(self, row: int, value: T) -> None: """Insert a new row at the given index. Re-implement this method in a sub-class to handle the change.""" - super().insert_row(row, data) + super().insert_row(row, value) def notify_row_changed(self, row: int) -> None: """Call this method from a sub-class to notify the views that a row has changed.""" @@ -106,16 +106,16 @@ def set_row_data(self, row: int, value: T) -> None: self.list[row] = value super().notify_row_changed(row) - def push_row(self, data: T) -> None: - self.list.append(data) + def push_row(self, value: T) -> None: + self.list.append(value) super().notify_row_added(len(self.list) - 1, 1) def remove_row(self, row: int) -> None: del self.list[row] super().notify_row_removed(row, 1) - def insert_row(self, row: int, data: T) -> None: - self.list.insert(row, data) + def insert_row(self, row: int, value: T) -> None: + self.list.insert(row, value) self.notify_row_added(row, 1) def __delitem__(self, key: int | slice) -> None: diff --git a/api/python/slint/slint/slint.pyi b/api/python/slint/slint/slint.pyi index 7dc3f22e3bd..afe316d4b8e 100644 --- a/api/python/slint/slint/slint.pyi +++ b/api/python/slint/slint/slint.pyi @@ -169,6 +169,9 @@ class PyModelBase: def row_count(self) -> int: ... def row_data(self, row: int) -> typing.Optional[Any]: ... def set_row_data(self, row: int, value: Any) -> None: ... + def push_row(self, value: Any) -> None: ... + def remove_row(self, row: int) -> None: ... + def insert_row(self, row: int, value: Any) -> None: ... def notify_row_changed(self, row: int) -> None: ... def notify_row_removed(self, row: int, count: int) -> None: ... def notify_row_added(self, row: int, count: int) -> None: ... From bb0bcdeab20ea71de5f2740512690bedfb829e8b Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Thu, 7 May 2026 01:11:57 +0200 Subject: [PATCH 10/16] Apply first batch of fixes --- internal/compiler/llr/optim_passes/inline_expressions.rs | 6 +++--- internal/core/model.rs | 9 +++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/internal/compiler/llr/optim_passes/inline_expressions.rs b/internal/compiler/llr/optim_passes/inline_expressions.rs index d874319685f..0d3aebfbff0 100644 --- a/internal/compiler/llr/optim_passes/inline_expressions.rs +++ b/internal/compiler/llr/optim_passes/inline_expressions.rs @@ -135,10 +135,10 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize { BuiltinFunction::ColorMix => 50, BuiltinFunction::ColorWithAlpha => 50, BuiltinFunction::ImageSize => 50, - BuiltinFunction::ArrayLength - | BuiltinFunction::ArrayPush + BuiltinFunction::ArrayLength => 50, + BuiltinFunction::ArrayPush | BuiltinFunction::ArrayRemove - | BuiltinFunction::ArrayInsert => 50, + | BuiltinFunction::ArrayInsert => ALLOC_COST, BuiltinFunction::Rgb => 50, BuiltinFunction::Hsv => 50, BuiltinFunction::Oklch => 50, diff --git a/internal/core/model.rs b/internal/core/model.rs index 3e13866b3e7..a6c3d601648 100644 --- a/internal/core/model.rs +++ b/internal/core/model.rs @@ -531,18 +531,15 @@ impl Model for VecModel { } fn push_row(&self, data: Self::Data) { - self.array.borrow_mut().push(data); - self.notify.row_added(self.array.borrow().len() - 1, 1); + self.push(data); } fn remove_row(&self, row: usize) { - self.array.borrow_mut().remove(row); - self.notify.row_removed(row, 1); + self.remove(row); } fn insert_row(&self, row: usize, data: Self::Data) { - self.array.borrow_mut().insert(row, data); - self.notify.row_added(row, 1); + self.insert(row, data); } fn model_tracker(&self) -> &dyn ModelTracker { From e2c9ea0d59709978ae526fad4976e37cab9a2612 Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Mon, 18 May 2026 13:45:24 +0200 Subject: [PATCH 11/16] Move boundary checks in `_row` methods --- internal/compiler/generator/rust.rs | 8 ++------ internal/core/model.rs | 14 ++++++++++++-- internal/interpreter/eval.rs | 8 +------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index 55eb3a6fea3..622305ca62a 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -3939,9 +3939,7 @@ fn compile_builtin_function_call( let model = &#model; let index = #index as usize; - if index < model.row_count() { - model.remove_row(index); - } + model.remove_row(index); }) } BuiltinFunction::ArrayInsert => { @@ -3953,9 +3951,7 @@ fn compile_builtin_function_call( let index = #index as usize; let value = #value; - if index <= model.row_count() { - model.insert_row(index, value); - } + model.insert_row(index, value); }) } BuiltinFunction::Rgb => { diff --git a/internal/core/model.rs b/internal/core/model.rs index 0344611602e..30a28c0c6b0 100644 --- a/internal/core/model.rs +++ b/internal/core/model.rs @@ -535,11 +535,15 @@ impl Model for VecModel { } fn remove_row(&self, row: usize) { - self.remove(row); + if row < self.row_count() { + self.remove(row); + } } fn insert_row(&self, row: usize, data: Self::Data) { - self.insert(row, data); + if row <= self.row_count() { + self.insert(row, data); + } } fn model_tracker(&self) -> &dyn ModelTracker { @@ -601,6 +605,9 @@ impl Model for SharedVectorModel { } fn remove_row(&self, row: usize) { + if row >= self.array.borrow().len() { + return; + } let mut array = self.array.borrow_mut(); array.make_mut_slice()[row..].rotate_left(1); array.pop(); @@ -608,6 +615,9 @@ impl Model for SharedVectorModel { } fn insert_row(&self, row: usize, data: Self::Data) { + if row > self.array.borrow().len() { + return; + } let mut array = self.array.borrow_mut(); array.push(data); array.make_mut_slice()[row..].rotate_right(1); diff --git a/internal/interpreter/eval.rs b/internal/interpreter/eval.rs index 901d4baaa0a..c958350ae5c 100644 --- a/internal/interpreter/eval.rs +++ b/internal/interpreter/eval.rs @@ -1432,9 +1432,7 @@ fn call_builtin_function( _ => panic!("Second argument not an integer: {:?}", arguments[0]), }; - if index < model.row_count() { - model.remove_row(index); - } + model.remove_row(index); Value::Void } @@ -1453,10 +1451,6 @@ fn call_builtin_function( _ => panic!("Second argument not an integer: {:?}", arguments[0]), }; - if index > model.row_count() { - return Value::Void; - } - let value = eval_expression(&arguments[2], local_context); model.insert_row(index, value); From 997df9d1cf243ee776b5f454c4af0b0d0360aeb3 Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Mon, 18 May 2026 13:45:42 +0200 Subject: [PATCH 12/16] Fix compilation error with napi --- api/node/rust/types/model.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/node/rust/types/model.rs b/api/node/rust/types/model.rs index 408450fe4ce..61a6f398e42 100644 --- a/api/node/rust/types/model.rs +++ b/api/node/rust/types/model.rs @@ -245,7 +245,7 @@ impl Model for JsModel { } fn push_row(&self, data: Self::Data) { - let Ok(model_unknown) = self.js_impl.get_unknown() else { + let Some(model_unknown) = self.js_impl.get_unknown() else { eprintln!("Node.js: JavaScript Model's pushRow threw an exception"); return; }; @@ -282,7 +282,7 @@ impl Model for JsModel { } fn remove_row(&self, row: usize) { - let Ok(model_unknown) = self.js_impl.get_unknown() else { + let Some(model_unknown) = self.js_impl.get_unknown() else { eprintln!("Node.js: JavaScript Model's removeRow threw an exception"); return; }; @@ -311,7 +311,7 @@ impl Model for JsModel { } fn insert_row(&self, row: usize, data: Self::Data) { - let Ok(model_unknown) = self.js_impl.get_unknown() else { + let Some(model_unknown) = self.js_impl.get_unknown() else { eprintln!("Node.js: JavaScript Model's insertRow threw an exception"); return; }; From 20f85b1c72d2c530cf73ee7f086205f7801e72f9 Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Mon, 18 May 2026 13:46:10 +0200 Subject: [PATCH 13/16] Use newly added methods in Python and Typescript APIs --- api/node/typescript/models.ts | 9 +++------ api/python/slint/slint/models.py | 6 ++---- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/api/node/typescript/models.ts b/api/node/typescript/models.ts index c070f568c2f..3919784bb0b 100644 --- a/api/node/typescript/models.ts +++ b/api/node/typescript/models.ts @@ -263,8 +263,7 @@ export class ArrayModel extends Model { * @param data new data item to store in a new row. */ pushRow(data: T) { - this.#array.push(data); - this.notifyRowAdded(this.#array.length - 1, 1); + this.push(data); } /** @@ -272,8 +271,7 @@ export class ArrayModel extends Model { * @param _index index of the row to remove. */ removeRow(_index: number) { - this.#array.splice(_index, 1); - this.notifyRowRemoved(_index, 1); + this.remove(_index, 1); } /** @@ -282,8 +280,7 @@ export class ArrayModel extends Model { * @param _data data item to store in the new row. */ insertRow(_index: number, _data: T) { - this.#array.splice(_index, 0, _data); - this.notifyRowAdded(_index, 1); + this.insert(_index, _data); } /** diff --git a/api/python/slint/slint/models.py b/api/python/slint/slint/models.py index 2c7c8507502..66b54df86f7 100644 --- a/api/python/slint/slint/models.py +++ b/api/python/slint/slint/models.py @@ -107,16 +107,14 @@ def set_row_data(self, row: int, value: T) -> None: super().notify_row_changed(row) def push_row(self, value: T) -> None: - self.list.append(value) - super().notify_row_added(len(self.list) - 1, 1) + self.append(value) def remove_row(self, row: int) -> None: del self.list[row] super().notify_row_removed(row, 1) def insert_row(self, row: int, value: T) -> None: - self.list.insert(row, value) - self.notify_row_added(row, 1) + self.insert(row, value) def __delitem__(self, key: int | slice) -> None: if isinstance(key, slice): From aec31d72742c1f5f36b92a1dd8d56579fc4933c5 Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Mon, 18 May 2026 23:19:58 +0200 Subject: [PATCH 14/16] Remove unnecessary error as lookup would fail before getting there. --- internal/compiler/builtin_macros.rs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/internal/compiler/builtin_macros.rs b/internal/compiler/builtin_macros.rs index 5c9a2db4c99..5ba35d2dbc1 100644 --- a/internal/compiler/builtin_macros.rs +++ b/internal/compiler/builtin_macros.rs @@ -398,12 +398,10 @@ fn array_push_macro( return Expression::Invalid; } - let element_type = match args[0].0.ty() { - Type::Array(t) => (*t).clone(), - _ => { - diag.push_error(format!("push() was called on a non-array: {:?}", args[0].0), node); - return Expression::Invalid; - } + let element_type = if let Type::Array(t) = args[0].0.ty() { + (*t).clone() + } else { + Type::Invalid }; let (model_expr, _) = args.remove(0); @@ -432,11 +430,6 @@ fn array_remove_macro( return Expression::Invalid; } - if !matches!(args[0].0.ty(), Type::Array(_)) { - diag.push_error(format!("remove() was called on a non-array: {:?}", args[0].0), node); - return Expression::Invalid; - } - let (model_expr, _) = args.remove(0); let (index_expr, index_node) = args.remove(0); let index = index_expr.maybe_convert_to(Type::Int32, &index_node, diag); @@ -463,12 +456,10 @@ fn array_insert_macro( return Expression::Invalid; } - let element_type = match args[0].0.ty() { - Type::Array(t) => (*t).clone(), - _ => { - diag.push_error(format!("push() was called on a non-array: {:?}", args[0].0), node); - return Expression::Invalid; - } + let element_type = if let Type::Array(t) = args[0].0.ty() { + (*t).clone() + } else { + Type::Invalid }; let (model_expr, _) = args.remove(0); From 5a7c38e702a604015208790523250b86bd6d8c29 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 21:22:01 +0000 Subject: [PATCH 15/16] [autofix.ci] apply automated fixes --- internal/compiler/builtin_macros.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/internal/compiler/builtin_macros.rs b/internal/compiler/builtin_macros.rs index 5ba35d2dbc1..2189a5b82af 100644 --- a/internal/compiler/builtin_macros.rs +++ b/internal/compiler/builtin_macros.rs @@ -398,11 +398,8 @@ fn array_push_macro( return Expression::Invalid; } - let element_type = if let Type::Array(t) = args[0].0.ty() { - (*t).clone() - } else { - Type::Invalid - }; + let element_type = + if let Type::Array(t) = args[0].0.ty() { (*t).clone() } else { Type::Invalid }; let (model_expr, _) = args.remove(0); let (value_expr, value_node) = args.remove(0); @@ -456,11 +453,8 @@ fn array_insert_macro( return Expression::Invalid; } - let element_type = if let Type::Array(t) = args[0].0.ty() { - (*t).clone() - } else { - Type::Invalid - }; + let element_type = + if let Type::Array(t) = args[0].0.ty() { (*t).clone() } else { Type::Invalid }; let (model_expr, _) = args.remove(0); let (index_expr, index_node) = args.remove(0); From 3a3ec692ac77450374243d5d270aadd9d4921839 Mon Sep 17 00:00:00 2001 From: uAtomicBoolean Date: Mon, 18 May 2026 23:43:24 +0200 Subject: [PATCH 16/16] Use Type::InferredProperty in builtin function types. Remove useless part of comment --- internal/compiler/expression_tree.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/compiler/expression_tree.rs b/internal/compiler/expression_tree.rs index d6a70b26fda..16d4c21ade0 100644 --- a/internal/compiler/expression_tree.rs +++ b/internal/compiler/expression_tree.rs @@ -275,9 +275,10 @@ declare_builtin_function_types!( name: crate::langtype::BuiltinPrivateStruct::Size.into(), })), ArrayLength: (Type::Model) -> Type::Int32, - ArrayPush: (Type::Model) -> Type::Void, + // Using Type::InferredProperty as there is currently no valid type for the data argument. + ArrayPush: (Type::Model, Type::InferredProperty) -> Type::Void, ArrayRemove: (Type::Model, Type::Int32) -> Type::Void, - ArrayInsert: (Type::Model, Type::Int32) -> Type::Void, + ArrayInsert: (Type::Model, Type::Int32, Type::InferredProperty) -> Type::Void, Rgb: (Type::Int32, Type::Int32, Type::Int32, Type::Float32) -> Type::Color, Hsv: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color, Oklch: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color,