Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve inferrability of findall(::Union{AbstractString,AbstractPattern}, ::AbstractString) #38636

Closed
wants to merge 2 commits into from

Conversation

aviatesk
Copy link
Member

@aviatesk aviatesk commented Dec 1, 2020

eliminates succeeding possibilities:

┌ @ regex.jl:386 Base.#findall#393(overlap, _3, t, s)
│┌ @ regex.jl:391 Base.push!(found, r)
││┌ @ array.jl:914 Base.convert(_, item)
│││ no matching method found for call signature: Base.convert(_::Type{UnitRange{Int64}}, item::Nothing)
││└────────────────
│┌ @ regex.jl:392 Base.isempty(r)
││┌ @ essentials.jl:768 Base.iterate(itr)
│││ no matching method found for call signature: Base.iterate(itr::Nothing)
││└─────────────────────
│┌ @ regex.jl:392 Base.first(r)
││┌ @ abstractarray.jl:386 Base.iterate(itr)
│││ no matching method found for call signature: Base.iterate(itr::Nothing)
││└────────────────────────
│┌ @ regex.jl:392 Base.last(r)
││┌ @ abstractarray.jl:434 Base.lastindex(a)
│││ no matching method found for call signature: Base.lastindex(a::Nothing)
││└────────────────────────

(underlying issue is #37342 though)

@Keno
Copy link
Member

Keno commented Dec 1, 2020

Add a test to make sure this doesn't regress?

@aviatesk aviatesk force-pushed the findall branch 3 times, most recently from fedadb0 to 8b5f0a3 Compare December 4, 2020 01:26
…ern},::AbstractString)`

eliminates possibilities of:
- `Base.convert(_::Type{UnitRange{Int64}}, item::Nothing)`
- `Base.iterate(itr::Nothing)`
- `Base.iterate(itr::Nothing)`
- `Base.lastindex(a::Nothing)`
@Keno Keno added the needs tests Unit tests are required for this change label Dec 9, 2020
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 16, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constrain on the call arguments.
Then `Conditional`s will be converted into `InterConditional` objects,
which is implemented in `Core` and can be directly put into the global
cache. Finally `InterConditional` will be re-converted into
`Conditional` in the context of caller frame.

So now some simple "isa"-wrapper functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

Well, there're certain limitations around. One of the biggest ones would
be that we can't propagate constrains when there're multiple callee
conditions, e.g. `Meta.isexpr` can't still propagate its type constraint
to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 16, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 16, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 17, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 17, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 18, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 18, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 19, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 19, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 21, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 22, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 25, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 26, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 27, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 30, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 30, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 31, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 31, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Dec 31, 2020
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 2, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 6, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

There're certain limitations around. One of the biggest ones would be
that it can propagate constrains only on a single argument, and it fails
to back-propagate constrains when there're multiple conditions on
different slots, e.g. `Meta.isexpr` can't still propagate its type
constraint to the caller:
```julia
@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # still x::Any but ideally x::Expr
    return nothing
end == [Nothing,Expr]
```
(and because of this reason, this PR can't close JuliaLang#37342)
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 7, 2021
## improvements

This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

---

## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
# original definition of `Meta.isexpr`
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === 
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and 
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this 
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 8, 2021
## improvements

This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

---

## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
# original definition of `Meta.isexpr`
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === 
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and 
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this 
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 11, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 13, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 15, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 15, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 16, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 19, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 19, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == [Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == [Int]
```

