diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8c2d9d31..7049e7200 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1360,7 +1360,9 @@ jobs: name: modelica_models Compatibility Gate runs-on: ubuntu-24.04 needs: [nix-build-msl] - timeout-minutes: 15 + # Cachix is optional for PRs, so a cache miss must leave enough time to + # rebuild the shared MSL artifact bundle before compiling the corpus. + timeout-minutes: 60 steps: - uses: actions/checkout@v5 with: diff --git a/crates/rumoca-phase-solve/src/lower/array_values/dynamic_selection.rs b/crates/rumoca-phase-solve/src/lower/array_values/dynamic_selection.rs index 8155ac12e..a41ce1a66 100644 --- a/crates/rumoca-phase-solve/src/lower/array_values/dynamic_selection.rs +++ b/crates/rumoca-phase-solve/src/lower/array_values/dynamic_selection.rs @@ -1365,6 +1365,13 @@ impl<'a> LowerBuilder<'a> { } }; + if component_reference_has_slice_subscript(comp) { + match self.lower_statement_or_stop(statement, scope, call_depth)? { + true => break, + false => continue, + } + } + let target = assignment_target(comp, &self.local_const_bindings)?; if target.indices.is_some() || target.base != output.name { match self.lower_statement_or_stop(statement, scope, call_depth)? { diff --git a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection.rs b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection.rs index 31c843416..ebe265352 100644 --- a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection.rs +++ b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection.rs @@ -25,6 +25,8 @@ mod projected_array; #[path = "function_projection/projection_helpers.rs"] mod projection_helpers; mod projection_selection; +#[path = "function_projection/scope_initialization.rs"] +mod scope_initialization; #[path = "function_projection/selected_output.rs"] mod selected_output; #[path = "function_projection/target_projection.rs"] @@ -39,11 +41,12 @@ use dimension_helpers::{ assignment_projection_dims, binary_mul_dims, constructor_input_projection_dims, copy_projection_dims, declared_dims, dimension_mismatch_error, elementwise_binary_dims, exact_declared_function_output_dims, flat_index_from_indices, flatten_array_elements, - formal_actual_projection_dims, is_ignorable_projection_statement, is_same_plain_var_ref, - named_actual_span, named_argument_spans, projected_declared_output_dims, - projected_field_output_dims, projection_assignment_target, required_flat_index_to_subscripts, - reserve_projection_capacity, scalar_count_for_dims, selector_dims_from_indices, - single_field_path, sum_expressions, valid_product_dim, + formal_accepts_structured_actual, formal_actual_projection_dims, + is_ignorable_projection_statement, is_same_plain_var_ref, named_actual_span, + named_argument_spans, projected_declared_output_dims, projected_field_output_dims, + projection_assignment_target, required_flat_index_to_subscripts, reserve_projection_capacity, + scalar_count_for_dims, selector_dims_from_indices, single_field_path, sum_expressions, + valid_product_dim, }; #[cfg(test)] use entrypoints::take_selected_projected_output; @@ -64,9 +67,9 @@ use projected_array::projected_array_expression; use projection_helpers::{ ArrayProjectionValueCtx, IfStatementProjection, MatrixVectorProductDims, ProjectionAssignmentSelector, ProjectionAssignmentTarget, ProjectionValueCtx, - ScalarSelectionCtx, SelectedAssignment, array_element_scalar_width, - inherited_projection_source_span, inherited_projection_span, matrix_column_child_flat_index, - matrix_column_operand_count, matrix_elements_are_row_literals, + ScalarSelectionCtx, ScopedSelectionValueCtx, ScopedSubscriptProjectionCtx, SelectedAssignment, + array_element_scalar_width, inherited_projection_source_span, inherited_projection_span, + matrix_column_child_flat_index, matrix_column_operand_count, matrix_elements_are_row_literals, outputs_contain_unresolved_function_scope_refs, projection_actual_with_span, projection_arg_or_context_span, projection_value_ctx, }; @@ -393,59 +396,29 @@ impl<'a> FunctionProjectionAnalysis<'a> { format!("function `{}` input `{}`", function.name, input.name), actual.span().unwrap_or(actual_span), )?; - if let Some(dims) = dims.filter(|dims| !dims.is_empty()) { - self.insert_input_scalar_projection( - input, - &actual, - dims, - &mut scope, - depth + 1, - actual_span, - )?; + match dims { + Some(dims) if !dims.is_empty() => { + self.insert_input_scalar_projection( + input, + &actual, + dims, + &mut scope, + depth + 1, + actual_span, + )?; + } + Some(dims) => { + scope.dims.insert(input.name.clone(), dims); + } + None if input.dims.is_empty() && !formal_accepts_structured_actual(input) => { + scope.dims.insert(input.name.clone(), Vec::new()); + } + None => {} } } Ok(Some(scope)) } - fn initialize_projected_declared_arrays( - &self, - function: &rumoca_core::Function, - scope: &mut FunctionProjectionScope, - depth: usize, - owner_span: rumoca_core::Span, - ) -> Result<(), LowerError> { - for param in function.outputs.iter().chain(function.locals.iter()) { - if self.initialize_declared_default(param, scope, depth + 1, owner_span)? { - continue; - } - if param.dims.is_empty() || scope.scalars.contains_key(param.name.as_str()) { - continue; - } - let param_span = inherited_projection_span(param.span, owner_span); - let count = scalar_count_for_dims( - ¶m.dims, - "function declared array dimensions", - param_span, - )?; - let dims = copy_projection_dims( - ¶m.dims, - "projected declared array dimension count", - param_span, - )?; - scope.dims.insert(param.name.clone(), dims); - let mut scalars = projection_vec_with_capacity( - count, - "projected declared array scalar count", - param_span, - )?; - for _ in 0..count { - scalars.push(rumoca_core::Expression::Empty { span: param_span }); - } - scope.scalars.insert(param.name.clone(), scalars); - } - Ok(()) - } - fn initialize_declared_default( &self, param: &rumoca_core::FunctionParam, @@ -644,7 +617,7 @@ impl<'a> FunctionProjectionAnalysis<'a> { is_constructor: false, span, }; - let call = self.substitute(&call, scope)?; + let call = self.substitute_for_call(&call, scope)?; let projected = self .function_call_outputs_with_owner(&call, depth + 1, span)? .ok_or_else(|| { @@ -786,8 +759,13 @@ impl<'a> FunctionProjectionAnalysis<'a> { } return Ok(()); } - let inferred_dims = self.expr_dims_with_owner(&value, scope, depth + 1, value_span)?; + let inferred_dims = self + .expr_dims_with_owner(&value, scope, depth + 1, value_span)? + .or_else(|| scope.dims.get(target.as_str()).cloned()); let dims = assignment_projection_dims(function, &target, inferred_dims, value_span)?; + if let Some(dims) = dims.as_ref().filter(|dims| dims.is_empty()) { + scope.dims.insert(target.clone(), dims.clone()); + } if let Some(dims) = dims.filter(|dims| !dims.is_empty()) { let scalars = self .project_value_scalars(&value, &dims, scope, depth + 1, value_span) @@ -932,6 +910,12 @@ impl<'a> FunctionProjectionAnalysis<'a> { span, ) })?; + if !self + .subscripted_projection_dims(dims, subscripts, scope, depth + 1, span)? + .is_empty() + { + return self.substitute(value, scope); + } return projected_scalar_selection( ScalarSelectionCtx { name: name.as_str(), @@ -969,6 +953,12 @@ impl<'a> FunctionProjectionAnalysis<'a> { *span, ) })?; + if !self + .subscripted_projection_dims(dims, subscripts, scope, depth + 1, *span)? + .is_empty() + { + return Ok(value); + } projected_scalar_selection( ScalarSelectionCtx { name: name.as_str(), @@ -1427,7 +1417,28 @@ impl<'a> FunctionProjectionAnalysis<'a> { expr: &rumoca_core::Expression, scope: &FunctionProjectionScope, ) -> Result { - let mut substituter = FunctionScopeSubstituter { scope, error: None }; + let mut substituter = FunctionScopeSubstituter { + scope, + materialize_arrays: false, + error: None, + }; + let expr = substituter.rewrite_expression(expr); + if let Some(error) = substituter.error { + return Err(error); + } + Ok(expr) + } + + fn substitute_for_call( + &self, + expr: &rumoca_core::Expression, + scope: &FunctionProjectionScope, + ) -> Result { + let mut substituter = FunctionScopeSubstituter { + scope, + materialize_arrays: true, + error: None, + }; let expr = substituter.rewrite_expression(expr); if let Some(error) = substituter.error { return Err(error); @@ -1463,7 +1474,7 @@ impl<'a> FunctionProjectionAnalysis<'a> { depth: usize, owner_span: rumoca_core::Span, ) -> Result>, LowerError> { - let mut substituted = self.substitute(expr, scope)?; + let mut substituted = self.substitute_for_call(expr, scope)?; if substituted.span().is_none() { substituted = substituted.with_span(owner_span); } @@ -1506,6 +1517,21 @@ impl<'a> FunctionProjectionAnalysis<'a> { if dims.is_empty() { return Ok(Some(self.substitute(expr, scope)?)); } + if let Some((name, subscripts, selection_span)) = indexed_var_selection(expr) + && scope.scalars.contains_key(name.as_str()) + { + return self.project_scoped_selection_value( + name, + subscripts, + ScopedSelectionValueCtx { + result_dims: dims, + flat_index, + scope, + depth: depth + 1, + span: inherited_projection_span(selection_span, owner_span), + }, + ); + } match expr { rumoca_core::Expression::VarRef { name, diff --git a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/dimension_helpers.rs b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/dimension_helpers.rs index 97967b737..77fc77f55 100644 --- a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/dimension_helpers.rs +++ b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/dimension_helpers.rs @@ -299,7 +299,7 @@ pub(super) fn assignment_projection_dims( )), } } - (Some(value_dims), _) if value_dims.is_empty() => Ok(None), + (Some(value_dims), _) if value_dims.is_empty() => Ok(Some(value_dims)), (Some(value_dims), _) => Ok(Some(value_dims)), (None, Some(declared)) => Ok(Some(declared)), (None, None) => Ok(None), @@ -403,6 +403,7 @@ pub(super) fn projection_assignment_target( pub(super) struct FunctionScopeSubstituter<'a> { pub(super) scope: &'a FunctionProjectionScope, + pub(super) materialize_arrays: bool, pub(super) error: Option, } @@ -451,6 +452,34 @@ impl FunctionScopeSubstituter<'_> { } } } + + fn projected_array_binding( + &mut self, + expr: &rumoca_core::Expression, + name: &rumoca_core::Reference, + span: rumoca_core::Span, + ) -> rumoca_core::Expression { + if !self.materialize_arrays { + return expr.clone(); + } + let Some(values) = self.scope.scalars.get(name.as_str()) else { + return expr.clone(); + }; + let Some(dims) = self.scope.dims.get(name.as_str()) else { + self.error = Some(LowerError::contract_violation( + format!( + "projected array `{}` has scalar values but no dimensions", + name.as_str() + ), + span, + )); + return expr.clone(); + }; + projected_array_expression(values, dims, span).unwrap_or_else(|error| { + self.error = Some(error); + expr.clone() + }) + } } impl ExpressionRewriter for FunctionScopeSubstituter<'_> { @@ -469,6 +498,9 @@ impl ExpressionRewriter for FunctionScopeSubstituter<'_> { if !subscripts.is_empty() { return self.walk_expression(expr); } + if self.scope.scalars.contains_key(name.as_str()) { + return self.projected_array_binding(expr, name, *span); + } if let Some(expr) = self.scope.full.get(name.as_str()) { return expr.clone().with_span(*span); } @@ -501,6 +533,25 @@ impl ExpressionRewriter for FunctionScopeSubstituter<'_> { } self.walk_expression(expr) } + + fn walk_function_call_expression( + &mut self, + name: &rumoca_core::Reference, + args: &[rumoca_core::Expression], + is_constructor: bool, + span: rumoca_core::Span, + ) -> rumoca_core::Expression { + let previous = self.materialize_arrays; + self.materialize_arrays = true; + let args = self.rewrite_expressions(args); + self.materialize_arrays = previous; + rumoca_core::Expression::FunctionCall { + name: name.clone(), + args, + is_constructor, + span, + } + } } pub(super) fn scalar_count_for_dims( diff --git a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/dimension_inference.rs b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/dimension_inference.rs index 08c1535d7..c1d151fb2 100644 --- a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/dimension_inference.rs +++ b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/dimension_inference.rs @@ -33,46 +33,42 @@ impl<'a> FunctionProjectionAnalysis<'a> { match expr { rumoca_core::Expression::VarRef { name, subscripts, .. - } if subscripts.is_empty() => { - if let Some(dims) = scope.dims.get(name.as_str()) { - return Ok(Some(copy_projection_dims( - dims, - "projected scope dimension count", - span, - )?)); - } - if let Some(values) = scope.scalars.get(name.as_str()) { - return Ok(Some(match values.len() { - 0 | 1 => Vec::new(), - len => copy_projection_dims( - &[checked_usize_to_i64( - len, - "projected scalar value count", - span, - )?], - "projected scalar dimension count", - span, - )?, - })); - } - if let Some(expr) = scope.full.get(name.as_str()) - && !is_same_plain_var_ref(expr, name.as_str()) - { - return self.expr_dims_with_owner(expr, scope, depth + 1, span); - } - if let Some(variable) = variable_by_name(self.dae_model, name.as_str()) { - return variable_dims_i64(variable, span).map(Some); - } - if scalarized_aggregate_binding(self.dae_model, name, span)?.is_some() { - return Ok(Some(Vec::new())); - } - Ok(None) + } => { + let Some(base_dims) = self.variable_reference_dims(name, scope, depth + 1, span)? + else { + return Ok(None); + }; + self.subscripted_projection_dims(&base_dims, subscripts, scope, depth + 1, span) + .map(Some) } rumoca_core::Expression::Array { elements, is_matrix, .. } => self.array_expression_dims(elements, *is_matrix, scope, depth, span), + rumoca_core::Expression::Range { .. } => { + let count = self.for_index_values(expr, scope, span)?.len(); + copy_projection_dims( + &[checked_usize_to_i64( + count, + "function projection range length", + span, + )?], + "function projection range dimension count", + span, + ) + .map(Some) + } + rumoca_core::Expression::Index { + base, subscripts, .. + } => { + let Some(base_dims) = self.expr_dims_with_owner(base, scope, depth + 1, span)? + else { + return Ok(None); + }; + self.subscripted_projection_dims(&base_dims, subscripts, scope, depth + 1, span) + .map(Some) + } rumoca_core::Expression::Literal { .. } => Ok(Some(Vec::new())), rumoca_core::Expression::Unary { rhs, .. } => { self.expr_dims_with_owner(rhs, scope, depth, span) @@ -112,12 +108,12 @@ impl<'a> FunctionProjectionAnalysis<'a> { name, is_constructor: false, .. - } => self.function_call_expr_dims(name, expr, span, depth), + } => self.function_call_expr_dims(name, expr, scope, span, depth), rumoca_core::Expression::FieldAccess { base, field, .. } => { if let Some(dims) = self.bound_field_access_dims(base, field, scope, span, depth)? { return Ok(Some(dims)); } - self.function_field_access_dims(base, field, span, depth) + self.function_field_access_dims(base, field, scope, span, depth) } rumoca_core::Expression::If { branches, @@ -155,6 +151,157 @@ impl<'a> FunctionProjectionAnalysis<'a> { } } + fn variable_reference_dims( + &self, + name: &rumoca_core::Reference, + scope: &FunctionProjectionScope, + depth: usize, + span: rumoca_core::Span, + ) -> Result>, LowerError> { + if name.as_str() == "time" { + return Ok(Some(Vec::new())); + } + if let Some(dims) = scope.dims.get(name.as_str()) { + return copy_projection_dims(dims, "projected scope dimension count", span).map(Some); + } + if let Some(values) = scope.scalars.get(name.as_str()) { + return Self::projected_scalar_values_dims(values, span).map(Some); + } + if let Some(expr) = scope.full.get(name.as_str()) + && !is_same_plain_var_ref(expr, name.as_str()) + { + return self.expr_dims_with_owner(expr, scope, depth + 1, span); + } + if let Some(variable) = variable_by_name(self.dae_model, name.as_str()) { + return variable_dims_i64(variable, span).map(Some); + } + if scalarized_aggregate_binding(self.dae_model, name, span)?.is_some() { + return Ok(Some(Vec::new())); + } + Ok(None) + } + + pub(super) fn resolved_function_param_dims( + &self, + param: &rumoca_core::FunctionParam, + scope: &FunctionProjectionScope, + _depth: usize, + span: rumoca_core::Span, + ) -> Result>, LowerError> { + if param.dims.is_empty() || param.dims.iter().all(|dim| *dim > 0) { + return copy_projection_dims( + ¶m.dims, + "resolved function parameter dimension count", + span, + ) + .map(Some); + } + if param.shape_expr.len() != param.dims.len() { + return Ok(None); + } + let mut dims = projection_vec_with_capacity( + param.shape_expr.len(), + "resolved function parameter dimension count", + span, + )?; + for subscript in ¶m.shape_expr { + let dim = match subscript { + rumoca_core::Subscript::Index { value, .. } if *value >= 0 => *value, + rumoca_core::Subscript::Expr { + expr, + span: subscript_span, + } => self.compile_time_int( + expr, + scope, + inherited_projection_span(*subscript_span, span), + )?, + _ => return Ok(None), + }; + if dim < 0 { + return Err(LowerError::contract_violation( + format!( + "function parameter `{}` has negative dimension {dim}", + param.name + ), + span, + )); + } + dims.push(dim); + } + Ok(Some(dims)) + } + + pub(super) fn subscripted_projection_dims( + &self, + base_dims: &[i64], + subscripts: &[rumoca_core::Subscript], + scope: &FunctionProjectionScope, + depth: usize, + span: rumoca_core::Span, + ) -> Result, LowerError> { + if subscripts.len() > base_dims.len() { + return Err(LowerError::contract_violation( + format!( + "array selection has {} subscripts for dimensions {}", + subscripts.len(), + crate::lower::helpers::format_i64_dims(base_dims) + ), + span, + )); + } + let mut selected = projection_vec_with_capacity( + base_dims.len(), + "selected expression dimension count", + span, + )?; + for (axis, subscript) in subscripts.iter().enumerate() { + if let Some(dim) = self.projected_subscript_dimension( + base_dims[axis], + subscript, + scope, + depth + 1, + span, + )? { + selected.push(dim); + } + } + selected.extend(base_dims.iter().skip(subscripts.len()).copied()); + Ok(selected) + } + + fn projected_subscript_dimension( + &self, + base_dim: i64, + subscript: &rumoca_core::Subscript, + scope: &FunctionProjectionScope, + depth: usize, + span: rumoca_core::Span, + ) -> Result, LowerError> { + match subscript { + rumoca_core::Subscript::Colon { .. } => Ok(Some(base_dim)), + rumoca_core::Subscript::Index { + value, + span: subscript_span, + } => validate_scalar_projection_subscript( + *value, + base_dim, + inherited_projection_span(*subscript_span, span), + ), + rumoca_core::Subscript::Expr { + expr, + span: subscript_span, + } => { + let span = inherited_projection_span(*subscript_span, span); + let dims = self + .expr_dims_with_owner(expr, scope, depth + 1, span)? + .ok_or_else(|| { + unsupported_at("array subscript has unknown dimensions", span) + })?; + projection_subscript_result_dimension(&dims, span) + } + } + } + fn if_expression_dims( &self, branches: &[(rumoca_core::Expression, rumoca_core::Expression)], @@ -216,10 +363,72 @@ impl<'a> FunctionProjectionAnalysis<'a> { rumoca_core::BuiltinFunction::Symmetric => { self.symmetric_builtin_dims(args, scope, depth, span) } + rumoca_core::BuiltinFunction::Size => self.size_builtin_dims(args, scope, depth, span), + rumoca_core::BuiltinFunction::Ndims + | rumoca_core::BuiltinFunction::Scalar + | rumoca_core::BuiltinFunction::Sum + | rumoca_core::BuiltinFunction::Product + | rumoca_core::BuiltinFunction::Div + | rumoca_core::BuiltinFunction::Mod + | rumoca_core::BuiltinFunction::Rem + | rumoca_core::BuiltinFunction::Min + | rumoca_core::BuiltinFunction::Max + | rumoca_core::BuiltinFunction::Atan2 + | rumoca_core::BuiltinFunction::Initial + | rumoca_core::BuiltinFunction::Terminal + | rumoca_core::BuiltinFunction::Edge + | rumoca_core::BuiltinFunction::Change + | rumoca_core::BuiltinFunction::Reinit => Ok(Some(Vec::new())), + rumoca_core::BuiltinFunction::Smooth => self.argument_dims(args, 1, scope, depth, span), + function if projection_builtin_preserves_first_arg(*function) => { + self.argument_dims(args, 0, scope, depth, span) + } _ => Ok(None), } } + fn argument_dims( + &self, + args: &[rumoca_core::Expression], + index: usize, + scope: &FunctionProjectionScope, + depth: usize, + span: rumoca_core::Span, + ) -> Result>, LowerError> { + let Some(arg) = args.get(index) else { + return Ok(None); + }; + self.expr_dims_with_owner(arg, scope, depth + 1, span) + } + + fn size_builtin_dims( + &self, + args: &[rumoca_core::Expression], + scope: &FunctionProjectionScope, + depth: usize, + span: rumoca_core::Span, + ) -> Result>, LowerError> { + if args.len() > 1 { + return Ok(Some(Vec::new())); + } + let Some(array) = args.first() else { + return Ok(None); + }; + let Some(array_dims) = self.expr_dims_with_owner(array, scope, depth + 1, span)? else { + return Ok(None); + }; + copy_projection_dims( + &[checked_usize_to_i64( + array_dims.len(), + "size() result length", + span, + )?], + "size() result dimension count", + span, + ) + .map(Some) + } + fn dimension_argument_builtin_dims( &self, args: &[rumoca_core::Expression], @@ -352,13 +561,15 @@ impl<'a> FunctionProjectionAnalysis<'a> { &self, base: &rumoca_core::Expression, field: &str, + scope: &FunctionProjectionScope, span: rumoca_core::Span, depth: usize, ) -> Result>, LowerError> { if !matches!(base, rumoca_core::Expression::FunctionCall { .. }) { return Ok(None); } - let Some(outputs) = self.function_call_outputs_with_owner(base, depth + 1, span)? else { + let call = self.substitute_for_call(base, scope)?; + let Some(outputs) = self.function_call_outputs_with_owner(&call, depth + 1, span)? else { return Ok(None); }; projected_field_output_dims(&outputs, field, span) @@ -414,6 +625,7 @@ impl<'a> FunctionProjectionAnalysis<'a> { &self, name: &rumoca_core::Reference, expr: &rumoca_core::Expression, + scope: &FunctionProjectionScope, owner_span: rumoca_core::Span, depth: usize, ) -> Result>, LowerError> { @@ -424,13 +636,51 @@ impl<'a> FunctionProjectionAnalysis<'a> { if let Some(dims) = self.declared_function_output_dims(name, call_span)? { return Ok(Some(dims)); } - let Some(outputs) = self.function_call_outputs_with_owner(expr, depth + 1, call_span)? + let call = self.substitute_for_call(expr, scope)?; + let Some(outputs) = self.function_call_outputs_with_owner(&call, depth + 1, call_span)? else { return Ok(None); }; + if let Some(dims) = self.projected_call_expr_dims(name, &outputs, call_span)? { + return Ok(Some(dims)); + } function_outputs_dims(outputs.len(), call_span).map(Some) } + fn projected_call_expr_dims( + &self, + requested: &rumoca_core::Reference, + outputs: &[ProjectedFunctionOutput], + span: rumoca_core::Span, + ) -> Result>, LowerError> { + let Some((_, function)) = + resolve_function_reference(&self.dae_model.symbols.functions, requested) + else { + return Ok(None); + }; + let projection = output_projection_suffix(function, requested); + let output_name = projection + .as_ref() + .map(|projection| projection.output_name.as_str()) + .or_else(|| function.outputs.first().map(|output| output.name.as_str())); + let Some(output_name) = output_name else { + return Ok(None); + }; + let field_path = projection + .as_ref() + .map(|projection| projection.output_fields.as_slice()) + .unwrap_or_default(); + let selector_indices = outputs + .iter() + .filter(|output| { + output.output_name.as_deref() == Some(output_name) + && output.field_path == field_path + }) + .map(|output| output.selector_indices.as_slice()) + .collect::>(); + selector_dims_from_indices(&selector_indices, span) + } + fn declared_function_output_dims( &self, name: &rumoca_core::Reference, @@ -577,3 +827,62 @@ impl<'a> FunctionProjectionAnalysis<'a> { } } } + +fn projection_builtin_preserves_first_arg(function: rumoca_core::BuiltinFunction) -> bool { + matches!( + function, + rumoca_core::BuiltinFunction::Der + | rumoca_core::BuiltinFunction::Pre + | rumoca_core::BuiltinFunction::Abs + | rumoca_core::BuiltinFunction::Sign + | rumoca_core::BuiltinFunction::Sqrt + | rumoca_core::BuiltinFunction::Floor + | rumoca_core::BuiltinFunction::Ceil + | rumoca_core::BuiltinFunction::Integer + | rumoca_core::BuiltinFunction::Sin + | rumoca_core::BuiltinFunction::Cos + | rumoca_core::BuiltinFunction::Tan + | rumoca_core::BuiltinFunction::Asin + | rumoca_core::BuiltinFunction::Acos + | rumoca_core::BuiltinFunction::Atan + | rumoca_core::BuiltinFunction::Sinh + | rumoca_core::BuiltinFunction::Cosh + | rumoca_core::BuiltinFunction::Tanh + | rumoca_core::BuiltinFunction::Exp + | rumoca_core::BuiltinFunction::Log + | rumoca_core::BuiltinFunction::Log10 + | rumoca_core::BuiltinFunction::Sample + | rumoca_core::BuiltinFunction::NoEvent + | rumoca_core::BuiltinFunction::Homotopy + | rumoca_core::BuiltinFunction::SemiLinear + | rumoca_core::BuiltinFunction::Delay + ) +} + +fn validate_scalar_projection_subscript( + value: i64, + dimension: i64, + span: rumoca_core::Span, +) -> Result, LowerError> { + if value <= 0 || value > dimension { + return Err(unsupported_at( + format!("array subscript {value} is outside dimension {dimension}"), + span, + )); + } + Ok(None) +} + +fn projection_subscript_result_dimension( + dimensions: &[i64], + span: rumoca_core::Span, +) -> Result, LowerError> { + match dimensions { + [] => Ok(None), + [dimension] => Ok(Some(*dimension)), + _ => Err(unsupported_at( + "array subscript expression must be scalar or one-dimensional", + span, + )), + } +} diff --git a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/loop_projection.rs b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/loop_projection.rs index 4a0bc5608..cbc0c96ba 100644 --- a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/loop_projection.rs +++ b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/loop_projection.rs @@ -97,7 +97,7 @@ impl FunctionProjectionAnalysis<'_> { } } - fn for_index_values( + pub(super) fn for_index_values( &self, range: &rumoca_core::Expression, scope: &FunctionProjectionScope, diff --git a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/projection_helpers.rs b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/projection_helpers.rs index e21e64241..4829c7eb9 100644 --- a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/projection_helpers.rs +++ b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/projection_helpers.rs @@ -74,6 +74,22 @@ pub(super) struct ScalarSelectionCtx<'a> { pub(super) depth: usize, } +pub(super) struct ScopedSelectionValueCtx<'a> { + pub(super) result_dims: &'a [i64], + pub(super) flat_index: usize, + pub(super) scope: &'a FunctionProjectionScope, + pub(super) depth: usize, + pub(super) span: rumoca_core::Span, +} + +pub(super) struct ScopedSubscriptProjectionCtx<'a> { + pub(super) result_indices: &'a [usize], + pub(super) result_axis: &'a mut usize, + pub(super) scope: &'a FunctionProjectionScope, + pub(super) depth: usize, + pub(super) span: rumoca_core::Span, +} + struct FunctionScopeRefChecker<'a> { scoped_names: &'a [&'a str], } diff --git a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/scope_initialization.rs b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/scope_initialization.rs new file mode 100644 index 000000000..e58c6bc9a --- /dev/null +++ b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/scope_initialization.rs @@ -0,0 +1,85 @@ +use super::*; + +impl FunctionProjectionAnalysis<'_> { + pub(super) fn initialize_projected_declared_arrays( + &self, + function: &rumoca_core::Function, + scope: &mut FunctionProjectionScope, + depth: usize, + owner_span: rumoca_core::Span, + ) -> Result<(), LowerError> { + for param in function.outputs.iter().chain(function.locals.iter()) { + self.initialize_projected_declared_param(param, scope, depth + 1, owner_span)?; + self.initialize_projected_record_fields(param, scope, depth + 1, owner_span)?; + } + Ok(()) + } + + fn initialize_projected_declared_param( + &self, + param: &rumoca_core::FunctionParam, + scope: &mut FunctionProjectionScope, + depth: usize, + owner_span: rumoca_core::Span, + ) -> Result<(), LowerError> { + if self.initialize_declared_default(param, scope, depth + 1, owner_span)? { + return Ok(()); + } + let param_span = inherited_projection_span(param.span, owner_span); + let Some(dims) = self.resolved_function_param_dims(param, scope, depth + 1, param_span)? + else { + return Ok(()); + }; + if dims.is_empty() || scope.scalars.contains_key(param.name.as_str()) { + return Ok(()); + } + let count = scalar_count_for_dims(&dims, "function declared array dimensions", param_span)?; + let dims = copy_projection_dims( + &dims, + "projected declared array dimension count", + param_span, + )?; + scope.dims.insert(param.name.clone(), dims); + let mut scalars = projection_vec_with_capacity( + count, + "projected declared array scalar count", + param_span, + )?; + for _ in 0..count { + scalars.push(rumoca_core::Expression::Empty { span: param_span }); + } + scope.scalars.insert(param.name.clone(), scalars); + Ok(()) + } + + fn initialize_projected_record_fields( + &self, + param: &rumoca_core::FunctionParam, + scope: &mut FunctionProjectionScope, + depth: usize, + owner_span: rumoca_core::Span, + ) -> Result<(), LowerError> { + if param.type_class != Some(rumoca_core::ClassType::Record) + || depth > crate::lower::MAX_FUNCTION_INLINE_DEPTH + { + return Ok(()); + } + let Some(type_def_id) = param.type_def_id else { + return Ok(()); + }; + let Ok(constructor) = rumoca_core::resolve_record_constructor( + self.dae_model.symbols.functions.values(), + ¶m.type_name, + type_def_id, + ) else { + return Ok(()); + }; + for field in &constructor.inputs { + let mut scoped_field = field.clone(); + scoped_field.name = format!("{}.{}", param.name, field.name); + self.initialize_projected_declared_param(&scoped_field, scope, depth + 1, owner_span)?; + self.initialize_projected_record_fields(&scoped_field, scope, depth + 1, owner_span)?; + } + Ok(()) + } +} diff --git a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/tensor_projection.rs b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/tensor_projection.rs index ff5bbb4e4..150c86cb3 100644 --- a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/tensor_projection.rs +++ b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/tensor_projection.rs @@ -1,6 +1,128 @@ use super::*; impl<'a> FunctionProjectionAnalysis<'a> { + pub(super) fn project_scoped_selection_value( + &self, + name: &rumoca_core::Reference, + subscripts: &[rumoca_core::Subscript], + ctx: ScopedSelectionValueCtx<'_>, + ) -> Result, LowerError> { + let ScopedSelectionValueCtx { + result_dims, + flat_index, + scope, + depth, + span, + } = ctx; + let Some(base_dims) = scope.dims.get(name.as_str()) else { + return Ok(None); + }; + let Some(values) = scope.scalars.get(name.as_str()) else { + return Ok(None); + }; + let result_indices = required_flat_index_to_subscripts(result_dims, flat_index, span)?; + let mut result_axis = 0usize; + let mut scalar_subscripts = projection_vec_with_capacity( + base_dims.len(), + "projected selected expression subscript count", + span, + )?; + for axis in 0..base_dims.len() { + let projected = self.project_scoped_selection_subscript( + subscripts.get(axis), + ScopedSubscriptProjectionCtx { + result_indices: &result_indices, + result_axis: &mut result_axis, + scope, + depth, + span, + }, + )?; + let Some(projected) = projected else { + return Ok(None); + }; + scalar_subscripts.push(projected); + } + if result_axis != result_indices.len() { + return Err(LowerError::contract_violation( + "projected selected expression did not consume its result dimensions", + span, + )); + } + projected_scalar_selection( + ScalarSelectionCtx { + name: name.as_str(), + subscripts: &scalar_subscripts, + dims: base_dims, + values, + span, + depth, + }, + self, + scope, + ) + .map(Some) + } + + fn project_scoped_selection_subscript( + &self, + subscript: Option<&rumoca_core::Subscript>, + ctx: ScopedSubscriptProjectionCtx<'_>, + ) -> Result, LowerError> { + match subscript { + None | Some(rumoca_core::Subscript::Colon { .. }) => { + result_axis_subscript(ctx.result_indices, ctx.result_axis, ctx.span).map(Some) + } + Some(subscript @ rumoca_core::Subscript::Index { .. }) => Ok(Some(subscript.clone())), + Some(rumoca_core::Subscript::Expr { expr, span }) => { + self.project_scoped_expression_subscript(expr, *span, ctx) + } + } + } + + fn project_scoped_expression_subscript( + &self, + expr: &rumoca_core::Expression, + source_span: rumoca_core::Span, + ctx: ScopedSubscriptProjectionCtx<'_>, + ) -> Result, LowerError> { + let span = inherited_projection_span(source_span, ctx.span); + let Some(index_dims) = self.expr_dims_with_owner(expr, ctx.scope, ctx.depth + 1, span)? + else { + return Ok(None); + }; + if index_dims.is_empty() { + return Ok(Some(rumoca_core::Subscript::Expr { + expr: Box::new(expr.clone()), + span, + })); + } + let [index_width] = index_dims.as_slice() else { + return Err(unsupported_at( + "array subscript expression must be scalar or one-dimensional", + span, + )); + }; + let coordinate = result_axis_coordinate(ctx.result_indices, ctx.result_axis, span)?; + validate_projected_subscript_coordinate(coordinate, *index_width, span)?; + let projected = self + .project_value( + expr, + &index_dims, + coordinate - 1, + ctx.scope, + ctx.depth + 1, + span, + )? + .ok_or_else(|| { + unsupported_at("array subscript expression could not be projected", span) + })?; + Ok(Some(rumoca_core::Subscript::Expr { + expr: Box::new(projected), + span, + })) + } + pub(super) fn project_function_call_value( &self, expr: &rumoca_core::Expression, @@ -9,7 +131,7 @@ impl<'a> FunctionProjectionAnalysis<'a> { depth: usize, owner_span: rumoca_core::Span, ) -> Result, LowerError> { - let mut call = self.substitute(expr, scope)?; + let mut call = self.substitute_for_call(expr, scope)?; if call.span().is_none() { call = call.with_span(owner_span); } @@ -282,3 +404,53 @@ impl<'a> FunctionProjectionAnalysis<'a> { Ok(Some(sum_expressions(terms, ctx.span))) } } + +fn result_axis_subscript( + result_indices: &[usize], + result_axis: &mut usize, + span: rumoca_core::Span, +) -> Result { + let coordinate = result_axis_coordinate(result_indices, result_axis, span)?; + checked_generated_subscript_from_usize( + coordinate, + span, + "projected selected expression subscript", + ) +} + +fn result_axis_coordinate( + result_indices: &[usize], + result_axis: &mut usize, + span: rumoca_core::Span, +) -> Result { + let coordinate = result_indices.get(*result_axis).copied().ok_or_else(|| { + LowerError::contract_violation( + "projected selected expression is missing a result dimension", + span, + ) + })?; + *result_axis = result_axis.checked_add(1).ok_or_else(|| { + LowerError::contract_violation( + "projected selected expression dimension index overflows host range", + span, + ) + })?; + Ok(coordinate) +} + +fn validate_projected_subscript_coordinate( + coordinate: usize, + dimension: i64, + span: rumoca_core::Span, +) -> Result<(), LowerError> { + if i64::try_from(coordinate) + .ok() + .is_some_and(|index| index <= dimension) + { + return Ok(()); + } + Err(LowerError::contract_violation( + "projected array subscript coordinate exceeds its dimension", + span, + )) +} diff --git a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/tests.rs b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/tests.rs index 20b930a77..7c45a82c6 100644 --- a/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/tests.rs +++ b/crates/rumoca-phase-solve/src/lower/derivative_rhs/function_projection/tests.rs @@ -190,6 +190,50 @@ fn size_uses_resolved_callee_dims_after_following_full_alias() -> Result<(), Low Ok(()) } +#[test] +fn size_with_dimension_argument_has_scalar_projection_dimensions() { + let dae_model = dae::Dae::default(); + let structural_bindings = IndexMap::new(); + let analysis = FunctionProjectionAnalysis::new(&dae_model, &structural_bindings); + let mut scope = FunctionProjectionScope::default(); + scope.dims.insert("stage".to_string(), vec![3, 8]); + let size = builtin( + rumoca_core::BuiltinFunction::Size, + vec![ + local_var("stage"), + rumoca_core::Expression::Literal { + value: Literal::Integer(2), + span: test_span(), + }, + ], + ); + + assert_eq!( + analysis.expr_dims(&size, &scope, 0, test_span()), + Ok(Some(Vec::new())) + ); +} + +#[test] +fn colon_selection_retains_selected_axis_extent() { + let dae_model = dae::Dae::default(); + let structural_bindings = IndexMap::new(); + let analysis = FunctionProjectionAnalysis::new(&dae_model, &structural_bindings); + let scope = FunctionProjectionScope::default(); + let subscripts = vec![ + rumoca_core::Subscript::Colon { span: test_span() }, + rumoca_core::Subscript::Index { + value: 2, + span: test_span(), + }, + ]; + + assert_eq!( + analysis.subscripted_projection_dims(&[3, 8], &subscripts, &scope, 0, test_span()), + Ok(vec![3]) + ); +} + #[test] fn static_colon_assignment_projects_array_into_matrix_column() -> Result<(), LowerError> { let dae_model = dae::Dae::default(); diff --git a/crates/rumoca-phase-solve/src/lower/helpers.rs b/crates/rumoca-phase-solve/src/lower/helpers.rs index 5d4756d0e..01752ee97 100644 --- a/crates/rumoca-phase-solve/src/lower/helpers.rs +++ b/crates/rumoca-phase-solve/src/lower/helpers.rs @@ -1115,6 +1115,22 @@ pub(super) struct AssignmentTarget { pub indices: Option>, } +pub(super) fn component_reference_has_slice_subscript( + comp: &rumoca_core::ComponentReference, +) -> bool { + comp.parts + .iter() + .flat_map(|part| &part.subs) + .any(|subscript| { + matches!(subscript, rumoca_core::Subscript::Colon { .. }) + || matches!( + subscript, + rumoca_core::Subscript::Expr { expr, .. } + if matches!(expr.as_ref(), rumoca_core::Expression::Range { .. }) + ) + }) +} + pub(super) fn assignment_target( comp: &rumoca_core::ComponentReference, const_scope: &IndexMap, diff --git a/crates/rumoca-phase-solve/src/lower/statements/statement_support.rs b/crates/rumoca-phase-solve/src/lower/statements/statement_support.rs index dbe40c1e1..135eca169 100644 --- a/crates/rumoca-phase-solve/src/lower/statements/statement_support.rs +++ b/crates/rumoca-phase-solve/src/lower/statements/statement_support.rs @@ -230,19 +230,3 @@ pub(super) fn concrete_usize_dims_width(dims: &[usize]) -> Option { acc.checked_mul(*dim) }) } - -pub(super) fn component_reference_has_slice_subscript( - comp: &rumoca_core::ComponentReference, -) -> bool { - comp.parts - .iter() - .flat_map(|part| &part.subs) - .any(|subscript| { - matches!(subscript, rumoca_core::Subscript::Colon { .. }) - || matches!( - subscript, - rumoca_core::Subscript::Expr { expr, .. } - if matches!(expr.as_ref(), rumoca_core::Expression::Range { .. }) - ) - }) -} diff --git a/crates/rumoca/tests/function_projection_array_shape_test.rs b/crates/rumoca/tests/function_projection_array_shape_test.rs new file mode 100644 index 000000000..d88f13bab --- /dev/null +++ b/crates/rumoca/tests/function_projection_array_shape_test.rs @@ -0,0 +1,128 @@ +//! Regressions for array-shape preservation while projecting Modelica functions. + +use rumoca::Compiler; +use rumoca_sim::{EvalAtReport, SimOptions, eval_dae_at}; + +const NESTED_ARRAY_CALL_MODEL: &str = r#" +within; +function makeControlPoints + input Real u; + output Real controlPoint[2, 3]; +algorithm + controlPoint := [u, 2.0, 3.0; 4.0, 5.0, 6.0]; +end makeControlPoints; + +function evaluateGeneric + input Real controlPoint[:, :]; + output Real value[size(controlPoint, 1)]; +protected + Real stage[size(controlPoint, 1), size(controlPoint, 2)]; +algorithm + stage := controlPoint; + for pointIndex in 1:size(controlPoint, 2) - 1 loop + stage[:, pointIndex] := stage[:, pointIndex] + stage[:, pointIndex + 1]; + end for; + value := stage[:, 1]; +end evaluateGeneric; + +function evaluateWrapper + input Real controlPoint[2, 3]; + output Real value[2]; +algorithm + value := evaluateGeneric(controlPoint); +end evaluateWrapper; + +model NestedArrayCall + Real x[2](each start = 0.0, each fixed = true); +equation + der(x) = evaluateWrapper(makeControlPoints(time)); +end NestedArrayCall; +"#; + +const RECORD_RESULT_MODEL: &str = r#" +within; +record ProjectionResult + Real matrix[3, 3]; + Real scalar; +end ProjectionResult; + +function makeProjectionResult + input Real u; + output ProjectionResult result; +protected + Real column[3]; +algorithm + column := {u + 1.0, u + 2.0, u + 3.0}; + result.matrix[:, 1] := column; + result.matrix[:, 2] := {4.0, 5.0, 6.0}; + result.matrix[:, 3] := {7.0, 8.0, 9.0}; + result.scalar := u + 6.0; +end makeProjectionResult; + +model ObserveRecordScalar + ProjectionResult reference; + output Real observed; +equation + reference = makeProjectionResult(time); + observed = reference.scalar; +end ObserveRecordScalar; +"#; + +fn slot_value(report: &EvalAtReport, name: &str) -> f64 { + report + .solver_y + .iter() + .chain(report.derivatives.iter()) + .find(|slot| slot.name == name) + .unwrap_or_else(|| { + panic!( + "missing `{name}`; have solver values {:?} and derivatives {:?}", + report + .solver_y + .iter() + .map(|slot| slot.name.as_str()) + .collect::>(), + report + .derivatives + .iter() + .map(|slot| slot.name.as_str()) + .collect::>() + ) + }) + .value +} + +#[test] +fn nested_call_preserves_function_produced_array_shape() { + let compiled = Compiler::new() + .model("NestedArrayCall") + .compile_str(NESTED_ARRAY_CALL_MODEL, "NestedArrayCall.mo") + .expect("nested dimension-generic array functions should compile"); + + let probe = eval_dae_at(&compiled.dae, &SimOptions::default(), &[], 0.0) + .expect("nested dimension-generic array functions should lower and evaluate"); + assert!( + probe.report.error.is_none(), + "eval error: {:?}", + probe.report.error + ); + assert_eq!(slot_value(&probe.report, "der(x[1])"), 2.0); + assert_eq!(slot_value(&probe.report, "der(x[2])"), 9.0); +} + +#[test] +fn observed_scalar_field_lowers_record_function_matrix_slice_assignments() { + let compiled = Compiler::new() + .model("ObserveRecordScalar") + .compile_str(RECORD_RESULT_MODEL, "ObserveRecordScalar.mo") + .expect("record-valued function with matrix slice assignments should compile"); + + let probe = eval_dae_at(&compiled.dae, &SimOptions::default(), &[], 0.0) + .expect("observed record field should lower and evaluate"); + assert!( + probe.report.error.is_none(), + "eval error: {:?}", + probe.report.error + ); + assert_eq!(slot_value(&probe.report, "observed"), 6.0); +} diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index efd72aed4..6f7683e1c 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1732,6 +1732,17 @@ fn ensure_wasm_deps(root: &Path) -> Result<()> { "WASM tooling ready: wasm-bindgen-cli {wasm_bindgen_version}, wasm-pack {wasm_pack_version}" ); + let mut sysroot = Command::new("rustc"); + sysroot.arg("--print").arg("sysroot").current_dir(root); + let sysroot = run_capture(sysroot)?; + if rust_target_is_installed(Path::new(sysroot.trim()), "wasm32-unknown-unknown") { + return Ok(()); + } + ensure!( + command_exists("rustup"), + "the active Rust toolchain does not include wasm32-unknown-unknown and rustup is unavailable" + ); + let mut list = Command::new("rustup"); list.arg("target") .arg("list") @@ -1750,6 +1761,15 @@ fn ensure_wasm_deps(root: &Path) -> Result<()> { Ok(()) } +fn rust_target_is_installed(sysroot: &Path, target: &str) -> bool { + sysroot + .join("lib") + .join("rustlib") + .join(target) + .join("lib") + .is_dir() +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum WasmBuildProfile { Dev, diff --git a/crates/xtask/src/main_tests.rs b/crates/xtask/src/main_tests.rs index 53e35c8be..f8cc92603 100644 --- a/crates/xtask/src/main_tests.rs +++ b/crates/xtask/src/main_tests.rs @@ -2,7 +2,7 @@ use super::{ Cli, Commands, CoverageCommand, PlaygroundCommand, PythonCommand, RepoCliCommand, RepoCommand, RepoCompletionsCommand, RepoGraphCommand, RepoHooksCommand, RepoPolicyCommand, RepoUbuntuCommand, VscodeCommand, WebCommand, classify_candidate, - docs_wasm_package_is_up_to_date, is_line_count_excluded_rust_file, + docs_wasm_package_is_up_to_date, is_line_count_excluded_rust_file, rust_target_is_installed, }; use crate::docs_cmd::{DocsBook, DocsCommand}; use crate::modelica_dependency_cache::{CmmCommand, ModelicaDepsCommand}; @@ -988,3 +988,19 @@ fn docs_wasm_freshness_gate_rebuilds_when_no_artifact_present() { std::fs::write(pkg.join("rumoca_bind_wasm.js"), "// glue").expect("write glue"); assert!(!docs_wasm_package_is_up_to_date(root, &pkg).expect("freshness check")); } + +#[test] +fn rust_target_detection_uses_active_toolchain_sysroot() { + let temp = tempfile::tempdir().expect("temp dir"); + assert!(!rust_target_is_installed( + temp.path(), + "wasm32-unknown-unknown" + )); + + let target_lib = temp.path().join("lib/rustlib/wasm32-unknown-unknown/lib"); + std::fs::create_dir_all(target_lib).expect("create target lib"); + assert!(rust_target_is_installed( + temp.path(), + "wasm32-unknown-unknown" + )); +} diff --git a/flake.nix b/flake.nix index 25008971f..79077ce39 100644 --- a/flake.nix +++ b/flake.nix @@ -38,6 +38,7 @@ craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; ciJulia = pkgs.julia_111; ciPython = pkgs.python312.withPackages (ps: [ + ps.casadi ps.ipython ps.numpy ps.pandas @@ -245,11 +246,16 @@ ++ [ ciPython pkgs.binaryen + pkgs.cargo-llvm-cov + pkgs.libxml2 pkgs.maturin pkgs.mdbook pkgs.nodejs_22 pkgs.wasm-pack ]; + shellHook = '' + export PATH="''${CARGO_HOME:-$HOME/.cargo}/bin:$PATH" + ''; LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath ( [ diff --git a/packages/vscode/package-lock.json b/packages/vscode/package-lock.json index a41ad0da1..f3b188fa9 100644 --- a/packages/vscode/package-lock.json +++ b/packages/vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "rumoca-modelica", - "version": "0.9.13", + "version": "0.9.20", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rumoca-modelica", - "version": "0.9.13", + "version": "0.9.20", "license": "Apache-2.0", "dependencies": { "three": "^0.160.0", @@ -1668,9 +1668,9 @@ ] }, "node_modules/@vscode/vsce/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1892,15 +1892,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -2869,9 +2860,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "dev": true, "funding": [ { @@ -4079,16 +4070,16 @@ } }, "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/minimist": { @@ -5742,6 +5733,15 @@ "vscode": "^1.82.0" } }, + "node_modules/vscode-languageclient/node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/vscode-languageclient/node_modules/minimatch": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", diff --git a/packages/vscode/package.json b/packages/vscode/package.json index 80beb0cab..83cbe6cdc 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -352,6 +352,18 @@ "overrides": { "@vscode/vsce": { "glob": "^13.0.0" + }, + "minimatch@3.1.5": { + "brace-expansion": "1.1.16" + }, + "minimatch@5.1.9": { + "brace-expansion": "2.1.2" + }, + "minimatch@10.2.4": { + "brace-expansion": "5.0.8" + }, + "fast-uri@3.1.2": { + ".": "3.1.4" } } } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 02c2f62e1..c8d7e2b31 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # See: https://github.com/RReverser/wasm-bindgen-rayon [toolchain] channel = "nightly-2026-02-27" -components = ["rust-src"] +components = ["llvm-tools-preview", "rust-src"] targets = ["wasm32-unknown-unknown"]