# Constant propagation and inference of constructed types

**URL:** <https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279>\
**Category:** Internals & Design\
**Tags:** question, inference, constant-propagation\
**Created:** [March 20, 2020, 9:14pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279 "2020-03-20T21:14:28Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)\
**Post date:** [March 20, 2020, 9:14pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/1 "2020-03-20T21:14:29Z")

</div>

Context: I had a parameterized struct type with convenience constructors that input some of the parameters as arguments. Based on previous experience, I expected that if such constructors are provided with constant values for those arguments, the constants will be propagated and the type of the constructed object will be inferred. To my surprise it was not, even though that approach had generally worked for me previously.

Here’s an illustration of how constant propagation sometimes works and sometimes doesn’t, even for similar-looking code. First, a couple of cases for which constant propagation does work:

```julia
struct F1{T} end
f1(T) = F1{T}()
g1_float() = f1(Float64)
g1_tuple() = f1((1,2,3))

julia> @code_warntype g1_float()
Variables
  #self#::Core.Compiler.Const(g1_float, false)
Body::F1{Float64}
1 ─ %1 = Main.f1(Main.Float64)::Core.Compiler.Const(F1{Float64}(), false)
└── return %1

julia> @code_warntype g1_tuple()
Variables
  #self#::Core.Compiler.Const(g1_tuple, false)
Body::F1{(1, 2, 3)}
1 ─ %1 = Core.tuple(1, 2, 3)::Core.Compiler.Const((1, 2, 3), false)
│ %2 = Main.f1(%1)::Core.Compiler.Const(F1{(1, 2, 3)}(), false)
└── return %2

```

So far so good – constant types and tuples can be propagated to form types. Now, we add a second type parameter:

```julia
struct F2{T,X} end
f2(T, x) = F2{T,typeof(x)}()
g2_float(x) = f2(Float64, x)
g2_tuple(x) = f2((1,2,3), x)

julia> @code_warntype g2_float('a')
Variables
  #self#::Core.Compiler.Const(g2_float, false)
  x::Char
Body::F2{Float64,Char}
1 ─ %1 = Main.f2(Main.Float64, x)::Core.Compiler.Const(F2{Float64,Char}(), false)
└── return %1

julia> @code_warntype g2_tuple('a')
Variables
  #self#::Core.Compiler.Const(g2_tuple, false)
  x::Char
Body::F2{_A,Char} where _A
1 ─ %1 = Core.tuple(1, 2, 3)::Core.Compiler.Const((1, 2, 3), false)
│ %2 = Main.f2(%1, x)::F2{_A,Char} where _A
└── return %2

```

For some reason, adding a second argument (or type parameter) made the compiler forget how to propagate the tuple to the output type, but not the type parameter. (Note that the type of `char` argument was also propagated.)

It gets stranger. Suppose we don’t ask for an instance, just for the type of object that _would’ve_ been created:

```julia
f2_type(T, x) = F2{T,typeof(x)}
g2_type_tuple(x) = f2_type((1,2,3), x)

julia> @code_warntype g2_type_tuple('a')
Variables
  #self#::Core.Compiler.Const(g2_type_tuple, false)
  x::Char
Body::Type{F2{(1, 2, 3),Char}}
1 ─ %1 = Core.tuple(1, 2, 3)::Core.Compiler.Const((1, 2, 3), false)
│ %2 = Main.f2_type(%1, x)::Core.Compiler.Const(F2{(1, 2, 3),Char}, false)
└── return %2

```

The compiler remembered how to propagate the tuple again! This shows that the compiler actually has the mechanism to infer the output type of `f2`, but for some reason just doesn’t do so in the other case.

Finally, we can obtain the desired behavior by wrapping the tuple in a `Val`:

```julia
f2_val(::Val{T}, x) where {T} = F2{T, typeof(x)}()
g2_val_tuple(x) = f2_val(Val((1,2,3)), x)

julia> @code_warntype g2_val_tuple('a')
Variables
  #self#::Core.Compiler.Const(g2_val_tuple, false)
  x::Char

Body::F2{(1, 2, 3),Char}
1 ─ %1 = Core.tuple(1, 2, 3)::Core.Compiler.Const((1, 2, 3), false)
│ %2 = Main.Val(%1)::Core.Compiler.Const(Val{(1, 2, 3)}(), true)
│ %3 = Main.f2_val(%2, x)::Core.Compiler.Const(F2{(1, 2, 3),Char}(), false)
└── return %3

```

The heuristics regarding what will and what will not be inferred by the compiler seem to me quite complex, almost haphazard. I realize that the compiler has the prerogative to infer as much or as little as it wants, but it is helpful when writing code to have some intuition about what is likely to be performant and what is not. With behavior like this, I seem to spend a significant amount of time rewriting code trying different approaches until I stumble upon the “magic” way of writing things so that the compiler will propagate constants and infer types.

Is there a rational explanation for the behavior above? Could compiler type inference be made to work more consistently/predictably? (BTW, this is not meant to be a criticism of the developers, since Julia is overall quite impressive and I know a lot of effort has gone into the compiler.)

---

<div class="post-metadata">

**Author:** ![prittjam](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/prittjam/32/21267_2.png) [@prittjam](https://discourse.julialang.org/u/prittjam)\
**Post date:** [February 1, 2023, 3:18pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/2 "2023-02-01T15:18:41Z")

</div>

I’ve run into the same problem and have the same criticism. The OP was 3 years ago. Is there progress on this in the compiler?

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [February 2, 2023, 2:43pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/3 "2023-02-02T14:43:39Z")

</div>

Did you try the previously posted code? Everything seems to const prop now:

```julia
julia> @code_warntype g2_tuple('a')
MethodInstance for g2_tuple(::Char)
  from g2_tuple(x) @ Main REPL[26]:1
Arguments
  #self#::Core.Const(g2_tuple)
  x::Char
Body::F2{(1, 2, 3), Char}
1 ─ %1 = Core.tuple(1, 2, 3)::Core.Const((1, 2, 3))
│ %2 = Main.f2(%1, x)::Core.Const(F2{(1, 2, 3), Char}())
└── return %2

```

There has been a great deal of work on constant propagation.

---

<div class="post-metadata">

**Author:** ![prittjam](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/prittjam/32/21267_2.png) [@prittjam](https://discourse.julialang.org/u/prittjam)\
**Post date:** [February 3, 2023, 4:39pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/4 "2023-02-03T16:39:14Z")

</div>

Hi. Thanks for the response. My case follows:

```julia
using StaticArrays, StructArrays

const PointRp2{T <: AbstractFloat} = SVector{3,T}
const MinRp2Sample{N,T <: AbstractFloat} = MVector{N,PointRp2{T}}
const CspondFields = (:first,:second)
const PointCspond{T <: AbstractFloat} = NamedTuple{CspondFields, Tuple{PointRp2{T},PointRp2{T}}}
const PointCsponds{T <: AbstractFloat} = StructArrays.StructArray{PointCspond{T}}

struct SamplePc{N, T <: AbstractFloat}
    pc::PointCsponds{T}
    u₁::MinRp2Sample{N,T}
    u₂::MinRp2Sample{N,T}
    data_idx::MVector{N,UInt32}
end

SamplePc(pc::PointCsponds{T}, N) where {T} = SamplePc(pc,MinRp2Sample{N,T}(undef),MinRp2Sample{N,T}(undef),MVector{N,UInt32}(undef))

u1 = [SVector{3}(rand(3)) for k in 1:7]
u2 = [SVector{3}(rand(3)) for k in 1:7]
pc = PointCsponds{Float64}((u1,u2))

tst = SamplePc(pc,7)

```

produces

```julia
 @code_warntype SamplePc(pc,7)
MethodInstance for SamplePc(::StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{StaticArraysCore.SVector{3, Float64}, StaticArraysCore.SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{StaticArraysCore.SVector{3, Float64}}, Vector{StaticArraysCore.SVector{3, Float64}}}}, Int64}, ::Int64)
  from SamplePc(pc::StructArrays.StructArray{NamedTuple{(:first, :second), Tuple{StaticArraysCore.SVector{3, T}, StaticArraysCore.SVector{3, T}}}}, N) where T in VisualGeometryToolkit at /home/jbpritts/.julia/dev/VisualGeometryToolkit/src/estimators/ransac_helpers.jl:10
Static Parameters
  T = Float64
Arguments
  #self#::Type{SamplePc}
  pc::StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{StaticArraysCore.SVector{3, Float64}, StaticArraysCore.SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{StaticArraysCore.SVector{3, Float64}}, Vector{StaticArraysCore.SVector{3, Float64}}}}, Int64}
  N::Int64
Body::SamplePc{_A, Float64} where _A
1 ─ %1 = Core.apply_type(VisualGeometryToolkit.MinRp2Sample, N, $(Expr(:static_parameter, 1)))::Type{StaticArraysCore.MVector{_A, StaticArraysCore.SVector{3, Float64}}} where _A
│ %2 = (%1)(VisualGeometryToolkit.undef)::StaticArraysCore.MArray{_A, StaticArraysCore.SVector{3, Float64}, 1} where _A<:Tuple
│ %3 = Core.apply_type(VisualGeometryToolkit.MinRp2Sample, N, $(Expr(:static_parameter, 1)))::Type{StaticArraysCore.MVector{_A, StaticArraysCore.SVector{3, Float64}}} where _A
│ %4 = (%3)(VisualGeometryToolkit.undef)::StaticArraysCore.MArray{_A, StaticArraysCore.SVector{3, Float64}, 1} where _A<:Tuple
│ %5 = Core.apply_type(VisualGeometryToolkit.MVector, N, VisualGeometryToolkit.UInt32)::Type{StaticArraysCore.MVector{_A, UInt32}} where _A
│ %6 = (%5)(VisualGeometryToolkit.undef)::StaticArraysCore.MArray{_A, UInt32, 1} where _A<:Tuple
│ %7 = VisualGeometryToolkit.SamplePc(pc, %2, %4, %6)::SamplePc{_A, Float64} where _A
└── return %7

```

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [February 3, 2023, 6:12pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/5 "2023-02-03T18:12:25Z")

</div>

> [@prittjam](#):
>
> `MVector{N,UInt32}(under)`

`under` is supposed to be `undef` ?

---

<div class="post-metadata">

**Author:** ![prittjam](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/prittjam/32/21267_2.png) [@prittjam](https://discourse.julialang.org/u/prittjam)\
**Post date:** [February 3, 2023, 6:35pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/6 "2023-02-03T18:35:42Z")

</div>

yes, were you able to reproduce the code\_warntype message? I fixed a problem with the PointCsponds constructor as well. Should be an MWE now.

---

<div class="post-metadata">

**Author:** ![prittjam](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/prittjam/32/21267_2.png) [@prittjam](https://discourse.julialang.org/u/prittjam)\
**Post date:** [February 3, 2023, 8:15pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/7 "2023-02-03T20:15:27Z")

</div>

@StefanKarpinski , the code example is now an MWE where I think constant propagation fails. Can you confirm, or am I making some coding error?

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [February 4, 2023, 7:52am UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/8 "2023-02-04T07:52:50Z")

</div>

You are likely misunderstanding how `@code_warntype` works. As you have used it, there is no constant to propagate. You are asking Julia to do type inference on `SamplePc(..., ::Int)`.

If you want the `7` to propagate, try somethingvlkke below.

```julia
julia> @inline SamplePc(pc::PointCsponds{T}, N) where {T} = SamplePc(pc,MinRp2Sample{N,T}(undef),MinRp2Sample{N,T}(undef),MVector{N,UInt32}(undef))
SamplePc

julia> @code_warntype (pc->SamplePc(pc,7))(pc) MethodInstance for (::var"#19#20")(::StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64})
  from (::var"#19#20")(pc) @ Main REPL[23]:1
Arguments
  #self#::Core.Const(var"#19#20"())
  pc::StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}
Body::SamplePc{7, Float64}
1 ─ %1 = Main.SamplePc(pc, 7)::Core.PartialStruct(SamplePc{7, Float64}, Any[StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}, MVector{7, SVector{3, Float64}}, MVector{7, SVector{3, Float64}}, MVector{7, UInt32}])
└── return %1

```

---

<div class="post-metadata">

**Author:** ![prittjam](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/prittjam/32/21267_2.png) [@prittjam](https://discourse.julialang.org/u/prittjam)\
**Post date:** [February 4, 2023, 12:33pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/9 "2023-02-04T12:33:25Z")

</div>

Ok, I don’t why there is a type instability (or ambiguity) if I specify 7 as a constant for N as I did in the original code.

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [February 4, 2023, 12:39pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/10 "2023-02-04T12:39:20Z")

</div>

Look at the macro expansion of `@code_warntype`:

```julia
julia> @macroexpand @code_warntype SamplePc(pc,7) :(InteractiveUtils.code_warntype(SamplePc, (Base.typesof)(pc, 7)))

```

The value `7` is never analyzed. Only its type is used.

---

<div class="post-metadata">

**Author:** ![prittjam](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/prittjam/32/21267_2.png) [@prittjam](https://discourse.julialang.org/u/prittjam)\
**Post date:** [February 4, 2023, 12:41pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/11 "2023-02-04T12:41:42Z")

</div>

Oh wow. ok, I thought it was a result of compilation, not how it was called by code\_warntype. That’s a gotcha for sure, and it seems non-obvious. Should it be documented?

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [February 4, 2023, 12:43pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/12 "2023-02-04T12:43:24Z")

</div>

That’s exactly what the documentation says.

```julia
help?> @code_warntype
  @code_warntype

  Evaluates the arguments to the
  function or macro call,
  determines their types, and
  calls code_warntype on the
  resulting expression.

```

---

<div class="post-metadata">

**Author:** ![prittjam](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/prittjam/32/21267_2.png) [@prittjam](https://discourse.julialang.org/u/prittjam)\
**Post date:** [February 4, 2023, 12:47pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/13 "2023-02-04T12:47:03Z")

</div>

I understand now the problem, but I think the unanointed (e.g., me) might not catch this issue and think there is a type instability problem with constant propagation.

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [February 7, 2023, 7:30pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/14 "2023-02-07T19:30:43Z")

</div>

Any valid function call has specific concrete arguments; the fact that an argument is passed as a literal or a variable doesn’t affect the call itself, which is what is shown.

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [February 7, 2023, 10:49pm UTC](https://discourse.julialang.org/t/constant-propagation-and-inference-of-constructed-types/36279/15 "2023-02-07T22:49:01Z")

</div>

This is why I really am not a fan of `@code_warntype` (and all the other `@code_` macros). The way it’s presented is very misleading.

It’s quite convenient for quick interactive use, but typically if I’m displaying it anywhere I prefer to use the function where the types are specified.

```julia
julia> code_warntype(x -> x + 1, (Float64,))
MethodInstance for (::var"#7#8")(::Float64)
  from (::var"#7#8")(x) @ Main REPL[56]:1
Arguments
  #self#::Core.Const(var"#7#8"())
  x::Float64
Body::Float64
1 ─ %1 = (x + 1)::Float64
└── return %1

```

Simply saying that the behaviour is documented isn’t a good excuse for non-intuitive behaviour since people won’t read the docs if they think they intuitively understand what it does without reading the docs.