This PR also tweaks `isnothing` and `ismissing` so that there is no
longer any inferrability penalties to use them instead of
`x === nothing` or `x === missing` e.g.:
```julia
@test Base.return_types((Union{Nothing,Int},)) do a
    isnothing(a) && return 0
    return a # a::Int
end == [Int]
```
(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 20, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 20, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 20, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 21, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 21, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 21, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Jan 30, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

---

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

---

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but with the original definition of `Meta.isexpr`, the
heuristic ends up picking up a constraint on the second argument
(i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head ===
head

x::Any
if isexpr(x, :call)
    x.args # ideally `x::Expr`
end
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Feb 3, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

 ## improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

 ## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

 ## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but the current heuristic ends up picking up a constraint
on the second argument (i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Feb 5, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

 ## improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

 ## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

 ## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but the current heuristic ends up picking up a constraint
on the second argument (i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Feb 6, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

 ## improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

 ## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

 ## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but the current heuristic ends up picking up a constraint
on the second argument (i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Feb 10, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

 ## improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

 ## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

 ## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but the current heuristic ends up picking up a constraint
on the second argument (i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Feb 15, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

 ## improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

 ## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

 ## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but the current heuristic ends up picking up a constraint
on the second argument (i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Feb 16, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

 ## improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

 ## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

 ## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but the current heuristic ends up picking up a constraint
on the second argument (i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Feb 17, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

 ## improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

 ## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

 ## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but the current heuristic ends up picking up a constraint
on the second argument (i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Feb 18, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

 ## improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

 ## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

 ## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but the current heuristic ends up picking up a constraint
on the second argument (i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
aviatesk added a commit to aviatesk/julia that referenced this pull request Feb 24, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

 ## improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

 ## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

 ## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but the current heuristic ends up picking up a constraint
on the second argument (i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
@aviatesk aviatesk deleted the findall branch February 27, 2021 06:36
ElOceanografo pushed a commit to ElOceanografo/julia that referenced this pull request May 4, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

 ## improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

 ## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

 ## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but the current heuristic ends up picking up a constraint
on the second argument (i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
antoine-levitt pushed a commit to antoine-levitt/julia that referenced this pull request May 9, 2021
This PR propagates `Conditional`s inter-procedurally when a
`Conditional` at return site imposes a constraint on the call arguments.
When inference exits local frame and the return type is annotated as
`Conditional`, it will be converted into `InterConditional` object,
which is implemented in `Core` and can be directly put into the global
cache. Finally after going back to caller frame, `InterConditional` will
be re-converted into `Conditional` in the context of the caller frame.

 ## improvements

So now some simple "is-wrapper" functions will propagate its constraint
as expected, e.g.:
```julia
isaint(a) = isa(a, Int)
@test Base.return_types((Any,)) do a
    isaint(a) && return a # a::Int
    return 0
end == Any[Int]

isaint2(::Any) = false
isaint2(::Int) = true
@test Base.return_types((Any,)) do a
    isaint2(a) && return a # a::Int
    return 0
end == Any[Int]

function isa_int_or_float64(a)
    isa(a, Int) && return true
    isa(a, Float64) && return true
    return false
end
@test Base.return_types((Any,)) do a
    isa_int_or_float64(a) && return a # a::Union{Float64,Int}
    0
end == Any[Union{Float64,Int}]
```

(and now we don't need something like JuliaLang#38636)

 ## benchmarks

A compile time comparison:
> on the current master (82d79ce)
```
Sysimage built. Summary:
Total ───────  55.295376 seconds
Base: ───────  23.359226 seconds 42.2444%
Stdlibs: ────  31.934773 seconds 57.7531%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1283/1283
Precompilation complete. Summary:
Total ───────  91.129162 seconds
Generation ──  68.800937 seconds 75.4983%
Execution ───  22.328225 seconds 24.5017%
    LINK usr/lib/julia/sys.dylib
```

> on this PR (37e279b)
```
Sysimage built. Summary:
Total ───────  51.694730 seconds
Base: ───────  21.943914 seconds 42.449%
Stdlibs: ────  29.748987 seconds 57.5474%
    JULIA usr/lib/julia/sys-o.a
Generating REPL precompile statements... 29/29
Executing precompile statements... 1357/1357
Precompilation complete. Summary:
Total ───────  88.956226 seconds
Generation ──  67.077710 seconds 75.4053%
Execution ───  21.878515 seconds 24.5947%
    LINK usr/lib/julia/sys.dylib
```

Here is a sample code that benefits from this PR:
```julia
function summer(ary)
    r = 0
    for a in ary
        if ispositive(a)
            r += a
        end
    end
    r
end

ispositive(a) = isa(a, Int) && a > 0

ary = Any[]
for _ in 1:100_000
    if rand(Bool)
        push!(ary, rand(-100:100))
    elseif rand(Bool)
        push!(ary, rand('a':'z'))
    else
        push!(ary, nothing)
    end
end

using BenchmarkTools
@Btime summer($(ary))
```

> on the current master (82d79ce)
```
❯ julia summer.jl
  1.214 ms (24923 allocations: 389.42 KiB)
```

> on this PR (37e279b)
```
❯ julia summer.jl
  421.223 μs (0 allocations: 0 bytes)
```

 ## caveats

Within the `Conditional`/`InterConditional` framework, only a single
constraint can be back-propagated inter-procedurally. This PR implements
a naive heuristic to "pick up" a constraint to be propagated when a
return type is a boolean. The heuristic may fail to select an
"interesting" constraint in some cases. For example, we may expect
`::Expr` constraint to be imposed on the first argument of
`Meta.isexpr`, but the current heuristic ends up picking up a constraint
on the second argument (i.e. `ex.head === head`).
```julia
isexpr(@nospecialize(ex), head::Symbol) = isa(ex, Expr) && ex.head === head

@test_broken Base.return_types((Any,)) do x
    Meta.isexpr(x, :call) && return x # x::Expr, ideally
    return nothing
end == Any[Union{Nothing,Expr}]
```

I think We can get rid of this limitation by extending `Conditional` and
`InterConditional`
so that they can convey multiple constraints, but I'd like to leave this
as a future work.

---

- closes JuliaLang#38636
- closes JuliaLang#37342
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs tests Unit tests are required for this change
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants