# \[ANN\] ShowLint

**URL:** <https://discourse.julialang.org/t/ann-showlint/52140>\
**Category:** Package Announcements\
**Created:** [December 20, 2020, 7:14pm UTC](https://discourse.julialang.org/t/ann-showlint/52140 "2020-12-20T19:14:58Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)\
**Post date:** [December 20, 2020, 7:14pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/1 "2020-12-20T19:14:58Z")

</div>

Hi all,

**TLDR:** I made a Julia linter which shows rewrite suggestions online. At the time of writing it analyses [128 Julia repositories](https://lint.huijzer.xyz/repositories) (about 1,012,728 lines of code) and shows the diffs.

I was recently watching a video of [SourceGraph Campaigns](https://sourcegraph.com/campaigns) and found it super cool. Basically, it allows you to apply a transformation to many repositories automatically. It would be especially nice if you could rewrite, for example, all occurrences of `findfirst(a, b) === nothing` to `!occursin(a, b)`.

Anyway, Sourcegraph pointed me to an awesome rewrite tool, namely [Comby](https://comby.dev/). For a brief moment, I considered building a replacement for [Lint.jl](https://github.com/tonyhffong/Lint.jl). However, then everyone has to install that tool and keep it updated. So, now I created some rewrite rules, run them over many repositories and show the diffs online. This, for example, can be useful for spotting coding mistakes, make code more readable by simplifying it, and can help in the transition after the release of the new Julia LTS.

You can see the source code at [GitHub - rikhuijzer/ShowLint: Showing static code analysis results for open source repositories](https://github.com/rikhuijzer/ShowLint) and the replacement patterns and diffs at [https://lint.huijzer.xyz](https://lint.huijzer.xyz).

PRs for extra repositories or rewrite rules (patterns) are welcome! 😃

---

<div class="post-metadata">

**Author:** ![tlienart](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tlienart/32/7640_2.png) [@tlienart](https://discourse.julialang.org/u/tlienart)\
**Post date:** [December 20, 2020, 8:30pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/2 "2020-12-20T20:30:11Z")

</div>

Actually this is pretty nice, I wonder whether you could also automatically

1. Link to relevant docs for your suggestion
2. Open a PR automatically against the base repo (I guess maybe first you’d have to ask people if they’re happy to receive such PRs)

But having a centralised place for “poor patterns that could be improved” can be quite nice for devs to learn new stuff too 🙂

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [December 20, 2020, 8:32pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/3 "2020-12-20T20:32:44Z")

</div>

Sorry if this is off topic, but why suggest turning `x === missing` to `ismissing(x)`? Especially when it seems to be fine with `x === nothing`?

---

<div class="post-metadata">

**Author:** ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)\
**Post date:** [December 20, 2020, 9:02pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/4 "2020-12-20T21:02:04Z")

</div>

@tlienart Thanks! I’ve added an issue for your first suggestion and am going to improve on that. For your second suggestion, I think that’s not possible yet. The site shows too many false positives. So far, I did find some interesting problems in code and created PRs manually:

- [https://github.com/JuliaData/YAML.jl/pull/103](https://github.com/JuliaData/YAML.jl/pull/103)
- [https://github.com/JuliaLang/Pkg.jl/pull/2297](https://github.com/JuliaLang/Pkg.jl/pull/2297)

@Mason that’s not off topic. Are you sure that `x === missing` is the same as `x === nothing`? I’m not an expert, so it could definitely be true. Would you mind elaborating a bit more?

---

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [December 20, 2020, 9:12pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/5 "2020-12-20T21:12:11Z")

</div>

> [@rikh](#):
>
> @Mason that’s not off topic. Are you sure that `x === missing` is the same as `x === nothing` ? I’m not an expert, so it could definitely be true. Would you mind elaborating a bit more?

Both `missing` and `nothing` are defined as `Missing()` and `Nothing()` respectively, and `ismissing` and `isnothing` are both defined as

```julia
ismissing(::Any) = false
ismissing(::Missing) = true

```

and

```julia
isnothing(::Any) = false
isnothing(::Nothing) = true

```

respectively, so as far as I can tell, they’re exactly analogous.

I believe it’s usually recommended to use `x === nothing` and `x === missing` when possible because the compiler is able to optimize it in more circumstances. From what I understand, `ismissing` and `isnothing` are mostly useful for higher order functions, e.g.

```julia
any(ismissing, x)

```

---

<div class="post-metadata">

**Author:** ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)\
**Post date:** [December 20, 2020, 10:20pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/6 "2020-12-20T22:20:07Z")

</div>

I do expect that ismissing(x) is easier to read for new Julia users, and performance is not really the issue for most practical problems („premature optimization is the root of all evil“), but you might very well be right that its not an useful pattern to check for. I‘m gonna look into it a bit more. 👍

---

<div class="post-metadata">

**Author:** ![greg\_plowman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/greg_plowman/32/8100_2.png) [@greg\_plowman](https://discourse.julialang.org/u/greg_plowman)\
**Post date:** [December 21, 2020, 12:46am UTC](https://discourse.julialang.org/t/ann-showlint/52140/7 "2020-12-21T00:46:20Z")

</div>

Maybe these are relevant:

> <https://github.com/JuliaLang/julia/issues/35585>
>
> This
> 
> \`\`\`julia
> import BenchmarkTools: @btime
> import InteractiveUtils: @code\_…llvm
> 
> function countnothing1(xs)
> i = 0
> for x in xs
> i += x === nothing
> end
> i
> end
> 
> function countnothing2(xs)
> i = 0
> for x in xs
> i += isnothing(x)
> end
> i
> end
> 
> @inline myisnothing(x) = x === nothing
> 
> function countnothing3(xs)
> i = 0
> for x in xs
> i += myisnothing(x)
> end
> i
> end
> 
> function bench()
> xs = Union{Nothing, Int64}\[i % 2 == 0 ? nothing : 1 for i in 1:1\_000\_000\]
> 
> @btime countnothing1($xs)
> @btime countnothing2($xs)
> @btime countnothing3($xs)
> 
> @code\_llvm countnothing1(xs)
> @code\_llvm countnothing3(xs)
> end
> 
> bench()
> \`\`\`
> 
> produces the timings
> 
> \`\`\`
> 214.857 μs (0 allocations: 0 bytes)
> 448.005 μs (0 allocations: 0 bytes)
> 447.848 μs (0 allocations: 0 bytes)
> \`\`\`
> 
> on Julia 1.4.0 and
> 
> \`\`\`
> 214.870 μs (0 allocations: 0 bytes)
> 572.832 μs (0 allocations: 0 bytes)
> 572.833 μs (0 allocations: 0 bytes)
> \`\`\`
> 
> on current master. The \`countnothing3()\` version is there just to make sure nothing funny occurs with the definition of \`isnothing()\`, but nope, it's not that. The \`@code\_llvm\` output when using \`x === nothing\` is
> 
> \`\`\`
> ; @ /Users/rauli/bench.jl:5 within \`countnothing1'
> define i64 @julia\_countnothing1\_17323(%jl\_value\_t addrspace(10)\* nonnull align 16 dereferenceable(40)) {
> top:
> ; @ /Users/rauli/bench.jl:6 within \`countnothing1'
> ; ┌ @ array.jl:763 within \`iterate' @ array.jl:763
> ; │┌ @ array.jl:221 within \`length'
> %1 = addrspacecast %jl\_value\_t addrspace(10)\* %0 to %jl\_value\_t addrspace(11)\*
> %2 = bitcast %jl\_value\_t addrspace(11)\* %1 to %jl\_array\_t addrspace(11)\*
> %3 = getelementptr inbounds %jl\_array\_t, %jl\_array\_t addrspace(11)\* %2, i64 0, i32 1
> %4 = load i64, i64 addrspace(11)\* %3, align 8
> ; │└
> %5 = icmp slt i64 %4, 1
> br i1 %5, label %L46, label %L14
> 
> L14: ; preds = %top
> ; │┌ @ array.jl:787 within \`getindex'
> %6 = bitcast %jl\_value\_t addrspace(11)\* %1 to \[1 x i64\] addrspace(13)\* addrspace(11)\*
> %7 = load \[1 x i64\] addrspace(13)\*, \[1 x i64\] addrspace(13)\* addrspace(11)\* %6, align 8
> %8 = getelementptr inbounds %jl\_array\_t, %jl\_array\_t addrspace(11)\* %2, i64 0, i32 4
> %9 = load i32, i32 addrspace(11)\* %8, align 4
> %10 = bitcast %jl\_value\_t addrspace(11)\* %1 to %jl\_value\_t addrspace(10)\* addrspace(11)\*
> %11 = getelementptr inbounds %jl\_value\_t addrspace(10)\*, %jl\_value\_t addrspace(10)\* addrspace(11)\* %10, i64 4
> %12 = bitcast %jl\_value\_t addrspace(10)\* addrspace(11)\* %11 to i64 addrspace(11)\*
> %13 = load i64, i64 addrspace(11)\* %12, align 8
> %14 = zext i32 %9 to i64
> %15 = sub i64 %13, %14
> %16 = getelementptr inbounds \[1 x i64\], \[1 x i64\] addrspace(13)\* %7, i64 %15
> %17 = bitcast \[1 x i64\] addrspace(13)\* %16 to i8 addrspace(13)\*
> %18 = sext i32 %9 to i64
> %19 = getelementptr inbounds i8, i8 addrspace(13)\* %17, i64 %18
> ; └└
> %tindex\_phi3.in52 = load i8, i8 addrspace(13)\* %19, align 1
> ; @ /Users/rauli/bench.jl:7 within \`countnothing1'
> ; ┌ @ int.jl:858 within \`+'
> ; │┌ @ int.jl:442 within \`rem'
> ; ││┌ @ number.jl:7 within \`convert'
> ; │││┌ @ boot.jl:707 within \`Int64'
> ; ││││┌ @ boot.jl:634 within \`toInt64'
> %20 = xor i8 %tindex\_phi3.in52, 1
> %21 = zext i8 %20 to i64
> ; └└└└└
> ; ┌ @ array.jl:763 within \`iterate'
> ; │┌ @ int.jl:416 within \`\<' @ int.jl:409
> %22 = icmp eq i64 %4, 1
> ; │└
> br i1 %22, label %L46, label %L40.preheader
> 
> L40.preheader: ; preds = %L14
> ; │┌ @ array.jl:787 within \`getindex'
> %23 = add i64 %4, -1
> %min.iters.check = icmp ult i64 %23, 16
> br i1 %min.iters.check, label %scalar.ph, label %vector.ph
> 
> vector.ph: ; preds = %L40.preheader
> %n.vec = and i64 %23, -16
> %ind.end = or i64 %n.vec, 1
> %ind.end57 = or i64 %n.vec, 2
> %24 = insertelement \<4 x i64\> \<i64 undef, i64 0, i64 0, i64 0\>, i64 %21, i32 0
> br label %vector.body
> 
> vector.body: ; preds = %vector.body, %vector.ph
> %index = phi i64 \[0, %vector.ph \], \[%index.next, %vector.body \]
> %vec.phi = phi \<4 x i64\> \[%24, %vector.ph \], \[%41, %vector.body \]
> %vec.phi61 = phi \<4 x i64\> \[zeroinitializer, %vector.ph \], \[%42, %vector.body \]
> %vec.phi62 = phi \<4 x i64\> \[zeroinitializer, %vector.ph \], \[%43, %vector.body \]
> %vec.phi63 = phi \<4 x i64\> \[zeroinitializer, %vector.ph \], \[%44, %vector.body \]
> %offset.idx = or i64 %index, 1
> %25 = getelementptr inbounds i8, i8 addrspace(13)\* %19, i64 %offset.idx
> ; └└
> %26 = bitcast i8 addrspace(13)\* %25 to \<4 x i8\> addrspace(13)\*
> %wide.load = load \<4 x i8\>, \<4 x i8\> addrspace(13)\* %26, align 1
> %27 = getelementptr inbounds i8, i8 addrspace(13)\* %25, i64 4
> %28 = bitcast i8 addrspace(13)\* %27 to \<4 x i8\> addrspace(13)\*
> %wide.load71 = load \<4 x i8\>, \<4 x i8\> addrspace(13)\* %28, align 1
> %29 = getelementptr inbounds i8, i8 addrspace(13)\* %25, i64 8
> %30 = bitcast i8 addrspace(13)\* %29 to \<4 x i8\> addrspace(13)\*
> %wide.load72 = load \<4 x i8\>, \<4 x i8\> addrspace(13)\* %30, align 1
> %31 = getelementptr inbounds i8, i8 addrspace(13)\* %25, i64 12
> %32 = bitcast i8 addrspace(13)\* %31 to \<4 x i8\> addrspace(13)\*
> %wide.load73 = load \<4 x i8\>, \<4 x i8\> addrspace(13)\* %32, align 1
> ; @ /Users/rauli/bench.jl:7 within \`countnothing1'
> ; ┌ @ int.jl:858 within \`+'
> ; │┌ @ int.jl:442 within \`rem'
> ; ││┌ @ number.jl:7 within \`convert'
> ; │││┌ @ boot.jl:707 within \`Int64'
> ; ││││┌ @ boot.jl:634 within \`toInt64'
> %33 = xor \<4 x i8\> %wide.load, \<i8 1, i8 1, i8 1, i8 1\>
> %34 = xor \<4 x i8\> %wide.load71, \<i8 1, i8 1, i8 1, i8 1\>
> %35 = xor \<4 x i8\> %wide.load72, \<i8 1, i8 1, i8 1, i8 1\>
> %36 = xor \<4 x i8\> %wide.load73, \<i8 1, i8 1, i8 1, i8 1\>
> %37 = zext \<4 x i8\> %33 to \<4 x i64\>
> %38 = zext \<4 x i8\> %34 to \<4 x i64\>
> %39 = zext \<4 x i8\> %35 to \<4 x i64\>
> %40 = zext \<4 x i8\> %36 to \<4 x i64\>
> ; │└└└└
> ; │ @ int.jl:860 within \`+' @ int.jl:53
> %41 = add \<4 x i64\> %vec.phi, %37
> %42 = add \<4 x i64\> %vec.phi61, %38
> %43 = add \<4 x i64\> %vec.phi62, %39
> %44 = add \<4 x i64\> %vec.phi63, %40
> %index.next = add i64 %index, 16
> %45 = icmp eq i64 %index.next, %n.vec
> br i1 %45, label %middle.block, label %vector.body
> 
> middle.block: ; preds = %vector.body
> %bin.rdx = add \<4 x i64\> %42, %41
> %bin.rdx74 = add \<4 x i64\> %43, %bin.rdx
> %bin.rdx75 = add \<4 x i64\> %44, %bin.rdx74
> %rdx.shuf = shufflevector \<4 x i64\> %bin.rdx75, \<4 x i64\> undef, \<4 x i32\> \<i32 2, i32 3, i32 undef, i32 undef\>
> %bin.rdx76 = add \<4 x i64\> %bin.rdx75, %rdx.shuf
> %rdx.shuf77 = shufflevector \<4 x i64\> %bin.rdx76, \<4 x i64\> undef, \<4 x i32\> \<i32 1, i32 undef, i32 undef, i32 undef\>
> %bin.rdx78 = add \<4 x i64\> %bin.rdx76, %rdx.shuf77
> %46 = extractelement \<4 x i64\> %bin.rdx78, i32 0
> %cmp.n = icmp eq i64 %23, %n.vec
> ; └
> ; ┌ @ array.jl:763 within \`iterate'
> ; │┌ @ array.jl:787 within \`getindex'
> br i1 %cmp.n, label %L46, label %scalar.ph
> 
> scalar.ph: ; preds = %middle.block, %L40.preheader
> %bc.resume.val = phi i64 \[%ind.end, %middle.block \], \[1, %L40.preheader \]
> %bc.resume.val56 = phi i64 \[%ind.end57, %middle.block \], \[2, %L40.preheader \]
> %bc.merge.rdx = phi i64 \[%46, %middle.block \], \[%21, %L40.preheader \]
> br label %L40
> 
> L40: ; preds = %scalar.ph, %L40
> %47 = phi i64 \[%value\_phi553, %L40 \], \[%bc.resume.val, %scalar.ph \]
> %48 = phi i64 \[%53, %L40 \], \[%bc.merge.rdx, %scalar.ph \]
> %value\_phi553 = phi i64 \[%50, %L40 \], \[%bc.resume.val56, %scalar.ph \]
> %49 = getelementptr inbounds i8, i8 addrspace(13)\* %19, i64 %47
> ; │└
> ; │┌ @ int.jl:53 within \`+'
> %50 = add i64 %value\_phi553, 1
> ; └└
> %tindex\_phi3.in = load i8, i8 addrspace(13)\* %49, align 1
> ; @ /Users/rauli/bench.jl:7 within \`countnothing1'
> ; ┌ @ int.jl:858 within \`+'
> ; │┌ @ int.jl:442 within \`rem'
> ; ││┌ @ number.jl:7 within \`convert'
> ; │││┌ @ boot.jl:707 within \`Int64'
> ; ││││┌ @ boot.jl:634 within \`toInt64'
> %51 = xor i8 %tindex\_phi3.in, 1
> %52 = zext i8 %51 to i64
> ; │└└└└
> ; │ @ int.jl:860 within \`+' @ int.jl:53
> %53 = add i64 %48, %52
> ; └
> ; ┌ @ array.jl:763 within \`iterate'
> ; │┌ @ int.jl:416 within \`\<' @ int.jl:409
> %54 = icmp ult i64 %value\_phi553, %4
> ; │└
> br i1 %54, label %L40, label %L46
> 
> L46: ; preds = %L40, %middle.block, %L14, %top
> %value\_phi10 = phi i64 \[0, %top \], \[%21, %L14 \], \[%53, %L40 \], \[%46, %middle.block \]
> ; └
> ; @ /Users/rauli/bench.jl:9 within \`countnothing1'
> ret i64 %value\_phi10
> }
> \`\`\`
> 
> and when using \`myisnothing(x)\` it is
> 
> \`\`\`
> ; @ /Users/rauli/bench.jl:23 within \`countnothing3'
> define i64 @julia\_countnothing3\_17327(%jl\_value\_t addrspace(10)\* nonnull align 16 dereferenceable(40)) {
> top:
> ; @ /Users/rauli/bench.jl:24 within \`countnothing3'
> ; ┌ @ array.jl:763 within \`iterate' @ array.jl:763
> ; │┌ @ array.jl:221 within \`length'
> %1 = addrspacecast %jl\_value\_t addrspace(10)\* %0 to %jl\_value\_t addrspace(11)\*
> %2 = bitcast %jl\_value\_t addrspace(11)\* %1 to %jl\_array\_t addrspace(11)\*
> %3 = getelementptr inbounds %jl\_array\_t, %jl\_array\_t addrspace(11)\* %2, i64 0, i32 1
> %4 = load i64, i64 addrspace(11)\* %3, align 8
> ; │└
> %5 = icmp slt i64 %4, 1
> br i1 %5, label %L54, label %L14.L14.split\_crit\_edge
> 
> L14.L14.split\_crit\_edge: ; preds = %top
> ; │┌ @ array.jl:787 within \`getindex'
> %6 = bitcast %jl\_value\_t addrspace(11)\* %1 to \[1 x i64\] addrspace(13)\* addrspace(11)\*
> %7 = load \[1 x i64\] addrspace(13)\*, \[1 x i64\] addrspace(13)\* addrspace(11)\* %6, align 8
> %8 = getelementptr inbounds %jl\_array\_t, %jl\_array\_t addrspace(11)\* %2, i64 0, i32 4
> %9 = load i32, i32 addrspace(11)\* %8, align 4
> %10 = bitcast %jl\_value\_t addrspace(11)\* %1 to %jl\_value\_t addrspace(10)\* addrspace(11)\*
> %11 = getelementptr inbounds %jl\_value\_t addrspace(10)\*, %jl\_value\_t addrspace(10)\* addrspace(11)\* %10, i64 4
> %12 = bitcast %jl\_value\_t addrspace(10)\* addrspace(11)\* %11 to i64 addrspace(11)\*
> %13 = load i64, i64 addrspace(11)\* %12, align 8
> %14 = zext i32 %9 to i64
> %15 = sub i64 %13, %14
> %16 = getelementptr inbounds \[1 x i64\], \[1 x i64\] addrspace(13)\* %7, i64 %15
> %17 = bitcast \[1 x i64\] addrspace(13)\* %16 to i8 addrspace(13)\*
> %18 = sext i32 %9 to i64
> %19 = getelementptr inbounds i8, i8 addrspace(13)\* %17, i64 %18
> ; └└
> br label %L20
> 
> L20: ; preds = %L48, %L14.L14.split\_crit\_edge
> %value\_phi2 = phi i64 \[0, %L14.L14.split\_crit\_edge \], \[%22, %L48 \]
> %tindex\_phi3.in.in = phi i8 addrspace(13)\* \[%19, %L14.L14.split\_crit\_edge \], \[%25, %L48 \]
> %value\_phi5 = phi i64 \[2, %L14.L14.split\_crit\_edge \], \[%26, %L48 \]
> %tindex\_phi3.in = load i8, i8 addrspace(13)\* %tindex\_phi3.in.in, align 1
> ; @ /Users/rauli/bench.jl:25 within \`countnothing3'
> %20 = xor i8 %tindex\_phi3.in, 1
> %21 = zext i8 %20 to i64
> ; ┌ @ int.jl:860 within \`+' @ int.jl:53
> %22 = add i64 %value\_phi2, %21
> ; └
> ; ┌ @ array.jl:763 within \`iterate'
> ; │┌ @ int.jl:860 within \`-' @ int.jl:52
> %23 = add i64 %value\_phi5, -1
> ; │└
> ; │┌ @ int.jl:416 within \`\<' @ int.jl:409
> %24 = icmp ult i64 %23, %4
> ; │└
> br i1 %24, label %L48, label %L54
> 
> L48: ; preds = %L20
> ; │┌ @ array.jl:787 within \`getindex'
> %25 = getelementptr inbounds i8, i8 addrspace(13)\* %19, i64 %23
> ; │└
> ; │┌ @ int.jl:53 within \`+'
> %26 = add i64 %value\_phi5, 1
> ; └└
> br label %L20
> 
> L54: ; preds = %L20, %top
> %value\_phi11 = phi i64 \[0, %top \], \[%22, %L20 \]
> ; @ /Users/rauli/bench.jl:27 within \`countnothing3'
> ret i64 %value\_phi11
> }
> \`\`\`
> 
> The former uses vectorization (\`\<4 x i64\>\`, \`\<4 x i8\>\`, \`shufflevector\`, etc.), whereas the latter does not. The only difference in the code is that the former is manually inlined (literally by copy-pasting the function body), whereas the latter is automatically inlined.
> 
> Julia 1.4.0 versioninfo:
> 
> \`\`\`
> Julia Version 1.4.0
> Commit b8e9a9ecc6 (2020-03-21 16:36 UTC)
> Platform Info:
> OS: macOS (x86\_64-apple-darwin18.7.0)
> CPU: Intel(R) Core(TM) i7-6920HQ CPU @ 2.90GHz
> WORD\_SIZE: 64
> LIBM: libopenlibm
> LLVM: libLLVM-8.0.1 (ORCJIT, skylake)
> \`\`\`
> 
> Julia master versioninfo:
> 
> \`\`\`
> Julia Version 1.5.0-DEV.676
> Commit b49d5ba395 (2020-04-24 06:56 UTC)
> Platform Info:
> OS: macOS (x86\_64-apple-darwin18.7.0)
> CPU: Intel(R) Core(TM) i7-6920HQ CPU @ 2.90GHz
> WORD\_SIZE: 64
> LIBM: libopenlibm
> LLVM: libLLVM-9.0.1 (ORCJIT, skylake)
> \`\`\`

> <https://github.com/JuliaLang/julia/pull/38905>
>
> This PR propagates \`Conditional\`s inter-procedurally when a
> \`Conditional\` at re…turn 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 #38636)
> 
> ## benchmarks
> 
> A compile time comparison:
> \> on the current master (82d79ce18f88923c14d322b70699da43a72e6b32)
> \`\`\`
> 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 (37e279bce7136e48f159811641b68143412c3881)
> \`\`\`
> 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 (82d79ce18f88923c14d322b70699da43a72e6b32)
> \`\`\`
> ❯ julia summer.jl
> 1.214 ms (24923 allocations: 389.42 KiB)
> \`\`\`
> 
> \> on this PR (37e279bce7136e48f159811641b68143412c3881)
> \`\`\`
> ❯ 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.
> 
> EDIT: 3df027a implements the "pick up" logic within a caller
> (i.e. within \`abstract\_call\_gf\_by\_type\`), which allows us to choose a
> constraint more appropriately, and now the \`Meta.isexpr\` case is fixed.
> Still there is a limitation of multiple conditional constraint
> back-propagation; the following broken test case will explain the future
> work.
> \`\`\`julia
> is\_int\_and\_int(a, b) = isa(a, Int) && isa(b, Int)
> @test\_broken Base.return\_types((Any,Any)) do a, b
> is\_int\_and\_int(a, b) && return a, b # (a::Int, b::Int) ideally, but (a::Any, b::Int)
> 0, 0
> end == Any\[Tuple{Int,Int}\]
> \`\`\`
> 
> \---
> 
> \- closes #38636
> \- closes #37342

---

<div class="post-metadata">

**Author:** ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)\
**Post date:** [December 21, 2020, 1:30pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/8 "2020-12-21T13:30:44Z")

</div>

I’ve opened a topic at [Ismissing(x) versus x === missing](https://discourse.julialang.org/t/ismissing-x-versus-x-missing/52171).

---

<div class="post-metadata">

**Author:** ![Wikunia](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wikunia/32/2180_2.png) [@Wikunia](https://discourse.julialang.org/u/Wikunia)\
**Post date:** [December 21, 2020, 2:27pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/9 "2020-12-21T14:27:01Z")

</div>

That’s pretty dope and I’m happy that you analysed two of my repos 🙂  
How long does it take to produces the results? Is it out of reach to do this for all registered packages?

---

<div class="post-metadata">

**Author:** ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)\
**Post date:** [December 21, 2020, 2:33pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/10 "2020-12-21T14:33:58Z")

</div>

Haha, you’re welcome

Currently, it takes about 20 minutes ([https://github.com/rikhuijzer/ShowLint/actions](https://github.com/rikhuijzer/ShowLint/actions)), so it’s not too bad. Good idea. I’ve put it in my task list ([https://github.com/rikhuijzer/ShowLint/issues/11](https://github.com/rikhuijzer/ShowLint/issues/11)).

---

<div class="post-metadata">

**Author:** ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)\
**Post date:** [December 21, 2020, 2:44pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/11 "2020-12-21T14:44:18Z")

</div>

@joa-quim In response to [https://discourse.julialang.org/t/ismissing-x-versus-x-missing/52171/3:](https://discourse.julialang.org/t/ismissing-x-versus-x-missing/52171/3:) That’s why I’m working ShowLint with the possibility to disable lint patterns in bulk. Instead of adding a lint configuration to each project/repository, I could just disable the check for this pattern and similar checks on all “high-performance” repositories. Basically, the idea is that all the configuration complexity is moved away from the user and is managed at one place. Also, the idea is that its easier for the Julia community to help busy package creators out. For example, some packages are created one or more years ago and the creator has moved on to other things. With something like ShowLint, the community can more easily assist these creators in keeping their packages working and up-to-date.

---

<div class="post-metadata">

**Author:** ![Wikunia](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wikunia/32/2180_2.png) [@Wikunia](https://discourse.julialang.org/u/Wikunia)\
**Post date:** [December 21, 2020, 2:50pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/12 "2020-12-21T14:50:57Z")

</div>

Oh and maybe only lint the `src` folder as something like `a == a` can be helpful in testing to check if the function for `==` is correctly implemented.

---

<div class="post-metadata">

**Author:** ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)\
**Post date:** [December 21, 2020, 3:00pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/13 "2020-12-21T15:00:15Z")

</div>

You’re quick. It took me until this morning to realize that [https://github.com/rikhuijzer/ShowLint/issues/9](https://github.com/rikhuijzer/ShowLint/issues/9)

---

<div class="post-metadata">

**Author:** ![GunnarFarneback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gunnarfarneback/32/1827_2.png) [@GunnarFarneback](https://discourse.julialang.org/u/GunnarFarneback)\
**Post date:** [December 21, 2020, 3:23pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/14 "2020-12-21T15:23:08Z")

</div>

Comparison with Boolean constants are only redundant if you can prove that the value itself is a `Bool`. And `b == trues(n)` is not equivalent to `bs(n)`.

---

<div class="post-metadata">

**Author:** ![Wikunia](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wikunia/32/2180_2.png) [@Wikunia](https://discourse.julialang.org/u/Wikunia)\
**Post date:** [December 21, 2020, 3:27pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/15 "2020-12-21T15:27:15Z")

</div>

Haha I didn’t look at the issues though 😃 I saw that you changed some `a == a` in the latest run. Have you tackled the false positives in JuMP.jl for example? There you have `@constraint(m, x-1 == 1)` and form it into `@constraint(m, x-true)`

---

<div class="post-metadata">

**Author:** ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)\
**Post date:** [December 21, 2020, 3:40pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/16 "2020-12-21T15:40:18Z")

</div>

@GunnarFarneback Yes, I’m still tweaking the rules and am note sure what to do with it. I do think that [https://github.com/JuliaData/YAML.jl/pull/103](https://github.com/JuliaData/YAML.jl/pull/103) shows that there are situations where that check can be useful but, indeed, its a part of Julia since `x == true` will give `missing` if `x` is a missing.

@Wikunia I got some tips from Rijnard van Tonder (Comby’s creator) and expect to have that fixed later today

---

<div class="post-metadata">

**Author:** ![jakobnissen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jakobnissen/32/13477_2.png) [@jakobnissen](https://discourse.julialang.org/u/jakobnissen)\
**Post date:** [December 21, 2020, 4:08pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/17 "2020-12-21T16:08:18Z")

</div>

This is great. I’d love to see more linting in Julia. A few points:

- `a == a` appears to also trigger on `2 * a == a * 2` and even `x/a == a`, which should not be intended.
- `x -> f(x)` triggers on `x -> 2gx(x)`, suggesting `2gx`, which is not corret

---

<div class="post-metadata">

**Author:** ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)\
**Post date:** [December 21, 2020, 4:27pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/18 "2020-12-21T16:27:29Z")

</div>

Thanks for both. Good catch on `x/a == a`. Should be fixed somewhere today.

---

<div class="post-metadata">

**Author:** ![mcabbott](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcabbott/32/6603_2.png) [@mcabbott](https://discourse.julialang.org/u/mcabbott)\
**Post date:** [December 21, 2020, 4:37pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/19 "2020-12-21T16:37:20Z")

</div>

Looks like a neat tool!

My few points:

- The replacement for `findall(x -> x==false, [true, false])` should probably be `findall(!, [true, false])` rather than broadcasting.
- But replacing `cond == false` with `cond` only seems safe if you are sure that `cond::Bool`. I don’t see examples where this is clearly violated, but I do have functions which expect either `nothing` or `false`. And `findall(!, [true, false, nothing])` obviously won’t work.

---

<div class="post-metadata">

**Author:** ![rikh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rikh/32/204104_2.png) [@rikh](https://discourse.julialang.org/u/rikh)\
**Post date:** [December 21, 2020, 6:07pm UTC](https://discourse.julialang.org/t/ann-showlint/52140/20 "2020-12-21T18:07:06Z")

</div>

Thanks for your suggestions!

I’m afraid that I don’t understand what would be the difference between `findall(!, A)` and `findall(.!A)`, and why the former would be preferred. Could you explain the difference?

So, this is now the second pattern which doesn’t handle missings well. 🧐 Thanks for pointing that out

[Next page](https://discourse.julialang.org/t/ann-showlint/52140.md?page=2)
