Unexpected Boolean flag behavior in Julia 1.12 vs 1.10

I have encountered a strange issue while migrating code from Julia 1.10 to Julia 1.12.3. It seems like a variable assignment within a function is not being reflected correctly in the caller’s scope, or I am fundamentally misunderstanding a change in the recent version.

Here is a minimal working example:

str = "[1 232.4;\n 2 40;\n 2 40];"
write("my_file.txt", str)

function readFile(str::String)
    flag = false

    for line in eachline("my_file.txt")
        if !flag && occursin("[", line)
            flag = true
            println("Flag value after first change: ",  flag)
        end

        if flag
            flag = parseLine(line, flag, "[", "]")
            println("Flag returned from parseLine: ", flag)
        end
    end
end

function parseLine(line::String, flag::Bool, start::String, last::String)
    if occursin(start, line)
        line = split(line, start)[end]
    end

    if occursin(last, line)
        flag = false
        println("Flag updated inside parseLine: ",  flag)
    end

    return flag
end

readFile(str)

The problematic output:

Flag value after first change: true
Flag returned from parseLine: true
Flag returned from parseLine: true
Flag updated inside parseLine: false
Flag returned from parseLine: true      <-- Why is this true?

The last line indicates that even though parseLine explicitly returned false, the flag variable in the readFile function remains true.

Two strange observations:

  1. Adding an else block fixes it: If I modify parseLine to explicitly set flag = true in an else branch, the output is correct (the flag becomes false in the caller scope).
function parseLine(line::String, flag::Bool, start::String, last::String)
    if occursin(start, line)
        line = split(line, start)[end]
    end

    if occursin(last, line)
        flag = false
        println("Flag updated inside parseLine: ",  flag)
    else
        flag = true
    end

    return flag
end
  1. Commenting out the split logic:
if occursin(start, line)
   line = split(line, start)[end]
end

also fixes it.

I am certain this logic worked in Julia 1.10. Has there been a change in how local bindings or string manipulations (like split) interact with function returns in Julia 1.12?

Any help or explanation would be greatly appreciated!

This is possibly a manifestation of a compiler bug

yes, this is a bug that is fixed on master and will be backported to the next 1.12 patch release

Can confirm 1.12 starts the anomaly…:

julia> readFile(str) # 1.12.4, 1.13.0-beta2
Flag value after first change: true
Flag returned from parseLine: true
Flag returned from parseLine: true
Flag updated inside parseLine: false
Flag returned from parseLine: true

…because 1.11 also does the correct thing. Can confirm nightly fixes this particular example at least:

julia> readFile(str) # 1.11.9, nightly 1.14.0-DEV.1694
Flag value after first change: true
Flag returned from parseLine: true
Flag returned from parseLine: true
Flag updated inside parseLine: false
Flag returned from parseLine: false

Wonder where in the compilation process goes wrong, interestingly the @code_lowered doesn’t show a misscoped variable as it appeared to be.

The issue is still present in the latest Julia v1.12.5 release.

The fix barely didn’t get in the 1.12.5 release. It’s marked for backport, so it’ll be in 1.12.6.

The bug remains in 1.12.6. Even though the logic checks out, the results are still inconsistent with what we expect.

Yeah, looks like julia#60902 doesn’t merge into 1.12 cleanly, so it requires some extra manual effort that’s not been done: Backports for 1.12.6 by KristofferC · Pull Request #61154 · JuliaLang/julia · GitHub