diff --git a/src/macros.jl b/src/macros.jl index 209c3ed3f41..c9749d84cc6 100644 --- a/src/macros.jl +++ b/src/macros.jl @@ -253,6 +253,16 @@ function _rewrite_expression(expr::Expr) new_expr = MacroTools.postwalk(_rewrite_to_jump_logic, expr) new_aff, parse_aff = _MA.rewrite(new_expr; move_factors_into_sums = false) ret = gensym() + has_copy_if_mutable = Ref(false) + MacroTools.postwalk(parse_aff) do x + if x === MutableArithmetics.copy_if_mutable + has_copy_if_mutable[] = true + end + return x + end + if !has_copy_if_mutable[] + new_aff = :($_MA.copy_if_mutable($new_aff)) + end code = quote $parse_aff $ret = $flatten!($new_aff) diff --git a/src/macros/@constraint.jl b/src/macros/@constraint.jl index f3dcfe27551..f1f6d289ecd 100644 --- a/src/macros/@constraint.jl +++ b/src/macros/@constraint.jl @@ -1020,6 +1020,9 @@ function _clear_constant!(α::Number) return zero(α), α end +# !!! warning +# This method assumes that we can mutate `expr`. Ensure that this is the +# case upstream of this call site. function build_constraint( ::Function, expr::Union{Number,GenericAffExpr,GenericQuadExpr}, diff --git a/test/test_macros.jl b/test/test_macros.jl index 3ec99b5f498..d923dfedacf 100644 --- a/test/test_macros.jl +++ b/test/test_macros.jl @@ -2487,4 +2487,31 @@ function test_array_scalar_sets() return end +function test_do_not_mutate_expression_double_sided_comparison() + model = Model() + @variable(model, x) + @expression(model, a[1:1], x + 1) + @constraint(model, -1 <= a[1] <= 1) + @test isequal_canonical(a[1], x + 1) + return +end + +function test_do_not_mutate_expression_single_sided_comparison() + model = Model() + @variable(model, x) + @expression(model, a[1:1], x + 1) + @constraint(model, a[1] >= 1) + @test isequal_canonical(a[1], x + 1) + return +end + +function test_do_not_mutate_expression_in_set() + model = Model() + @variable(model, x) + @expression(model, a[1:1], x + 1) + @constraint(model, a[1] in MOI.Interval(-1, 1)) + @test isequal_canonical(a[1], x + 1) + return +end + end # module