From f9cdca7e7992f258fa5a63e6dc698cb6b77223ad Mon Sep 17 00:00:00 2001 From: Felipe Lema <1232306+FelipeLema@users.noreply.github.com> Date: Fri, 21 Dec 2018 18:13:17 -0300 Subject: [PATCH] Fix #252 ~ Update code to julia 1.0 (#256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Try to fix precompile issues for Julia 1.0 * Try to fix some static analysis test cases * Empty `Tuple` method dispatch See documentation on `Vararg` * Fix handling of `Vector` `Vector` has no types ⇒ `length(T.types) == 0` * Remove `try` without `catch` Why was it there to begin with? * basic.jl runs ok Added a heuristic on `lintpkg` to distinguish a package path from a package name. Compat `renamed is_windows`. * Re-write and documentation of `_lintstr` It was *REALLY* hard to understand the intent of the function. The code now is still verbose, but each _code block_ has its specific purpose. Before this we had several stateful variables and conditions that may or may not be triggered * Adjust versions No going back * Using Pkg * `isnull(…)` → `… == nothing` * Remove more `isnull`s and helper function `BROADCAST` * `contains` → `occursin` * Minor code update * `isbits` applies to objects, `isbitstype` to types `t` was supposed to be a type, not an object * Break expression from-string producer code * Use `let …` statements for several reasons ● It's visually easy to distinguish between two different test cases ● Ensures that global state will not change between tests * Colon detection in expression is bad. At least now I know how to fix it * Correctly detect a `:` * Break `guesstype` into multi-dispatch code. This will make testing much easier. Inspired on https://pixorblog.wordpress.com/2018/02/23/julia-dispatch-enum-vs-type-comparison/ (or https://www.juliabloggers.com/julia-dispatching-enum-versus-type/ ) * Fix `guesstype` tests * Remove more `get(…)` calls from version<07's nullables * `contains` → `occursin` * `@lintpragma` seems to be parsed differently now * sed -i s/contains/occursin/g *.jl * forgot that `occursin` has needle as 1st argument 😒 * `--compilecache=no` → `--compiled-modules=no` Per https://github.com/tonyhffong/Lint.jl/pull/256#issuecomment-443962287 * Remove `Test` as it belongs to std lib * Simple updates around `get(…)` and `isnull(…)` * Remove `occursin(…)` as they only clog testing now They were meant to test the error being popped, but this was before we had codes for errors. * Base.var → Statistics.var in v1.0 Also, cosmetics * Using another `Base.+` instead of `Base.var` Friendly reminder that Base.var → Statistics.var in v1.0 * Remove `occursin`s * Reformat dict tests * Looks like this test is no longer broken * `Void` → `Cvoid` See https://github.com/JuliaLang/julia/issues/25082 for more context * Remove `occursin` (legacy testing before using codes) * Remove `occursin` * Forward `isknownerror` arguments from `infertype` * Chasing `shadowed variable` problem This error is the root of many tests failing * Drop versions prior to v1.0 * `… ≠ nothing` → `… !== nothing` https://github.com/tonyhffong/Lint.jl/pull/256#discussion_r243461417 * Fix parsing Lines were being parsed _regardless_ of whether they were parsed before. Now the expression _and_ expression offset is being reported to keep track and omit lines already parsed * Mark TODO * Line iterator * Must handle (SubString) indices and not Strings We have to track offsets, so we need indices to the original string. It surprises me that `split(…)` does not return _or_ handle an iterator (forced Array output) * Rewrite test * Offset lines and most of expression iterator Re-wrote `each_line_iterator` so we can actually use it as iterator (with `iterate(…)`) * Inline functions, use offsets as lines * Iterators are hard * Redid the API to have line's offsets The code is made to report expression and line offsets, but I wrapped around those iterators to provide a "just expression" API * Try to replace previous parsing (doesn't work) * Missing try-catch Do mind that pre-compile forced `lintstr` seems to be reporting errors * delete legacy file --- .travis.yml | 5 +- src/Lint.jl | 4 +- src/abstracteval.jl | 42 +++--- src/ast.jl | 6 +- src/blocks.jl | 2 +- src/cli.jl | 58 ++++---- src/compat.jl | 41 ------ src/curly.jl | 5 +- src/dict.jl | 4 +- src/dynamic.jl | 6 +- src/expression_iterator.jl | 85 ++++++++++++ src/exprutils.jl | 59 ++++---- src/functions.jl | 15 +- src/guesstype.jl | 270 +++++++++++++++++++++++------------- src/knowndeprec.jl | 22 +-- src/linttypes.jl | 87 ++++++------ src/modules.jl | 48 ++++--- src/pragma.jl | 9 +- src/result.jl | 2 +- src/statictype.jl | 98 +++++++------ src/types.jl | 6 +- src/types/lintmessage.jl | 2 +- src/types/location.jl | 2 +- src/variables.jl | 34 +++-- test/E100.jl | 6 +- test/E522.jl | 18 +-- test/I340.jl | 10 +- test/I343.jl | 2 +- test/W361.jl | 2 +- test/array.jl | 231 +++++++++++++++--------------- test/badvars.jl | 21 ++- test/basics.jl | 2 +- test/bitopbool.jl | 4 - test/curly.jl | 16 +-- test/deadcode.jl | 2 +- test/deprecate.jl | 10 +- test/dictkey.jl | 25 ++-- test/doc.jl | 1 - test/dupexport.jl | 2 +- test/expression_iterator.jl | 43 ++++++ test/exprutils.jl | 2 - test/forloop.jl | 6 +- test/funcall.jl | 28 ---- test/globals.jl | 10 +- test/guesstype.jl | 26 +++- test/ifstmt.jl | 30 ++-- test/incomplete.jl | 4 +- test/lambda.jl | 6 +- test/linthelper.jl | 2 +- test/macro.jl | 4 +- test/misuse.jl | 10 +- test/pragma.jl | 16 +-- test/range.jl | 8 +- test/ref.jl | 6 +- test/runtests.jl | 90 ++++++------ test/server.jl | 2 +- test/similarity.jl | 2 +- test/stagedfuncs.jl | 8 +- test/statictype.jl | 29 ++-- test/strings.jl | 8 +- test/throw.jl | 2 +- test/tuple.jl | 6 +- test/type.jl | 34 ++--- test/typecheck.jl | 42 +++--- test/undeclare.jl | 4 +- test/variables.jl | 19 +++ test/versions.jl | 40 +++--- 67 files changed, 958 insertions(+), 793 deletions(-) delete mode 100644 src/compat.jl create mode 100644 src/expression_iterator.jl create mode 100644 test/expression_iterator.jl create mode 100644 test/variables.jl diff --git a/.travis.yml b/.travis.yml index 3a3f43c..af785cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,7 @@ os: - linux - osx julia: - - 0.5 - - 0.6 + - 1.0 - nightly notifications: email: false @@ -13,5 +12,5 @@ after_success: - julia -e 'cd(Pkg.dir("Lint")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(process_folder())'; script: - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi - - julia --compilecache=no -e 'Pkg.clone(pwd()); Pkg.build("Lint"); Pkg.test("Lint"; coverage=true)'; + - julia --compiled-modules=no -e 'using Pkg; Pkg.clone(pwd()); Pkg.build("Lint"); Pkg.test("Lint"; coverage=true)' - julia -e 'using Lint' # loading after precompilation diff --git a/src/Lint.jl b/src/Lint.jl index dcbabb3..862be09 100644 --- a/src/Lint.jl +++ b/src/Lint.jl @@ -7,6 +7,7 @@ using Compat using Compat: TypeUtils, readline using JSON using AutoHashEquals +using Printf if isdefined(Base, :unwrap_unionall) using Base: unwrap_unionall @@ -25,9 +26,6 @@ const SIMILARITY_THRESHOLD = 10.0 macro lintpragma(s) end -# needed for BROADCAST -include("compat.jl") -using .LintCompat include("exprutils.jl") using .ExpressionUtils diff --git a/src/abstracteval.jl b/src/abstracteval.jl index 73d26ee..ad60d1b 100644 --- a/src/abstracteval.jl +++ b/src/abstracteval.jl @@ -1,59 +1,63 @@ """ - abstract_eval(ctx::LintContext, ex) :: Nullable + abstract_eval(ctx::LintContext, ex) :: Union{Any, Nothing} Like `eval`, but does it in the current context and without any dynamism. -Returns `Nullable()` if the result can't be evaluated. +Returns `nothing` if the result can't be evaluated. """ -abstract_eval(ctx::LintContext, ex::Symbol) = - flatten(BROADCAST(extractobject, lookup(ctx, ex))) +function abstract_eval(ctx::LintContext, ex::Symbol) + let lu = lookup(ctx, ex) + if lu !== nothing + extractobject(lu) + end + end +end """ - abstract_eval(ctx::LintContext, ex::Expr) :: Nullable + abstract_eval(ctx::LintContext, ex::Expr) :: Union{Any, Nothing} If the given expression is curly, and each component of the curly is a constant object in the given `ctx`, construct the object `x` as would have been done in -the program itself, and return `Nullable(x)`. +the program itself, and return `x`. Otherwise, if the given expression is `foo.bar`, and `foo` is a standard library object with attribute `bar`, then construct `foo.bar` as would be done in the program itself and return it. -Otherwise, return `Nullable()`. +Otherwise, return `nothing`. """ abstract_eval(ctx::LintContext, ex::Expr) = begin if isexpr(ex, :curly) - # TODO: when 0.5 support dropped, remove [...] around ctx - objs = abstract_eval.([ctx], ex.args) - if all(!isnull, objs) + objs = [abstract_eval(ctx, arg) for arg in ex.args] + if all(e->e!==nothing, objs) try - Nullable(Core.apply_type(get.(objs)...)) + Core.apply_type(objs...) catch - Nullable() + nothing end else - Nullable() + nothing end elseif isexpr(ex, :(.)) head = ex.args[1] tail = ex.args[2].value obj = abstract_eval(ctx, head) - if !isnull(obj) + if obj !== nothing try - Nullable(getfield(get(obj), tail)) + getfield(obj, tail) catch - Nullable() + nothing end else - Nullable() + nothing end else - Nullable() + nothing end end """ abstract_eval(ctx::LintContext, ex) -Return the literal embedded within a `Nullable{Any}`. +Return the literal embedded within a `Union{Any, Nothing}`. """ abstract_eval(ctx::LintContext, ex) = lexicalvalue(ex) diff --git a/src/ast.jl b/src/ast.jl index 31c7765..c1564b1 100644 --- a/src/ast.jl +++ b/src/ast.jl @@ -43,8 +43,8 @@ function lintexpr(ex::Expr, ctx::LintContext) lintifexpr(ex, ctx) elseif ex.head == :(=) && typeof(ex.args[1])==Expr && ex.args[1].head == :call lintfunction(ex, ctx) - elseif !isnull(expand_assignment(ex)) - ea = get(expand_assignment(ex)) + elseif expand_assignment(ex) !== nothing + ea = expand_assignment(ex) lintassignment(Expr(:(=), ea[1], ea[2]), ctx) elseif ex.head == :local lintlocal(ex, ctx) @@ -99,7 +99,7 @@ function lintexpr(ex::Expr, ctx::LintContext) lintmacrocall(ex, ctx) elseif ex.head == :call lintfunctioncall(ex, ctx) - elseif ex.head == :(:) + elseif ex.head == :(:) # TODO(felipe) check for `Colon()` lintrange(ex, ctx) elseif ex.head == :(::) # type assert/convert lintexpr(ex.args[1], ctx) diff --git a/src/blocks.jl b/src/blocks.jl index 66d328d..1492116 100644 --- a/src/blocks.jl +++ b/src/blocks.jl @@ -102,7 +102,7 @@ function expr_similar_score(e1::Expr, e2::Expr, base::Float64 = 1.0) return score end -function test_similarity_string{T<:AbstractString}(str::T) +function test_similarity_string(str::T) where T<:AbstractString i = start(str) firstexpr = nothing lastexpr = nothing diff --git a/src/cli.jl b/src/cli.jl index 632d857..66df28f 100644 --- a/src/cli.jl +++ b/src/cli.jl @@ -1,9 +1,16 @@ +include("expression_iterator.jl") +using Compat function lintpkg(pkg::AbstractString) - p = joinpath(Pkg.dir(pkg), "src", basename(pkg) * ".jl") - if !ispath(p) - throw("cannot find path: " * p) + if occursin("/", pkg) # pkg is a file path + return LintResult(lintpkgforfile(pkg)) + end + + try + p = Base.find_package(pkg) + LintResult(lintpkgforfile(p)) + catch + throw("cannot find package: " * pkg) end - LintResult(lintpkgforfile(p)) end """ @@ -15,7 +22,7 @@ If file is in base lint all files in base dir. function lintpkgforfile(path::AbstractString, ctx::LintContext=LintContext()) path = abspath(path) if ispath(ctx.path) - if is_windows() + if Sys.iswindows() len = count(x -> x == '\\', path) else len = count(x -> x == '/', path) - 1 @@ -74,34 +81,27 @@ function lintfile(f::AbstractString, code::AbstractString) LintResult(msgs) end +"Lint over each expression in each line. + +Calls `lintexpr` over each parseable-parsed expression. +Each parse is called over each line." function _lintstr(str::AbstractString, ctx::LintContext, lineoffset = 0) - linecharc = cumsum(map(x->endof(x)+1, split(str, "\n", keep=true))) - numlines = length(linecharc) - i = start(str) - while !done(str,i) - problem = false - ex = nothing - linerange = searchsorted(linecharc, i) - if linerange.start > numlines # why is it not donw? - break - else - linebreakloc = linecharc[linerange.start] + non_empty_lines=split(str, "\n", limit=0, keepempty=false) + offset_where_last_expression_ends=nothing + try + for (ex, line_begin, line_end) in ExpressionIterator.each_expression_and_offset(str) + # inform context of current line + ctx.line = ctx.lineabs = line_end + lineoffset +1 + lintexpr(ex, ctx) end - if linebreakloc == i || isempty(strip(str[i:(linebreakloc-1)]))# empty line - i = linebreakloc + 1 - continue + catch y + # report an unexpected error + # end-of-input and parsing errors are expected + if typeof(y) != Meta.ParseError || y.msg != "end of input" + msg(ctx, :E111, string(y)) end - ctx.line = ctx.lineabs = linerange.start + lineoffset - try - (ex, i) = parse(str,i) - catch y - if typeof(y) != ParseError || y.msg != "end of input" - msg(ctx, :E111, string(y)) - end - break - end - lintexpr(ex, ctx) end + end """ diff --git a/src/compat.jl b/src/compat.jl deleted file mode 100644 index 0932dd5..0000000 --- a/src/compat.jl +++ /dev/null @@ -1,41 +0,0 @@ -module LintCompat - -export BROADCAST, flatten - -# TODO: remove when 0.5 support dropped -function BROADCAST(f, x::Nullable) - if isnull(x) - Nullable() - else - Nullable(f(get(x))) - end -end - -# TODO: move to a different repository -""" - flatten(f, x::Nullable{<:Nullable}) :: Nullable - -Unwrap one layer of a two-layed `Nullable` object. - -Often combined with broadcast as in `flatten(broadcast(f, x))`, which is like -`broadcast(f, x)`, except returns the result of `f` directly. Expects `f` to -return a `Nullable` value. -""" -function flatten{T}(x::Nullable{Nullable{T}}) - if isnull(x) - Nullable{T}() - else - get(x) - end -end - -# fallback method for e.g. Nullable{Any}, Nullable{Union{}} -function flatten(x::Nullable) - if isnull(x) - Nullable() - else - get(x) :: Nullable - end -end - -end diff --git a/src/curly.jl b/src/curly.jl index 3084dc7..dd12026 100644 --- a/src/curly.jl +++ b/src/curly.jl @@ -16,7 +16,7 @@ const CURLY_CONTRACTS = Dict{Symbol, Any}( function lintcurly(ex::Expr, ctx::LintContext) head = ex.args[1] - if head == :Ptr && length(ex.args) == 2 && ex.args[2] == :Void + if head == :Ptr && length(ex.args) == 2 && ex.args[2] == :Nothing return end contract = get(CURLY_CONTRACTS, head, nothing) @@ -28,8 +28,9 @@ function lintcurly(ex::Expr, ctx::LintContext) continue # grandfathered else t = guesstype(a, ctx) - if !(t <: Type || t == Symbol || isbits(t) || t == Any) + if !(t <: Type || t == Symbol || isbitstype(t) || t == Any) msg(ctx, :W441, a, "probably illegal use inside curly") + elseif contract != nothing if i - 1 > length(contract) msg(ctx, :W446, head, "too many type parameters") diff --git a/src/dict.jl b/src/dict.jl index 5ad1e22..bf07a9e 100644 --- a/src/dict.jl +++ b/src/dict.jl @@ -7,7 +7,7 @@ function lintdict(ex::Expr, ctx::LintContext) if ispairexpr(a) keyexpr = lexicalfirst(a) lit = lexicalvalue(keyexpr) - if !isnull(lit) + if lit !== nothing if keyexpr in ks msg(ctx, :E334, keyexpr, "duplicate key in Dict") end @@ -16,7 +16,7 @@ function lintdict(ex::Expr, ctx::LintContext) for (j,s) in [(lexicalfirst,ktypes), (lexicallast,vtypes)] kvexpr = j(a) typeguess = lexicaltypeof(kvexpr) - if isleaftype(typeguess) + if isconcretetype(typeguess) push!(s, typeguess) elseif isexpr(kvexpr, :call) && in(kvexpr.args[1], [:Date, :DateTime]) # TODO: use the existing guesstype infrastructure diff --git a/src/dynamic.jl b/src/dynamic.jl index 307570c..5c97b00 100644 --- a/src/dynamic.jl +++ b/src/dynamic.jl @@ -2,12 +2,12 @@ Dynamically import the top-level module given by `sym`, and return it if possible. """ -function dynamic_import_toplevel_module(sym)::Nullable{Module} +function dynamic_import_toplevel_module(sym)::Union{Module, Nothing} info("dynamic import: $sym") try eval(Main, :(import $sym)) - Nullable(getfield(Main, sym)) + getfield(Main, sym) catch - Nullable() + nothing end end diff --git a/src/expression_iterator.jl b/src/expression_iterator.jl new file mode 100644 index 0000000..f0d4d69 --- /dev/null +++ b/src/expression_iterator.jl @@ -0,0 +1,85 @@ +module ExpressionIterator +"""Iterator of Substrings on which each contains a line""" +each_line_iterator(s::AbstractString) = Channel(c->begin + for substring in split(s, "\n", limit=0, keepempty=false) + put!(c, substring) + end + end, ctype=SubString{String}) + +struct EachExpressionAndOffset + original::String + next_line_offset_it # `lines_offsets(…)` +end + +""" Get [begin, end] offsets for each line (end is inclusive)""" +lines_offsets(s::AbstractString) = map(line->begin + real_offset=line.offset +1 # SubString.offset + 1 ↔ String.index + (real_offset, real_offset + length(line) -1) # TODO(felipe) use `prevind` ? + end, each_line_iterator(s)) + +"""points to info about "where should we get our *next* expression""" +struct _EachExpressionAndOffsetState + maybe_next_line_offset # as in ` = iterate(…)` + offset_where_last_expression_ends::Union{Nothing,Int} +end + +_EachExpressionAndOffsetState(iter::EachExpressionAndOffset) = _EachExpressionAndOffsetState( + Base.iterate(iter.next_line_offset_it), + nothing) + +function Base.iterate(iter::EachExpressionAndOffset, state=_EachExpressionAndOffsetState(iter)) #::Union{Nothing, Tuple{EachExpressionAndOffset, _EachExpressionAndOffsetState}} + if state.maybe_next_line_offset == nothing + # no more lines → we're done + return nothing + end + + # line ↔ [begin, end] offsets that describe a line + + # move next-line-wards … + (current_line, current_line_state) = state.maybe_next_line_offset + (line_begin, line_end) = current_line + maybe_next_line = Base.iterate(iter.next_line_offset_it, current_line_state) + + # … at least we catch up with last parsed expression + if state.offset_where_last_expression_ends !== nothing + while line_begin < state.offset_where_last_expression_ends + if maybe_next_line isa Nothing + # ran out of lines with the last expression → nothing left to parse + return nothing + end + # update current line info + (current_line, current_line_state) = maybe_next_line + (line_begin, line_end) = current_line + + # update next line info + maybe_next_line = Base.iterate(iter.next_line_offset_it, current_line_state) + end + end + + (ex, i_for_end_of_expression) = Meta.parse(iter.original, line_begin) + iter_value=(ex, line_begin, line_end) + iter_state=_EachExpressionAndOffsetState(maybe_next_line, + i_for_end_of_expression) + return (iter_value, iter_state) +end + +each_expression_and_offset(original::AbstractString) = EachExpressionAndOffset(original, lines_offsets(original)) + +function Base.length(iter::EachExpressionAndOffset) + count=0 + for ex in iter + count += 1 + end + count +end + +each_expression(original::AbstractString) = map(ex_off->begin + (ex, line_begin, line_end) = ex_off + ex + end, each_expression_and_offset(original)) + + + + + +end # module ExpressionIterator diff --git a/src/exprutils.jl b/src/exprutils.jl index 000393b..cfc5099 100644 --- a/src/exprutils.jl +++ b/src/exprutils.jl @@ -1,7 +1,7 @@ module ExpressionUtils using Base.Meta -using ..LintCompat +# using ..LintCompat export split_comparison, simplify_literal, ispairexpr, isliteral, lexicaltypeof, lexicalfirst, lexicallast, lexicalvalue, @@ -89,29 +89,29 @@ lexicallast(x) = VERSION < v"0.6.0-dev.2613" ? x.args[2] : x.args[3] # changed b """ Return `true` if the value represented by expression `x` is exactly `x` itself; -that is, `x` is not `Expr`, `QuoteNode`, or `Symbol`. +that is, `x` is not `Expr`, `LineNumberNode`, `QuoteNode`, or `Symbol`. """ -isliteral(x) = !isa(x, Expr) && !isa(x, QuoteNode) && !isa(x, Symbol) +isliteral(x) = !any(t->isa(x, t), [Expr LineNumberNode QuoteNode Symbol]) """ - lexicalvalue(x) :: Nullable{Any} + lexicalvalue(x) :: Union{Any, Nothing} -If `x` is a literal, or a quoted literal, return that literal wrapped in a -`Nullable`. Otherwise, return `Nullable{Any}()`. +If `x` is a literal, or a quoted literal, return that literal. +Otherwise, return `nothing`. """ function lexicalvalue(x) if isliteral(x) - Nullable{Any}(x) + x elseif isexpr(x, :quote) if isexpr(x.args[1], :($)) lexicalvalue(x.args[1].args[1]) else - Nullable{Any}(x.args[1]) + x.args[1] end elseif isa(x, QuoteNode) - Nullable{Any}(x.value) + x.value else - Nullable{Any}() + nothing end end @@ -127,7 +127,13 @@ type is defined as That is, the maximal amount of information detectable from the lexical context alone. """ -lexicaltypeof(x) = get(BROADCAST(typeof, lexicalvalue(x)), Any) +function lexicaltypeof(x) + lex_value = lexicalvalue(x) + if lex_value == nothing + return Nothing + end + broadcast(typeof, lex_value) +end """ expand_trivial_calls(x) @@ -137,12 +143,8 @@ expression nodes that almost always lower to calls but are not represented as such. The special case lowering of `A*B'` is neglected. """ function expand_trivial_calls(ex) - if isexpr(ex, :(:)) - Expr(:call, :colon, ex.args...) - elseif isexpr(ex, Symbol("'")) + if isexpr(ex, Symbol("'")) Expr(:call, :ctranspose, ex.args...) - elseif isexpr(ex, Symbol(".'")) - Expr(:call, :transpose, ex.args...) elseif isexpr(ex, :(=>)) Expr(:call, :(=>), ex.args...) elseif isexpr(ex, :vect) @@ -158,33 +160,32 @@ end Return a tuple `(LHS, RHS)` by expanding the expression as if it represents a single assignment. For example, `x += y` is expanded to `x = x + y`, which is -returned as `(x, x + y)`. Return `Nullable()` if the argument could not be +returned as `(x, x + y)`. Return `nothing` if the argument could not be interpreted as an assignment. """ -function expand_assignment(expr::Expr)::Nullable +function expand_assignment(expr::Expr)::Union{Any, Nothing} op = expr.head if op in COMPARISON_OPS - Nullable() + nothing elseif op == :(=) @assert length(expr.args) == 2 - Nullable((expr.args[1], expr.args[2])) + (expr.args[1], expr.args[2]) else str = string(op) if str[end] == '=' fop = Symbol(str[1:end-1]) @assert length(expr.args) == 2 - Nullable((expr.args[1], Expr(:call, fop, expr.args[1], - expr.args[2]))) + (expr.args[1], Expr(:call, fop, expr.args[1], expr.args[2])) else - Nullable() + nothing end end end -expand_assignment(_) = Nullable() +expand_assignment(_) = nothing const COMPARISON_OPS = [:(==), :(<), :(>), :(<=), :(>=), :(!=)] -immutable Import +struct Import """ The number of dots preceding the import. If `dots` is `0`, then this is a toplevel import (i.e., from Main, but additionally requiring the module if @@ -216,14 +217,14 @@ path(imp::Import) = imp.path kind(imp::Import) = imp.kind """ - understand_import(ex)::Nullable{Import} + understand_import(ex)::Union{Import, Nothing} Return an `Import` from extracting the important semantics from the given -expression `ex`, or `Nullable()` otherwise. +expression `ex`, or `nothing` otherwise. """ -function understand_import(ex)::Nullable{Import} +function understand_import(ex)::Union{Import, Nothing} if !isexpr(ex, [:using, :import, :importall]) - return Nullable() + return nothing end kind = ex.head diff --git a/src/functions.jl b/src/functions.jl index f1eaeec..61fc69d 100644 --- a/src/functions.jl +++ b/src/functions.jl @@ -24,7 +24,7 @@ end function istype(ctx::LintContext, x) obj = abstract_eval(ctx, x) - !isnull(obj) && isa(get(obj), Type) + obj !== nothing && isa(obj, Type) end # if ctorType isn't symbol("") then we are in the context of @@ -58,7 +58,7 @@ function lintfunction(ex::Expr, ctx::LintContext; ctorType = Symbol(""), isstage if isa(fname, Symbol) # TODO: warn if it's a using'd thing finfo = lookup(ctx.current, fname) - if isnull(finfo) + if finfo == nothing set!(ctx.current, fname, VarInfo(location(ctx), Function)) else # TODO: warn if it's something bad @@ -66,7 +66,7 @@ function lintfunction(ex::Expr, ctx::LintContext; ctorType = Symbol(""), isstage end ctx.scope = string(fname) - if fname != Symbol("") && !contains(ctx.file, "deprecate") + if fname != Symbol("") && !occursin(ctx.file, "deprecate") isDeprecated = functionIsDeprecated(ex.args[1]) if isDeprecated != nothing && !pragmaexists("Ignore deprecated $fname", ctx.current) msg(ctx, :E211, ex.args[1], "$(isDeprecated.message); See: " * @@ -102,7 +102,7 @@ function lintfunctionbody(ctx::LintContext, mi::MethodInfo) end if istype(ctx, typeconstraint) dt = parsetype(ctx, typeconstraint) - if isleaftype(dt) + if isconcretetype(dt) msg(ctx, :E513, adt, "leaf type as a type constraint makes no sense") end end @@ -240,6 +240,7 @@ function lintfunctionbody(ctx::LintContext, mi::MethodInfo) elseif haskey(typeRHShints, s) vi.typeactual = typeRHShints[s] end + catch end end @@ -307,7 +308,6 @@ function lintfunctioncall(ex::Expr, ctx::LintContext; inthrow::Bool=false) msg(ctx, :E311, inclfile, "cannot find include file") return else - #println("include: ", inclfile) lintinclude(ctx, inclfile) end else @@ -318,7 +318,7 @@ function lintfunctioncall(ex::Expr, ctx::LintContext; inthrow::Bool=false) end func = abstract_eval(ctx, ex.args[1]) - if !isnull(func) && isa(get(func), Type) && get(func) <: Dict + if func !== nothing && isa(func, Type) && func <: Dict lintdict(ex, ctx) end @@ -366,12 +366,13 @@ function lintfunctioncall(ex::Expr, ctx::LintContext; inthrow::Bool=false) if !inthrow && isa(ex.args[1], Symbol) s = lowercase(string(ex.args[1])) - if contains(s,"error") || contains(s,"exception") || contains(s,"mismatch") || contains(s,"fault") + if occursin(s,"error") || occursin(s,"exception") || occursin(s,"mismatch") || occursin(s,"fault") try dt = parsetype(ctx, ex.args[1]) if dt <: Exception && !pragmaexists( "Ignore unthrown " * string(ex.args[1]), ctx.current) msg(ctx, :W448, string(ex.args[1]) * " is an Exception but it is not enclosed in a throw()") end + catch end end end diff --git a/src/guesstype.jl b/src/guesstype.jl index 0bcc438..5d3ac52 100644 --- a/src/guesstype.jl +++ b/src/guesstype.jl @@ -3,16 +3,16 @@ using Base: isexported """ stdlibobject(name::Symbol) -If `name` is an export of Base or Core, return `Nullable{Any}(x)` where `x` is -the object itself. Otherwise, return `Nullable{Any}()`. +If `name` is an export of Base or Core, return `x` where `x` is +the object itself. Otherwise, return `nothing`. """ function stdlibobject(ex::Symbol) if isexported(Base, ex) && isdefined(Base, ex) - Nullable{Any}(getfield(Base, ex)) + getfield(Base, ex) elseif isexported(Core, ex) && isdefined(Core, ex) - Nullable{Any}(getfield(Core, ex)) + getfield(Core, ex) else - Nullable{Any}() + nothing end end @@ -23,12 +23,12 @@ Obtain a supertype of the type represented by `ex`. """ function parsetype(ctx::LintContext, ex) obj = abstract_eval(ctx, ex) - if !isnull(obj) && isa(get(obj), Type) - get(obj) + if obj !== nothing && isa(obj, Type) + obj elseif isexpr(ex, :curly) obj = abstract_eval(ctx, ex.args[1]) - if !isnull(obj) && isa(get(obj), Type) && get(obj) !== Union - get(obj) + if obj !== nothing && isa(obj, Type) && obj !== Union + obj else Any end @@ -37,75 +37,135 @@ function parsetype(ctx::LintContext, ex) end end +"""Returns true if ex is `:` or `Colon()`""" +function iscolon(ex)::Bool + return ex == :(:) || ex == :(Colon()) +end + function guesstype(ex::Symbol, ctx::LintContext) result = lookup(ctx, ex) - if isnull(result) + if result == nothing Any # conservative guess else - get(result).typeactual + result.typeactual end end -function guesstype(ex::Expr, ctx::LintContext)::Type - ex = ExpressionUtils.expand_trivial_calls(ex) +# Tag definitions +abstract type ExpressionTag end +struct TupleTag <: ExpressionTag end +struct ColonTag <: ExpressionTag end +struct BlockTag <: ExpressionTag end +struct ReturnTag <: ExpressionTag end +struct MacroCallTag <: ExpressionTag end +struct CallTag <: ExpressionTag end +struct RefTag <: ExpressionTag end +struct ComparisonTag <: ExpressionTag end +struct CurlyTag <: ExpressionTag end +struct TernaryIfTag <: ExpressionTag end +struct LambdaTag <: ExpressionTag end +struct AssignTag <: ExpressionTag end +struct DefaultTag <: ExpressionTag end +# condition → tag +# TODO(felipe): Use Match.jl +function get_tag_per_condition(ex::Expr) if isexpr(ex, :tuple) - ts = Type[] - for a in ex.args - push!(ts, guesstype(a, ctx)) - end - return Tuple{ts...} + return TupleTag end - if isexpr(ex, :(::)) && length(ex.args) == 2 - return parsetype(ctx, ex.args[2]) + return ColonTag end - if isexpr(ex, :block) - return isempty(ex.args) ? Void : guesstype(ex.args[end], ctx) + return BlockTag end - if isexpr(ex, :return) - tmp = guesstype(ex.args[1], ctx) - return tmp + return ReturnTag end - if isexpr(ex, :call) - fn = ex.args[1] - if any(x -> isexpr(x, :kw) || isexpr(x, :(...)), ex.args[2:end]) - # TODO: smarter way to deal with kw/vararg - return Any - end - argtypes = map(x -> guesstype(x, ctx), ex.args[2:end]) - - # infer return types of Base functions - obj = abstract_eval(ctx, fn) - type_argtypes = [isa(t, Type) ? t : Any for t in argtypes] - if !isnull(obj) - inferred = StaticTypeAnalysis.infertype(get(obj), type_argtypes) - if inferred ≠ Any - return inferred - end - end + return CallTag end - if isexpr(ex, :macrocall) - if ex.args[1] == Symbol("@sprintf") - return String - elseif ex.args[1] == Symbol("@compat") - return guesstype(ex.args[2], ctx) + return MacroCallTag + end + if isexpr(ex, :ref) # it could be a ref a[b] or an array Int[1,2,3], Vector{Int}[] + return RefTag + end + if isexpr(ex, :comparison) + return ComparisonTag + end + if isexpr(ex, :curly) + return CurlyTag + end + # simple if statement e.g. test ? 0 : 1 + if isexpr(ex, :if) && 2 ≤ length(ex.args) ≤ 3 + return TernaryIfTag + end + + if isexpr(ex, :(->)) + return LambdaTag + end + if isexpr(ex, :(=)) + return AssignTag + end + + return DefaultTag +end + +# Specializations for each type below +function guesstype(ex::Expr, ctx::LintContext, ::Type{TupleTag})::Type + ts = Type[] + for a in ex.args + push!(ts, guesstype(a, ctx)) + end + return Tuple{ts...} +end + +function guesstype(ex::Expr, ctx::LintContext, ::Type{ColonTag})::Type + parsetype(ctx, ex.args[2]) +end + +function guesstype(ex::Expr, ctx::LintContext, ::Type{BlockTag})::Type + isempty(ex.args) ? Nothing : guesstype(ex.args[end], ctx) +end + +function guesstype(ex::Expr, ctx::LintContext, ::Type{ReturnTag})::Type + guesstype(ex.args[1], ctx) +end + +function guesstype(ex::Expr, ctx::LintContext, ::Type{CallTag})::Type + fn = ex.args[1] + if any(x -> isexpr(x, :kw) || isexpr(x, :(...)), ex.args[2:end]) + # TODO: smarter way to deal with kw/vararg + return Any + end + argtypes = map(x -> guesstype(x, ctx), ex.args[2:end]) + + # infer return types of Base functions + obj = abstract_eval(ctx, fn) + type_argtypes = [isa(t, Type) ? t : Any for t in argtypes] + if obj !== nothing + inferred = StaticTypeAnalysis.infertype(obj, type_argtypes) + if inferred ≠ Any + return inferred end end + return Any +end - if isexpr(ex, :curly) - return Type +function guesstype(ex::Expr, ctx::LintContext, ::Type{MacroCallTag})::Type + if ex.args[1] == Symbol("@sprintf") + return String + elseif ex.args[1] == Symbol("@compat") + return guesstype(ex.args[2], ctx) end +end - if isexpr(ex, :ref) # it could be a ref a[b] or an array Int[1,2,3], Vector{Int}[] +function guesstype(ex::Expr, ctx::LintContext, ::Type{RefTag})::Type if isexpr(ex.args[1], :curly) # must be a datatype, right? elt = abstract_eval(ctx, ex.args[1]) - if !isnull(elt) && isa(get(elt), Type) - return Vector{get(elt)} + if elt !== nothing && isa(elt, Type) + return Vector{elt} else return Vector end @@ -115,8 +175,8 @@ function guesstype(ex::Expr, ctx::LintContext)::Type what = registersymboluse(ex.args[1], ctx) if what <: Type elt = abstract_eval(ctx, ex.args[1]) - if !isnull(elt) && isa(get(elt), Type) - return Vector{get(elt)} + if elt !== nothing && isa(elt, Type) + return Vector{elt} else return Vector end @@ -128,33 +188,31 @@ function guesstype(ex::Expr, ctx::LintContext)::Type # we are in a context of a constructor of a new type, so it's # difficult to figure out the content return Any - elseif partyp <: AbstractArray && !(partyp <: Range) + elseif partyp <: AbstractArray && !(partyp <: AbstractRange) eletyp = StaticTypeAnalysis.eltype(partyp) + nd=0 try nd = ndims(partyp) # This may throw if we couldn't infer the dimension - tmpdim = nd - (length(ex.args)-1) - if tmpdim < 0 - if nd == 0 && ex.args[2] == 1 # ok to do A[1] for a 0-dimensional array - return eletyp - else - msg(ctx, :E436, ex, "more indices than dimensions") - return Any - end - end - - for i in 2:length(ex.args) - if ex.args[i] == :(:) || isexpr(ex.args[i], :call) && - ex.args[i].args[1] == :Colon - tmpdim += 1 - end - end - if tmpdim != 0 - return Array{eletyp, tmpdim} # is this strictly right? - else + catch + return Any + end + dim_diff = nd - (length(ex.args)-1) + if dim_diff < 0 + if nd == 0 && ex.args[2] == 1 # ok to do A[1] for a 0-dimensional array return eletyp + else + msg(ctx, :E436, ex, "more indices than dimensions") + return Any end end - return Any + colon_arguments = filter(iscolon, ex.args[2:end]) + dim_diff+=length(colon_arguments) + + if dim_diff != 0 + return Array{eletyp, dim_diff} # is this strictly right? + else + return eletyp + end else argtypes = [guesstype(x, ctx) for x in ex.args] type_argtypes = [isa(t, Type) ? t : Any for t in argtypes] @@ -172,35 +230,49 @@ function guesstype(ex::Expr, ctx::LintContext)::Type end return inferred end - end +end - if isexpr(ex, :comparison) - return Bool - end +function guesstype(ex::Expr, ctx::LintContext, ::Type{ComparisonTag})::Type + Bool +end - # simple if statement e.g. test ? 0 : 1 - if isexpr(ex, :if) && 2 ≤ length(ex.args) ≤ 3 - tt = guesstype(ex.args[2], ctx) - ft = if length(ex.args) == 3 - guesstype(ex.args[3], ctx) - else - Void - end - if tt == ft - # we need this case because tt and ft might be symbols - return tt - elseif isa(tt, Type) && isa(ft, Type) - return Union{tt, ft} - else - return Any - end - end +function guesstype(ex::Expr, ctx::LintContext, ::Type{CurlyTag})::Type + Type +end - if isexpr(ex, :(->)) - return Function +function guesstype(ex::Expr, ctx::LintContext, ::Type{TernaryIfTag})::Type + tt = guesstype(ex.args[2], ctx) + ft = if length(ex.args) == 3 + guesstype(ex.args[3], ctx) + else + Nothing + end + if tt == ft + # we need this case because tt and ft might be symbols + return tt + elseif isa(tt, Type) && isa(ft, Type) + return Union{tt, ft} + else + return Any end +end - return Any +function guesstype(ex::Expr, ctx::LintContext, ::Type{LambdaTag})::Type + Function +end + +function guesstype(ex::Expr, ctx::LintContext, ::Type{DefaultTag})::Type + Any +end + +function guesstype(ex::Expr, ctx::LintContext, ::Type{AssignTag})::Type + return guesstype(ex.args[2], ctx) +end + +# `guesstype` dispatcher (resolves the tag) +function guesstype(ex::Expr, ctx::LintContext)::Type + ex = ExpressionUtils.expand_trivial_calls(ex) + return guesstype(ex, ctx, get_tag_per_condition(ex)) end guesstype(ex, ctx::LintContext) = lexicaltypeof(ex) diff --git a/src/knowndeprec.jl b/src/knowndeprec.jl index 73ada04..cce5d39 100644 --- a/src/knowndeprec.jl +++ b/src/knowndeprec.jl @@ -1,9 +1,9 @@ # all usage of deprecated functions are warning # all method extensions of deprecated generic functions are errors -type DeprecateInfo +struct DeprecateInfo funcname::Any - sig::Union{Void, Array{Any,1}} + sig::Union{Nothing, Array{Any,1}} message::Compat.String line::Int end @@ -14,17 +14,18 @@ function initDeprecateInfo() if sf === nothing return end - str = open(readstring, sf) - linecharc = cumsum(map(x->length(x)+1, @compat(split(str, "\n", keep=true)))) + str = read(sf, String) + linecharc = cumsum(map(x->length(x)+1, @compat(split(str, "\n", keepempty=true)))) - i = start(str) + itr = iterate(str) lineabs = 1 - while !done(str,i) + while itr !== nothing + (_, i) = itr problem = false ex = nothing - lineabs = searchsorted(linecharc, i).start + lineabs = first(searchsorted(linecharc, i)) try - (ex, i) = parse(str, i) + itr = Meta.parse(str, i) catch problem = true end @@ -86,7 +87,7 @@ function parseDeprecate(ex, lineabs) # can't deal with complex expressions like Broadcast.func yet return end - if in(funcname, [:depwarn, :firstcaller]) || contains(lowercase(string(funcname)), "deprecate") + if in(funcname, [:depwarn, :firstcaller]) || occursin("deprecate", lowercase(string(funcname))) # the first two are support functions. # Any function declaration that has "deprecate" in the name... # well, the user/developer should know what they are in for. @@ -122,7 +123,7 @@ function parseDeprecate(ex, lineabs) oldcall = sprint(io->Base.show_unquoted(io,old)) newcall = sprint(io->Base.show_unquoted(io,new)) - if contains(string(funcname), "deprecate") + if occursin("deprecate", string(funcname)) return end if sig == nothing @@ -227,6 +228,7 @@ function funcMatchesDeprecateInfo(sig, di::DeprecateInfo) ret = false try ret = eval(:($s1 <: $s2)) + catch end return ret elseif typeof(s1) == Expr && typeof(s2) == Expr && s1.head == s2.head && length(s1.args)==length(s2.args) diff --git a/src/linttypes.jl b/src/linttypes.jl index 6191fea..b533fbd 100644 --- a/src/linttypes.jl +++ b/src/linttypes.jl @@ -5,7 +5,7 @@ include("types/lintmessage.jl") # tree toward the root import Base: parent -function Typeof(x::ANY) +function Typeof(@nospecialize x) if x === Vararg typeof(x) elseif isa(x, Type) @@ -16,12 +16,12 @@ function Typeof(x::ANY) end @compat abstract type AdditionalVarInfo end -extractobject(_::AdditionalVarInfo) = Nullable() +extractobject(_::AdditionalVarInfo) = nothing """ A struct with information about a variable. """ -type VarInfo +mutable struct VarInfo location::Location typeactual::Type @@ -35,11 +35,11 @@ type VarInfo source::Symbol "Additional known information about the particular object." - extra::Nullable{AdditionalVarInfo} + extra::Union{AdditionalVarInfo, Nothing} VarInfo(loc::Location = UNKNOWN_LOCATION, t::Type = Any; source::Symbol = :defined) = - new(loc, t, 0, source, Nullable()) + new(loc, t, 0, source, nothing) end VarInfo(vi::VarInfo; source::Symbol = :defined) = @@ -50,13 +50,17 @@ registeruse!(vi::VarInfo) = (vi.usages += 1; vi) usages(vi::VarInfo) = vi.usages source(vi::VarInfo) = vi.source function info!(vi::VarInfo, info::AdditionalVarInfo) - vi.extra = Nullable(info) + vi.extra = info +end + +function extractobject(vi::VarInfo) + if vi.extra !== nothing + extractobject(vi.extra) + end end -extractobject(vi::VarInfo) = - flatten(BROADCAST(extractobject, vi.extra)) -immutable ModuleInfo <: AdditionalVarInfo +struct ModuleInfo <: AdditionalVarInfo name :: Symbol globals :: Dict{Symbol, VarInfo} exports :: Set{Symbol} @@ -68,23 +72,23 @@ name(data::ModuleInfo) = data.name export!(data::ModuleInfo, sym::Symbol) = push!(data.exports, sym) exports(data::ModuleInfo) = data.exports set!(data::ModuleInfo, sym::Symbol, info::VarInfo) = data.globals[sym] = info -function lookup(data::ModuleInfo, sym::Symbol)::Nullable{VarInfo} +function lookup(data::ModuleInfo, sym::Symbol)::Union{VarInfo, Nothing} if sym in keys(data.globals) return data.globals[sym] end # check standard library val = stdlibobject(sym) - if !isnull(val) - vi = VarInfo(UNKNOWN_LOCATION, Typeof(get(val))) - info!(vi, StandardLibraryObject(get(val))) + if val !== nothing + vi = VarInfo(UNKNOWN_LOCATION, Typeof(val)) + info!(vi, StandardLibraryObject(val)) return vi end - return Nullable{VarInfo}() + return nothing end -immutable MethodInfo <: AdditionalVarInfo +struct MethodInfo <: AdditionalVarInfo # signature :: ... location :: Location body :: Any @@ -96,22 +100,22 @@ location(mi::MethodInfo) = mi.location The binding is known to reference a standard library object. The "standard library" consists of `Core`, `Base`, `Compat`, and their submodules. """ -immutable StandardLibraryObject <: AdditionalVarInfo +struct StandardLibraryObject <: AdditionalVarInfo object :: Any end # TODO: remove {typeof(x.object)} part when #21397 fixed extractobject(x::StandardLibraryObject) = - Nullable{typeof(x.object)}(x.object) + x.object # TODO: currently, this is not actually used -immutable FunctionInfo <: AdditionalVarInfo +struct FunctionInfo <: AdditionalVarInfo name :: Symbol methods :: Vector{MethodInfo} end name(data::FunctionInfo) = data.name method!(data::FunctionInfo, mi::MethodInfo) = push!(data.methods, mi) -type PragmaInfo +struct PragmaInfo location :: Location used :: Bool end @@ -130,8 +134,8 @@ localset!(ctx::_LintContext, sym::Symbol, info::VarInfo) = set!(ctx, sym, info) # A special context for linting a `module` keyword -immutable ModuleContext <: _LintContext - parent :: Nullable{_LintContext} +struct ModuleContext <: _LintContext + parent :: Union{_LintContext, Nothing} data :: ModuleInfo pragmas :: Dict{String, PragmaInfo} @@ -144,13 +148,13 @@ immutable ModuleContext <: _LintContext ModuleContext(parent, data) = new(parent, data, Dict(), []) end -isroot(mctx::ModuleContext) = isnull(mctx.parent) +isroot(mctx::ModuleContext) = mctx.parent == nothing pragmas(mctx::ModuleContext) = mctx.pragmas parent(mctx::ModuleContext) = get(mctx.parent) data(mctx::ModuleContext) = mctx.data lookup(mctx::ModuleContext, args...; kwargs...) = lookup(mctx.data, args...; kwargs...) -locallookup(mctx::ModuleContext, name::Symbol) = Nullable() +locallookup(mctx::ModuleContext, name::Symbol) = nothing set!(mctx::ModuleContext, sym::Symbol, info::VarInfo) = set!(mctx.data, sym, info) function defer!(mctx::ModuleContext, mi::MethodInfo) @@ -164,7 +168,7 @@ function finish(ctx::ModuleContext, cursor) vi = ctx.data.globals[x] if source(vi) ∉ [:imported, :used] # allow imported/used bindings loc = location(vi) - if !isnull(stdlibobject(x)) + if stdlibobject(x) !== nothing msg(cursor, :I343, x, "global variable defined at $loc with same name as export from Base") end end @@ -174,7 +178,7 @@ function finish(ctx::ModuleContext, cursor) end end -type LocalContext <: _LintContext +struct LocalContext <: _LintContext parent :: _LintContext declglobs :: Set{Symbol} localvars :: Dict{Symbol, VarInfo} @@ -204,12 +208,12 @@ function finish(ctx::LocalContext, cursor) if usages(ctx.localvars[x]) == 0 && !startswith(string(x), "_") # TODO: a better line number msg(cursor, :I340, x, "unused local variable, defined at $loc") - elseif !isnull(stdlibobject(x)) + elseif stdlibobject(x) !== nothing msg(cursor, :I342, x, "local variable defined at $loc shadows export from Base") - elseif !isnull(lookup(tl, x)) - msg(cursor, :I341, x, "local variable defined at $loc shadows global variable defined at $(location(get(lookup(tl, x))))") - elseif !isnull(lookup(nl, x)) - msg(cursor, :I344, x, "local variable defined at $loc shadows local variable defined at $(location(get(lookup(nl, x))))") + elseif lookup(tl, x) !== nothing + msg(cursor, :I341, x, "local variable defined at $loc shadows global variable defined at $(location(lookup(tl, x)))") + elseif lookup(nl, x) !== nothing + msg(cursor, :I344, x, "local variable defined at $loc shadows local variable defined at $(location(lookup(nl, x)))") end end end @@ -217,9 +221,9 @@ end function set!(s::LocalContext, name::Symbol, vi::VarInfo) # TODO: check if it's soft or hard local scope var = locallookup(s, name) - if !isnull(var) + if var !== nothing # TODO: warn about type instability? - get(var).typeactual = Union{get(var).typeactual, vi.typeactual} + var.typeactual = Union{var.typeactual, vi.typeactual} else localset!(s, name, vi) end @@ -237,7 +241,7 @@ function globalset!(s::LocalContext, name::Symbol, vi::VarInfo) globalset!(parent(s), name, vi) end -function locallookup(ctx::LocalContext, name::Symbol)::Nullable{VarInfo} +function locallookup(ctx::LocalContext, name::Symbol)::Union{VarInfo, Nothing} if name in keys(ctx.localvars) return ctx.localvars[name] elseif name in ctx.declglobs @@ -247,16 +251,16 @@ function locallookup(ctx::LocalContext, name::Symbol)::Nullable{VarInfo} end end -function lookup(ctx::LocalContext, name::Symbol)::Nullable{VarInfo} +function lookup(ctx::LocalContext, name::Symbol)::Union{VarInfo, Nothing} var = locallookup(ctx, name) - if isnull(var) + if var == nothing lookup(toplevel(ctx), name) else var end end -@auto_hash_equals immutable LintIgnore +@auto_hash_equals struct LintIgnore errorcode :: Symbol variable :: String end @@ -267,7 +271,7 @@ const LINT_IGNORE_DEFAULT = [ LintIgnore(:W651, "") ] -type LintContext +mutable struct LintContext file :: String "Current line number." line :: Int @@ -285,7 +289,7 @@ type LintContext current :: _LintContext function LintContext() mdata = ModuleInfo(:Main) - mctx = ModuleContext(Nullable(), mdata) + mctx = ModuleContext(nothing, mdata) new("none", 0, 1, "", ".", AbstractString[], 0, 0, LintMessage[], _ -> true, copy(LINT_IGNORE_DEFAULT), 0, mctx) @@ -318,7 +322,7 @@ function withcontext(f, ctx::LintContext, temp::_LintContext) ctx.current = old end -function lookup(ctx::LintContext, sym::Symbol)::Nullable{VarInfo} +function lookup(ctx::LintContext, sym::Symbol)::Union{VarInfo, Nothing} lookup(ctx.current, sym) end @@ -326,9 +330,8 @@ function msg(ctx::LintContext, code::Symbol, variable, str::AbstractString) variable = string(variable) m = LintMessage(location(ctx), code, ctx.scope, variable, str) # filter out messages to ignore - i = max(findfirst(ctx.ignore, LintIgnore(code, variable)), - findfirst(ctx.ignore, LintIgnore(code, ""))) - if i == 0 + if !(LintIgnore(code, variable) in ctx.ignore || + LintIgnore(code, "") in ctx.ignore) push!(ctx.messages, m) end end diff --git a/src/modules.jl b/src/modules.jl index 85e583d..2c7b7e3 100644 --- a/src/modules.jl +++ b/src/modules.jl @@ -23,21 +23,22 @@ function lintmodule(ex::Expr, ctx::LintContext) withcontext(ctx, mctx) do lintexpr(ex.args[3], ctx) for sym in exports(ctx.current) - if isnull(lookup(ctx.current, sym)) + if lookup(ctx.current, sym) == nothing msg(ctx, :W361, sym, "exporting undefined symbol") end end end - info!(get(lookup(ctx.current, name)), data(mctx)) + @assert lookup(ctx.current, name) !== nothing + info!(lookup(ctx.current, name), data(mctx)) end """ - walkmodulepath(m::Module, path::AbstractVector{Symbol}) :: Nullable + walkmodulepath(m::Module, path::AbstractVector{Symbol}) :: Union{Any, Nothing} Walk the module `m` based on the path descripton given by a series of symbols describing submodules of `m`. For example, if `m === Base` and `path == -[:Iterators, :take]`, then this returns `Base.Iterators.take` wrapped in a -`Nullable`. If an error occurs at any step, `Nullable()` is returned. +[:Iterators, :take]`, then this returns `Base.Iterators.take`. If an error +occurs at any step, `nothing` is returned. ```jldoctest julia> using Lint.walkmodulepath @@ -48,16 +49,16 @@ julia> walkmodulepath(Compat, [:Iterators, :take]) take (generic function with 2 methods) ``` """ -function walkmodulepath(m::Module, path::AbstractVector{Symbol})::Nullable +function walkmodulepath(m::Module, path::AbstractVector{Symbol})::Union{Any, Nothing} # walk down m until we get to the requested symbol for s in path try m = getfield(m, s) catch - return Nullable() + return nothing end end - Nullable(m) + m end function importobject(ctx::LintContext, name::Symbol, obj, source::Symbol) @@ -71,12 +72,12 @@ function importintocontext(m::Module, p::AbstractVector{Symbol}, source::Symbol, getexports::Bool, ctx::LintContext) # walk down m until we get to the requested symbol maybem = walkmodulepath(m, @view(p[2:end])) - if isnull(maybem) + if maybem == nothing msg(ctx, :W360, join(string.(p), "."), "importing probably undefined symbol") return end - m = get(maybem) + m = maybem if getexports && isa(m, Module) for n in names(m) @@ -96,7 +97,8 @@ function lintimport(ex::Expr, ctx::LintContext) if ctx.quoteLvl > 0 return # not safe to import in quotes end - imp = get(understand_import(ex)) + imp = understand_import(ex) + @assert imp !== nothing @checktoplevel(ctx, kind(imp)) # Don't use modules protected by a guard (these can cause crashes!) @@ -114,13 +116,13 @@ function lintimport(ex::Expr, ctx::LintContext) elseif getexports # unfortunately, we need to import dynamically maybem = dynamic_import_toplevel_module(path(imp)[1]) - if isnull(maybem) + if maybem == nothing # TODO: make an effort to import the symbol? msg(ctx, :W101, path(imp)[1], "unfortunately, Lint could not determine the exports of this module") return end - m = get(maybem) + m = maybem importintocontext(m, path(imp), source, getexports, ctx) else set!(ctx.current, path(imp)[end], @@ -148,14 +150,14 @@ function lintimport(ex::Expr, ctx::LintContext) return end result = lookup(frommodule, s) - if isnull(result) + if result == nothing msg(ctx, :W360, join(string.(path(imp)), "."), "importing probably undefined symbol") return else - vi = get(result) - if vi.typeactual <: Module && !isnull(vi.extra) && isa(get(vi.extra), ModuleInfo) - frommodule = get(vi.extra) + vi = result + if vi.typeactual <: Module && vi.extra !== nothing && isa(vi.extra, ModuleInfo) + frommodule = vi.extra else canimport = false end @@ -164,12 +166,12 @@ function lintimport(ex::Expr, ctx::LintContext) set!(ctx.current, path(imp)[end], VarInfo(vi; source=source)) - if getexports && vi.typeactual <: Module && !isnull(vi.extra) && - isa(get(vi.extra), ModuleInfo) - for n in get(vi.extra).exports - nvi = lookup(get(vi.extra), n) - if !isnull(nvi) - set!(ctx.current, n, VarInfo(get(nvi); source=source)) + if getexports && vi.typeactual <: Module && vi.extra !== nothing && + isa(vi.extra, ModuleInfo) + for n in vi.extra.exports + nvi = lookup(vi.extra, n) + if nvi !== nothing + set!(ctx.current, n, VarInfo(nvi); source=source) else set!(ctx.current, n, VarInfo(location(ctx); source=source)) end diff --git a/src/pragma.jl b/src/pragma.jl index 9341b64..36c1c2c 100644 --- a/src/pragma.jl +++ b/src/pragma.jl @@ -1,12 +1,13 @@ function lintlintpragma(ex::Expr, ctx::LintContext) - if length(ex.args) >= 2 && isa(ex.args[2], AbstractString) - m = match(r"^((Print)|(Info)|(Warn)|(Error)) ((type)|(me)|(version)) +(.+)"s, ex.args[2]) - if m != nothing + # TODO(felipe) ↓ should do `@assert ex.args[1] == :(#= none:1 =#)`, but how to express the right hand expresion? + if length(ex.args) >= 2 && isa(ex.args[end], AbstractString) + m = match(r"^((Print)|(Info)|(Warn)|(Error)) ((type)|(me)|(version)) +(.+)"s, ex.args[end]) + if m !== nothing action = m.captures[1] infotype = m.captures[6] rest_str = m.captures[10] if infotype == "type" - v = parse(rest_str) + v = Meta.parse(rest_str) if isexpr(v, :incomplete) msg(ctx, :E138, rest_str, "incomplete pragma expression") str = "" diff --git a/src/result.jl b/src/result.jl index abb708a..95953f7 100644 --- a/src/result.jl +++ b/src/result.jl @@ -10,7 +10,7 @@ const LINT_RESULT_COLORS = Dict( A collection of `LintMessage`s. This behaves similarly to a vector of `LintMessage`s, but has different display behaviour. """ -immutable LintResult <: AbstractVector{LintMessage} +struct LintResult <: AbstractVector{LintMessage} messages::Array{LintMessage, 1} end diff --git a/src/statictype.jl b/src/statictype.jl index 6402d0f..2270929 100644 --- a/src/statictype.jl +++ b/src/statictype.jl @@ -2,42 +2,42 @@ module StaticTypeAnalysis macro lintpragma(ex); end -function __init__() - global const EQ_METHOD_FALSE = which(==, Tuple{Void, Int}) - global const CONSTRUCTOR_FALLBACK = which(Void, Tuple{Void}) -end +EQ_METHOD_FALSE = which(==, Tuple{Nothing, Int}) +#CONSTRUCTOR_FALLBACK = which(Nothing, Tuple{Nothing}) """ - StaticTypeAnalysis.canequal(S::Type, T::Type) :: Nullable{Bool} + StaticTypeAnalysis.canequal(S::Type, T::Type) :: Union{Bool, Nothing} -Given types `S` and `T`, return `Nullable(false)` if it is not possible for -`s::S == t::T`. Return `Nullable(true)` if it is possible, and -`Nullable{Bool}()` if it cannot be determined. +Given types `S` and `T`, return `false` if it is not possible for +`s::S == t::T`. Return `true` if it is possible, and +`nothing` if it cannot be determined. ```jldoctest julia> StaticTypeAnalysis.canequal(Int, Float64) -Nullable(true) +true julia> StaticTypeAnalysis.canequal(Int, String) -Nullable(false) +false ``` """ function canequal(S::Type, T::Type) if S == Union{} || T == Union{} - return Nullable(false) + false elseif typeintersect(S, T) ≠ Union{} # TODO: this is not fully correct; some types are not Union{} but still # not instantiated - return Nullable{Bool}(true) - elseif isleaftype(S) && isleaftype(T) && + true + elseif isconcretetype(S) && isconcretetype(T) && EQ_METHOD_FALSE == which(==, Tuple{S, T}) # == falls back to === here, but we saw earlier that the intersection # is empty - return Nullable(false) - elseif try zero(S) == zero(T) catch false end - return Nullable{Bool}(true) + false else - return Nullable{Bool}() + try zero(S) == zero(T) + true + catch + nothing + end end end @@ -51,17 +51,21 @@ Consumers of this package are advised to use `infertype` and check the result against `Union{}`, which covers more cases. """ isknownerror(_f, _argtypes) = false +function isknownerror(::typeof(Base.getindex), argtypes::Tuple) + # correctly forward arguments from `infertype` below + isknownerror(typeof(Base.getindex), argtypes[1]) +end function isknownerror(::typeof(Base.getindex), argtypes) if isempty(argtypes) true - elseif argtypes[1] <: Associative + elseif argtypes[1] <: AbstractDict if Base.length(argtypes) ≠ 2 true else try K = keytype(argtypes[1]) ce = canequal(K, argtypes[2]) - !isnull(ce) && !get(ce) + ce !== nothing && !ce catch false end @@ -78,7 +82,7 @@ Given a function `f` and a list of types `argtypes`, use inference and other static type checking techniques to figure out a type `S` such that the result of applying `f` to `argtypes` is always of type `S`. """ -infertype(f, argtypes) = infertype(f, (argtypes...)) +infertype(f, argtypes...) = infertype(f, argtypes) function infertype(f, argtypes::Tuple) if isknownerror(f, argtypes) Union{} @@ -87,17 +91,17 @@ function infertype(f, argtypes::Tuple) eltype(argtypes[2]) <: Integer # TODO: would be nice to get rid of this odd special case UnitRange - elseif isa(f, Type) && Base.length(argtypes) == 1 && - isleaftype(argtypes[1]) && - which(f, Tuple{argtypes[1]}) === CONSTRUCTOR_FALLBACK - # we can infer better code for the constructor `convert` fallback by - # inferring the convert itself - Core.Inference.return_type(convert, Tuple{Type{f}, argtypes[1]}) + # elseif isa(f, Type) && Base.length(argtypes) == 1 && + # isconcretetype(argtypes[1]) && + # which(f, Tuple{argtypes[1]}) === CONSTRUCTOR_FALLBACK + # # we can infer better code for the constructor `convert` fallback by + # # inferring the convert itself + # Core.Inference.return_type(convert, Tuple{Type{f}, argtypes[1]}) else try typejoin(Base.return_types(f, Tuple{argtypes...})...) catch # error might be thrown if generic function, try using inference - if all(isleaftype, argtypes) + if all(isconcretetype, argtypes) Core.Inference.return_type(f, Tuple{argtypes...}) else Any @@ -114,30 +118,19 @@ operation on numbers is consistent with iteration order. Note that, in particular, this is not true for `String` and `Dict`. """ -getindexable{T<:Union{Tuple,Pair,Array,Number}}(::Type{T}) = true +getindexable(::Type{T}) where {T <: Union{Tuple,Pair,Array,Number}} = true getindexable(::Type) = false """ - StaticTypeAnalysis.length(T::Type) :: Nullable{Int} + StaticTypeAnalysis.length(T::Type) :: Union{Int, Nothing} If it can be determined that all objects of type `T` have length `n`, then -return `Nullable(n)`. Otherwise, return `Nullable{Int}()`. +return `n`. Otherwise, return `nothing`. """ -length(::Type{Union{}}) = Nullable(0) -length(::Type) = Nullable{Int}() -length{T<:Pair}(::Type{T}) = Nullable(2) - -if VERSION < v"0.6.0-dev.2123" # where syntax introduced by julia PR #18457 - length{T<:Tuple}(::Type{T}) = if !isa(T, DataType) || Core.Inference.isvatuple(T) - Nullable{Int}() - else - Nullable{Int}(Base.length(T.types)) - end -else - include_string(""" - length(::Type{T}) where T <: NTuple{N, Any} where N = Nullable{Int}(N) - """) -end +length(::Type{Union{}}) = 0 +length(::Type) = nothing +length(::Type{T}) where {T <: Pair} = 2 +length(::Type{T}) where T <: NTuple{N, Any} where N = N """ StaticTypeAnalysis.eltype(T::Type) @@ -148,8 +141,8 @@ element type `S`. eltype(::Type{Union{}}) = Union{} eltype(T::Type) = Base.eltype(T) -_getindex_nth{n}(xs::Any, ::Type{Val{n}}) = xs[n] -_typeof_nth_getindex{T}(::Type{T}, n::Integer) = +_getindex_nth(xs::Any, ::Type{Val{n}}) where {n} = xs[n] +_typeof_nth_getindex(::Type{T}, n::Integer) where {T} = infertype(_getindex_nth, Any[T, Type{Val{Int(n)}}]) """ @@ -159,13 +152,18 @@ Return `S` as specific as possible such that all objects of type `T`, when iterated over, have `n`th element type `S`. """ typeof_nth(T::Type, n::Integer) = - if getindexable(T) - typeintersect(eltype(T), _typeof_nth_getindex(T, n)) + if getindexable(T) && 0 < Base.length(T.types) + if n ≤ Base.length(T.types) + typeintersect(eltype(T), T.types[n]) + else + Union{} + end else eltype(T) end -typeof_nth{K,V}(::Type{Pair{K,V}}, n::Integer) = +typeof_nth(::Type{Pair{K,V}}, n::Integer) where {K, V} = n == 1 ? K : n == 2 ? V : Union{} typeof_nth(::Type{Union{}}, ::Integer) = Union{} +typeof_nth(::Type{Tuple{Vararg{Any}}}, ::Integer) = Any end diff --git a/src/types.jl b/src/types.jl index 70ed8e1..8a79c80 100644 --- a/src/types.jl +++ b/src/types.jl @@ -14,7 +14,7 @@ function linttype(ex::Expr, ctx::LintContext) adt = sube.args[i] if isa(adt, Symbol) foundobj = lookup(ctx, adt) - if !isnull(foundobj) && get(foundobj).typeactual <: Type + if foundobj !== nothing && foundobj.typeactual <: Type msg(ctx, :I393, adt, "using an existing type as type parameter name is probably a typo") end # TODO: review all uses of this function @@ -25,14 +25,14 @@ function linttype(ex::Expr, ctx::LintContext) if temptype != :T foundobj = lookup(ctx, temptype) - if !isnull(foundobj) && get(foundobj).typeactual <: Type + if foundobj !== nothing && foundobj.typeactual <: Type msg(ctx, :E538, temptype, "known type in parametric data type, " * "use {T<:...}") end end if istype(ctx, typeconstraint) dt = parsetype(ctx, typeconstraint) - if isa(dt, Type) && isleaftype(dt) + if isa(dt, Type) && isconcretetype(dt) msg(ctx, :E513, adt, "leaf type as a type constraint makes no sense") end end diff --git a/src/types/lintmessage.jl b/src/types/lintmessage.jl index 6b7e22b..b63dc0e 100644 --- a/src/types/lintmessage.jl +++ b/src/types/lintmessage.jl @@ -1,4 +1,4 @@ -@auto_hash_equals immutable LintMessage +@auto_hash_equals struct LintMessage location:: Location code :: Symbol #[E|W|I][1-9][1-9][1-9] scope :: String diff --git a/src/types/location.jl b/src/types/location.jl index 1c9a437..ec073c6 100644 --- a/src/types/location.jl +++ b/src/types/location.jl @@ -1,4 +1,4 @@ -@auto_hash_equals immutable Location +@auto_hash_equals struct Location file::String line::Int end diff --git a/src/variables.jl b/src/variables.jl index b25625c..4c7f0a0 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -4,16 +4,21 @@ function registersymboluse(sym::Symbol, ctx::LintContext) return Any end - lookupresult = BROADCAST(registeruse!, lookup(ctx, sym)) + lookupresult = nothing + let lu = lookup(ctx, sym) + if lu !== nothing + lookupresult = registeruse!(lu) + end + end - if isnull(lookupresult) - if !pragmaexists("Ignore use of undeclared variable $sym", ctx.current) && - ctx.quoteLvl == 0 + if lookupresult == nothing + if (!pragmaexists("Ignore use of undeclared variable $sym", ctx.current) + && ctx.quoteLvl == 0) msg(ctx, :E321, sym, "use of undeclared symbol") end Any else - return get(lookupresult).typeactual + return lookupresult.typeactual end end @@ -21,8 +26,8 @@ function lintglobal(ex::Expr, ctx::LintContext) for sym in ex.args if isa(sym, Symbol) globalset!(ctx.current, sym, VarInfo(location(ctx), Any)) - elseif !isnull(expand_assignment(sym)) - ea = get(expand_assignment(sym)) + elseif expand_assignment(sym) !== nothing + ea = expand_assignment(sym) lintassignment(Expr(:(=), ea[1], ea[2]), ctx; isGlobal=true) else msg(ctx, :E134, sym, "unknown global pattern") @@ -109,21 +114,22 @@ function lintassignment(ex::Expr, ctx::LintContext; islocal = false, isConst=fal if lhsIsTuple computedlength = StaticTypeAnalysis.length(rhstype) - if !isnull(computedlength) && get(computedlength) ≠ tuplelen + if (computedlength !== nothing + && computedlength ≠ tuplelen) msg(ctx, :I474, rhstype, "iteration generates tuples, " * - "$tuplelen of $(get(computedlength)) variables used") + "$tuplelen of $(computedlength) variables used") end end elseif isa(rhstype, Type) && lhsIsTuple computedlength = StaticTypeAnalysis.length(rhstype) - if !isnull(computedlength) - if get(computedlength) < tuplelen + if computedlength !== nothing + if computedlength < tuplelen msg(ctx, :E418, rhstype, "RHS is a tuple, $tuplelen of " * - "$(get(computedlength)) variables used") - elseif get(computedlength) > tuplelen + "$(computedlength) variables used") + elseif computedlength > tuplelen msg(ctx, :W546, rhstype, string( "implicitly discarding values, $tuplelen of ", - get(computedlength), " used")) + computedlength, " used")) end end end diff --git a/test/E100.jl b/test/E100.jl index 2c3c4c3..a52ce5c 100644 --- a/test/E100.jl +++ b/test/E100.jl @@ -6,7 +6,7 @@ end """) @test messageset(msgs) == Set([:E100, :E321]) - @test contains(msgs[1].message, "using expression must be at top level") + @test occursin("using expression must be at top level", msgs[1].message) msgs = lintstr(""" function f(x, y) @@ -15,7 +15,7 @@ end """) @test messageset(msgs) == Set([:E100, :E321]) - @test contains(msgs[1].message, "import expression must be at top level") + @test occursin("import expression must be at top level", msgs[1].message) msgs = lintstr(""" function f(x, y) @@ -24,5 +24,5 @@ end """) @test messageset(msgs) == Set([:E100, :E321]) - @test contains(msgs[1].message, "export expression must be at top level") + @test occursin("export expression must be at top level", msgs[1].message) end diff --git a/test/E522.jl b/test/E522.jl index aa40d71..282e975 100644 --- a/test/E522.jl +++ b/test/E522.jl @@ -6,8 +6,8 @@ @test messageset(msgs) == Set([:E522]) @test msgs[1].variable == "s[1]" - @test contains(msgs[1].message, "indexing UniformScaling") - @test contains(msgs[1].message, "with index types $Int is not supported") + @test occursin("indexing UniformScaling", msgs[1].message) + @test occursin("with index types $Int is not supported", msgs[1].message) # dicts @test messageset(lintstr(""" @@ -33,8 +33,8 @@ """) @test messageset(msgs) == Set([:E522, :E539]) @test msgs[1].variable == "d[a]" - @test contains(msgs[1].message, "indexing Dict") - @test contains(msgs[1].message, "Int") + @test occursin("indexing Dict", msgs[1].message) + @test occursin("Int", msgs[1].message) # strings msgs = lintstr(""" @@ -45,8 +45,8 @@ """) @test messageset(msgs) == Set([:E522]) @test msgs[1].variable == "b[:start]" - @test contains(msgs[1].message, "indexing String") - @test contains(msgs[1].message, "with index types Symbol is not supported") + @test occursin("indexing String", msgs[1].message) + @test occursin("with index types Symbol is not supported", msgs[1].message) # zero-dimensional indexing msgs = lintstr(""" @@ -54,15 +54,15 @@ x = d[] """) @test messageset(msgs) == Set([:E522, :E539]) - @test contains(msgs[1].message, "indexing Dict") - @test contains(msgs[1].message, "with no indices is not supported") + @test occursin("indexing Dict", msgs[1].message) + @test occursin("with no indices is not supported", msgs[1].message) msgs = lintstr(""" a = "" a[] """) @test messageset(msgs) == Set([:E522]) - @test contains(msgs[1].message, "indexing String with no indices") + @test occursin("indexing String with no indices", msgs[1].message) # issue 196 @test messageset(lintstr(""" diff --git a/test/I340.jl b/test/I340.jl index 8a59394..f34a784 100644 --- a/test/I340.jl +++ b/test/I340.jl @@ -7,7 +7,7 @@ end """) @test messageset(msgs) == Set([:I340]) - @test contains(msgs[1].message, "unused local variable") + @test occursin("unused local variable", msgs[1].message) @test_broken msgs[1].line == 2 msgs = lintstr(""" @@ -17,7 +17,7 @@ end """) @test messageset(msgs) == Set([:I340]) - @test contains(msgs[1].message, "unused local variable") + @test occursin("unused local variable", msgs[1].message) @test_broken msgs[1].line == 2 msgs = lintstr(""" @@ -30,7 +30,7 @@ end """) @test messageset(msgs) == Set([:I340]) - @test contains(msgs[1].message, "unused local variable") + @test occursin("unused local variable", msgs[1].message) msgs = lintstr(""" function f(x) @@ -40,7 +40,7 @@ end """) @test messageset(msgs) == Set([:I340]) - @test contains(msgs[1].message, "unused local variable") + @test occursin("unused local variable", msgs[1].message) msgs = lintstr(""" function f(x) @@ -50,7 +50,7 @@ end """) @test messageset(msgs) == Set([:I340]) - @test contains(msgs[1].message, "unused local variable") + @test occursin("unused local variable", msgs[1].message) @test isempty(lintstr(""" function f(x) diff --git a/test/I343.jl b/test/I343.jl index 3bf8a8a..0797477 100644 --- a/test/I343.jl +++ b/test/I343.jl @@ -20,7 +20,7 @@ e = 1 """) @test messageset(msgs) == Set([:I343]) - @test contains(msgs[1].message, "with same name as export from Base") + @test occursin("with same name as export from Base", msgs[1].message) @test isempty(lintstr(""" import Base: parent diff --git a/test/W361.jl b/test/W361.jl index 99bded5..c0161b1 100644 --- a/test/W361.jl +++ b/test/W361.jl @@ -6,5 +6,5 @@ """ msgs = lintstr(s) @test msgs[1].code == :W361 - @test contains(msgs[1].message, "exporting undefined symbol") + @test occursin("exporting undefined symbol", msgs[1].message) end diff --git a/test/array.jl b/test/array.jl index bb4c875..d7997fd 100644 --- a/test/array.jl +++ b/test/array.jl @@ -1,75 +1,77 @@ @assert [[1;2];[3;4]] == [1;2;3;4] -s = """ -r = [[1;2];[3;4]] -""" -msgs = lintstr(s) -@test msgs[1].code == :W444 -@test contains(msgs[1].message, "nested vcat is treated as a 1-dimensional array") - -s = """ -r = [[1,2],[3,4]] -""" -msgs = lintstr(s) -@test isempty(msgs) +let s = """ + r = [[1;2];[3;4]] + """, + msgs = lintstr(s) + @test msgs[1].code == :W444 +end + +let s = """ + r = [[1,2],[3,4]] + """, + msgs = lintstr(s) + @test isempty(msgs) +end @assert [[1 2] [3 4]] == [1 2 3 4] -s = """ -r = [[1 2] [3 4]] -""" -msgs = lintstr(s) -@test msgs[1].code == :W445 -@test contains(msgs[1].message, "nested hcat is treated as a 1-row horizontal array of " * - "dim=2") - -s = """ -x = Any[[1,2],[7,8]] -y = Array[[1,2],[3,4]] -""" -msgs = lintstr(s) -@test isempty(msgs) - -s = """ -function f(x::Array{Float64,2}) +let s = """ + r = [[1 2] [3 4]] + """, + msgs = lintstr(s) + @test msgs[1].code == :W445 +end + +let s = """ + x = Any[[1,2],[7,8]] + y = Array[[1,2],[3,4]] + """, + msgs = lintstr(s) + @test isempty(msgs) +end + +let s = """ + function f(x::Array{Float64,2}) y = x[1, 2, 3] y + end + """, + msgs = lintstr(s) + @test msgs[1].code == :E436 end -""" -msgs = lintstr(s) -@test msgs[1].code == :E436 -@test contains(msgs[1].message, "more indices than dimensions") -s = """ -function f(x::Array{Float64,2}) +let s = """ + function f(x::Array{Float64,2}) x[1, 2, 3] + end + """, + msgs = lintstr(s) + @test msgs[1].code == :E436 end -""" -msgs = lintstr(s) -@test msgs[1].code == :E436 -@test contains(msgs[1].message, "more indices than dimensions") - -s = """ -function f(x::Array{Float64,2}) - y = x[Colon(), 1] - for i in y - println(i) + +let s = """ + function f(x::Array{Float64,2}) + y = x[Colon(), 1] + for i in y + println(i) + end end + """, + msgs = lintstr(s) + @test isempty(msgs) end -""" -msgs = lintstr(s) -@test isempty(msgs) -s = """ -function f(x::Array{Float64,2}) +let s = """ + function f(x::Array{Float64,2}) y = x[1,1] @lintpragma("Info type y") + end + """, + msgs = lintstr(s) + @test msgs[1].code == :I271 end -""" -msgs = lintstr(s) -@test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(y) == Float64") -s = """ -function f(t) +let s = """ + function f(t) x1 = zeros(1, 2) x2 = zeros(Int64, 2, 2) x3 = zeros(t, 2, 2) @@ -78,23 +80,20 @@ function f(t) @lintpragma("Info type x2") @lintpragma("Info type x3") @lintpragma("Info type x4") -end -""" -msgs = lintstr(s) -@test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x1) == Array{Float64,2}") -@test msgs[2].code == :I271 -@test contains(msgs[2].message, "typeof(x2) == Array{Int64,2}") -if VERSION ≥ v"0.6-" + end + """, + msgs = lintstr(s) + @test msgs[1].code == :I271 + @test msgs[2].code == :I271 + @test msgs[3].code == :I271 - @test contains(msgs[3].message, "typeof(x3) == $Array") -end -@test msgs[4].code == :I271 -@test contains(msgs[4].message, "typeof(x4) == Array{Float64,2}") + + @test msgs[4].code == :I271 +end # more array function -s = """ -function f(t::Array{Int64,2}, m, n) +let s = """ + function f(t::Array{Int64,2}, m, n) x2 = reshape(t, 1) x3 = reshape(t, (1,2)) x4 = reshape(m, (1,2)) @@ -107,63 +106,61 @@ function f(t::Array{Int64,2}, m, n) @lintpragma("Info type x6") @lintpragma("Info type x7") @lintpragma("Info type x8") + end + """, + msgs = lintstr(s) + @test msgs[1].code == :I271 + @test msgs[2].code == :I271 + @test msgs[3].code == :I271 + @test msgs[4].code == :I271 + @test msgs[5].code == :I271 end -""" -msgs = lintstr(s) -@test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x2) == Array{Int64,1}") -@test msgs[2].code == :I271 -@test contains(msgs[2].message, "typeof(x3) == Array{Int64,2}") -@test msgs[3].code == :I271 -@test contains(msgs[3].message, "typeof(x4) == Any") -@test msgs[4].code == :I271 -@test contains(msgs[4].message, "typeof(x6) == Array{Int64,2}") -@test msgs[5].code == :I271 -@test contains(msgs[5].message, "typeof(x7) == Array{Int64,2}") - -s = """ -function f(a::Array{Float64}) + +let s = """ + function f(a::Array{Float64}) x = a[1,2] @lintpragma("Info type x") return x + end + """, + msgs = lintstr(s) + # it could be Float64, or it could be an array still! + @test msgs[1].code == :I271 end -""" -msgs = lintstr(s) -# it could be Float64, or it could be an array still! -@test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x) == Any") - -s = """ -s = "abcdef" -s = s[chr2ind(s,2) :end] -""" -msgs = lintstr(s) -@test msgs[1].code == :I681 -@test contains(msgs[1].message, "ambiguity of :end as a symbol vs as part of a range") - -s = """ -s = "abcdef" -sndlast = s[end -1] -""" -msgs = lintstr(s) -@test msgs[1].code == :I682 -@test contains(msgs[1].message, "ambiguity of `[end -n]` as a matrix row vs index [end-n]") - -s = """ -function f() + +let s = """ + s = "abcdef" + s = s[chr2ind(s,2) :end] + """, + msgs = lintstr(s) + @test msgs[1].code == :I681 +end + +let s = """ + s = "abcdef" + sndlast = s[end -1] + """, + msgs = lintstr(s) + @test msgs[1].code == :I682 +end + +let s = """ + function f() x1 = zeros(100, 100) x2 = Array(Float64, (100, 100)) x1[1, 1] x2[1, 1] + end + """, + msgs = lintstr(s) + @test isempty(msgs) end -""" -msgs = lintstr(s) -@test isempty(msgs) -s = """ -function f(y::Array{Float64, 3}, x1::Int64) +let s = """ + function f(y::Array{Float64, 3}, x1::Int64) reshape(y[Colon(), x1, Colon()]', size(y, 1), size(y, 3)') + end + """, + msgs = lintstr(s) + @test isempty(msgs) end -""" -msgs = lintstr(s) -@test isempty(msgs) diff --git a/test/badvars.jl b/test/badvars.jl index ce88a96..60a2cc4 100644 --- a/test/badvars.jl +++ b/test/badvars.jl @@ -8,19 +8,18 @@ msgs = lintstr(s) @test msgs[1].code == :E332 @test msgs[1].variable == "call" - @test contains(msgs[1].message, "should not be used as a variable name") end @testset "I342" begin - s = """ - function f() - var = "hi" # this is just asking for trouble - var + let variable_with_conflicting_name = "+", + s = """ + function f() + $(variable_with_conflicting_name) = "hi" + $(variable_with_conflicting_name) + end + """ + msgs = lintstr(s) + @test msgs[1].code == :I342 + @test msgs[1].variable == variable_with_conflicting_name end - """ - msgs = lintstr(s) - @test msgs[1].code == :I342 - @test msgs[1].variable == "var" - @test contains(msgs[1].message, "local variable") - @test contains(msgs[1].message, "shadows export from Base") end diff --git a/test/basics.jl b/test/basics.jl index 940e435..cd4d11d 100644 --- a/test/basics.jl +++ b/test/basics.jl @@ -5,5 +5,5 @@ p = "non_existing_1234_4321" @test_throws(AbstractString, lintpkg(p)) # Lint package with full path -msgs = lintpkg(joinpath(Pkg.dir("Lint"), "test", "FakePackage")) +msgs = lintpkg(joinpath(Base.Filesystem.dirname(Base.find_package("Lint")), "test", "FakePackage")) @test isempty(msgs) diff --git a/test/bitopbool.jl b/test/bitopbool.jl index 9119f21..b88051a 100644 --- a/test/bitopbool.jl +++ b/test/bitopbool.jl @@ -6,9 +6,5 @@ msgs = lintstr(s) @test length(msgs)==2 @test msgs[1].code == :I475 @test msgs[1].variable == "&" -@test contains(msgs[1].message, "bit-wise in a boolean context. (&,|) do not have " * - "short-circuit behavior") @test msgs[2].code == :I475 @test msgs[2].variable == "|" -@test contains(msgs[2].message, "bit-wise in a boolean context. (&,|) do not have " * - "short-circuit behavior") diff --git a/test/curly.jl b/test/curly.jl index c4cc83a..3de20ea 100644 --- a/test/curly.jl +++ b/test/curly.jl @@ -1,11 +1,11 @@ @testset "W447" begin msgs = lintstr("a = Dict{:Symbol, Any}") @test messageset(msgs) == Set([:W447]) - @test contains(msgs[1].message, "type parameter for Dict") + @test occursin("type parameter for Dict", msgs[1].message) msgs = lintstr("a = Dict{:Symbol, Any}()") @test messageset(msgs) == Set([:W447]) - @test contains(msgs[1].message, "type parameter for Dict") + @test occursin("type parameter for Dict", msgs[1].message) @test isempty(lintstr("a = Set{Tuple{Int, Int}}()")) @@ -14,11 +14,11 @@ a = Dict{b, Any}() """) @test messageset(msgs) == Set([:W447]) - @test contains(msgs[1].message, "type parameter for Dict") + @test occursin("type parameter for Dict", msgs[1].message) msgs = lintstr("a = Array{2, Int64}()") - @test_broken messageset(msgs) == Set([:W447]) - @test contains(msgs[1].message, "type parameter for Array") + @test messageset(msgs) == Set([:W447]) + @test occursin("type parameter for Array", msgs[1].message) end @testset "Curly" begin @@ -27,17 +27,17 @@ end """ msgs = lintstr(s) @test msgs[1].code == :W441 - @test contains(msgs[1].message, "probably illegal use inside curly") + @test occursin("probably illegal use inside curly", msgs[1].message) s = """ a = Array{Int64, 5, 5}() """ msgs = lintstr(s) @test msgs[1].code == :W446 - @test contains(msgs[1].message, "too many type parameters") + @test occursin("too many type parameters", msgs[1].message) s = """ - a = Ptr{Void} + a = Ptr{Cvoid} """ msgs = lintstr(s) @test isempty(msgs) diff --git a/test/deadcode.jl b/test/deadcode.jl index 4ded54f..bf7b2b0 100644 --- a/test/deadcode.jl +++ b/test/deadcode.jl @@ -7,4 +7,4 @@ end msgs = lintstr(s) @test length(msgs) == 1 @test msgs[1].code == :W641 -@test contains(msgs[1].message, "unreachable code after return") +@test occursin("unreachable code after return", msgs[1].message) diff --git a/test/deprecate.jl b/test/deprecate.jl index efd051b..c033482 100644 --- a/test/deprecate.jl +++ b/test/deprecate.jl @@ -8,7 +8,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E211 -@test contains(msgs[1].message, "generic deprecate message") +@test occursin("generic deprecate message", msgs[1].message) # THIS DOESN'T TRIGGER LINT WARNING SINCE THE SIGNATURE DOESN'T MATCH s = """ @@ -43,7 +43,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E211 -@test contains(msgs[1].message, "generic deprecate message") +@test occursin("generic deprecate message", msgs[1].message) s = """ function testDep3{T <: Real}(x::Array{T,1}) @@ -52,7 +52,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E211 -@test contains(msgs[1].message, "generic deprecate message") +@test occursin("generic deprecate message", msgs[1].message) s = """ function testDep4(x::Int, y::Int...) @@ -61,7 +61,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E211 -@test contains(msgs[1].message, "generic deprecate message") +@test occursin("generic deprecate message", msgs[1].message) s = """ function testDep4(x::Int, y::Int) @@ -70,7 +70,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E211 -@test contains(msgs[1].message, "generic deprecate message") +@test occursin("generic deprecate message", msgs[1].message) s = """ function testDep5{T <: AbstractString}(x::Array{T,1}) diff --git a/test/dictkey.jl b/test/dictkey.jl index eb73065..4af62a5 100644 --- a/test/dictkey.jl +++ b/test/dictkey.jl @@ -1,22 +1,19 @@ -s = """ +@testset "Dictionary keys" begin + s = """ Dict(:a=>1, :b=>2, :a=>3) """ -msgs = lintstr(s) -@test msgs[1].code == :E334 -@test contains(msgs[1].message, "duplicate key in Dict") + msgs = lintstr(s) + @test msgs[1].code == :E334 -s = """ + s = """ Dict{Symbol,Int}(:a=>1, :b=>"") """ -msgs = lintstr(s) -@test msgs[1].code == :E532 -@test contains(msgs[1].message, "multiple value types detected. Use Dict{K,Any}() for " * - "mixed type dict") + msgs = lintstr(s) + @test msgs[1].code == :E532 -s = """ + s = """ Dict{Symbol,Int}(:a=>1, "b"=>2) """ -msgs = lintstr(s) -@test msgs[1].code == :E531 -@test contains(msgs[1].message, "multiple key types detected. Use Dict{Any,V}() for " * - "mixed type dict") + msgs = lintstr(s) + @test msgs[1].code == :E531 +end diff --git a/test/doc.jl b/test/doc.jl index 3789377..694de2e 100644 --- a/test/doc.jl +++ b/test/doc.jl @@ -14,7 +14,6 @@ f() = 0 """ msgs = lintstr(s) @test msgs[1].code == :W443 -@test contains(msgs[1].message, "did you forget an -> after @doc or make it inline?") s = """ @doc "this is a test" f() = 0 diff --git a/test/dupexport.jl b/test/dupexport.jl index 8c87c74..26680cf 100644 --- a/test/dupexport.jl +++ b/test/dupexport.jl @@ -12,4 +12,4 @@ end msgs = lintstr(s) @test msgs[1].code == :E333 -@test contains(msgs[1].message, "duplicate exports of symbol") +@test occursin("duplicate exports of symbol", msgs[1].message) diff --git a/test/expression_iterator.jl b/test/expression_iterator.jl new file mode 100644 index 0000000..592143e --- /dev/null +++ b/test/expression_iterator.jl @@ -0,0 +1,43 @@ +@testset "Expression iterator" begin + let line_array = ["a", "b", "c", "í", "ω", "゛"], + lines_string = join(line_array, "\n"), + line_it = Lint.ExpressionIterator.each_line_iterator(lines_string), + collected_lines = collect(line_it) + @test eltype(collected_lines) == SubString{String} # need the indices to original string + @test collected_lines == line_array + end + + let line_array=collect(map(i->"$i", 1:9)), + lines_string=join(line_array, "\n"), + lines_offsets = collect(Lint.ExpressionIterator.lines_offsets(lines_string)), + expected_offsets = [(i*2-1,i*2-1) for i in 1:9] # "1", "\n", … "7", "\n" … + @test lines_offsets == expected_offsets + @test [lines_string[r[1]:r[2]] for r in lines_offsets] == line_array + end + + let s = """ + function f() + x = 0 + x + end + """, + lines = collect(Lint.ExpressionIterator.each_line_iterator(s)), + lines_offsets = collect(Lint.ExpressionIterator.lines_offsets(s)), + lines_per_offsets = [SubString(s, t[1], t[2]) for t in lines_offsets] + @test lines == [SubString(s, t[1], t[2]) for t in lines_offsets] + @test collect(Lint.ExpressionIterator.each_expression(s)) == [Meta.parse(s)] + end + + let s = """ + function f() + x = zeros(10, 10) + x + end + """, + lines = collect(Lint.ExpressionIterator.each_line_iterator(s)), + lines_offsets = collect(Lint.ExpressionIterator.lines_offsets(s)), + lines_per_offsets = [SubString(s, t[1], t[2]) for t in lines_offsets] + @test lines == [SubString(s, t[1], t[2]) for t in lines_offsets] + @test collect(Lint.ExpressionIterator.each_expression(s)) == [Meta.parse(s)] + end +end diff --git a/test/exprutils.jl b/test/exprutils.jl index 168d994..0732cac 100644 --- a/test/exprutils.jl +++ b/test/exprutils.jl @@ -1,7 +1,5 @@ using Lint.ExpressionUtils @testset "Expressions" begin - @test expand_trivial_calls(:(1:2)) == :(colon(1, 2)) @test expand_trivial_calls(:(A')) == :(ctranspose(A)) - @test expand_trivial_calls(:(A.')) == :(transpose(A)) end diff --git a/test/forloop.jl b/test/forloop.jl index 9c208a5..52621f7 100644 --- a/test/forloop.jl +++ b/test/forloop.jl @@ -10,7 +10,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :W645 -@test contains(msgs[1].message, "while false block is unreachable") +@test occursin("while false block is unreachable", msgs[1].message) s = """ function f(x) @@ -46,6 +46,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x) == Int") +@test occursin("typeof(x, msgs[1].message) == Int") @test msgs[2].code == :I672 -@test contains(msgs[2].message, "iteration works for a number but it may be a typo") +@test occursin("iteration works for a number but it may be a typo", msgs[2].message) diff --git a/test/funcall.jl b/test/funcall.jl index e4581e0..9f39c53 100644 --- a/test/funcall.jl +++ b/test/funcall.jl @@ -35,7 +35,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E331 -@test contains(msgs[1].message, "duplicate argument") s = """ function f{Int64}(x::Int64, y::Int64) @@ -45,8 +44,6 @@ end msgs = lintstr(s) @test msgs[1].code == :E534 @test msgs[1].variable == "Int64" -@test contains(msgs[1].message, "introducing a new name for an implicit argument to the " * - "function, use {T<:Int64}") s = """ function f{T<:Int64}(x::T, y::T) @@ -56,7 +53,6 @@ end msgs = lintstr(s) @test msgs[1].code == :E513 @test msgs[1].variable == "T <: Int64" -@test contains(msgs[1].message, "leaf type as a type constraint makes no sense") s = """ function f{Int<:Real}(x::Int, y::Int) @@ -65,7 +61,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E536 -@test contains(msgs[1].message, "use {T<:...} instead of a known type") s = """ function f(x, args...) @@ -82,7 +77,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E413 -@test contains(msgs[1].message, "positional ellipsis ... can only be the last argument") s = """ function f(x=1, y, args...) @@ -91,7 +85,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E411 -@test contains(msgs[1].message, "non-default argument following default arguments") s = """ function f(x, y; z, q=1) @@ -100,7 +93,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E423 -@test contains(msgs[1].message, "named keyword argument must have a default") s = """ function f(x, y; args..., z=1) @@ -109,7 +101,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E412 -@test contains(msgs[1].message, "named ellipsis ... can only be the last argument") s = """ function f(x, args...; kwargs...) @@ -126,7 +117,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E533 -@test contains(msgs[1].message, "type parameters are invariant, try f{T<:Number}(x::T)...") s = """ function f(x::Dict{Symbol,Number}) @@ -135,7 +125,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E533 -@test contains(msgs[1].message, "type parameters are invariant, try f{T<:Number}(x::T)...") s = """ function f(x; y = 1, z::Int = 0.1) @@ -144,7 +133,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E516 -@test contains(msgs[1].message, "type assertion and default") s = """ function f(x; y = 1, z::Int = error("You must provide z")) @@ -190,21 +178,15 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(a) == Array") @test msgs[2].code == :I271 -@test contains(msgs[2].message, "typeof(n) == Tuple{Int64}") if VERSION ≥ v"0.6-" @test msgs[3].code == :I271 - @test contains(msgs[3].message, "typeof(tmp) == Array") end @test msgs[4].code == :I271 -@test_broken contains(msgs[4].message, "typeof(T) == Type") if VERSION ≥ v"0.6-" @test msgs[5].code == :I271 - @test contains(msgs[5].message, "typeof(tmp2) == Array") end @test msgs[6].code == :I271 -@test contains(msgs[6].message, "typeof(tmp3) == Array{Float64,3}") s = """ function f(x) @@ -215,7 +197,6 @@ end msgs = lintstr(s) @test msgs[1].code == :W355 @test msgs[1].variable == "f" -@test contains(msgs[1].message, "conflicts with function name") s = """ function f(x) @@ -242,7 +223,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x) == Int") s = """ function f(x::Int8=Int8(1)) @@ -252,7 +232,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x) == Int8") s = """ function f(c::Char) @@ -263,7 +242,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x) == Int") s = """ function f(args...; dict...) @@ -274,9 +252,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(args) == Tuple") @test msgs[2].code == :I271 -@test contains(msgs[2].message, "typeof(dict) == Tuple") s = """ function f(args::Float64...) @@ -286,7 +262,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(args) == Tuple{Vararg{Float64") s = """ function f(args::Float64...) @@ -311,7 +286,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :W542 -@test contains(msgs[1].message, "comparing apparently incompatible type") s = """ function f(X::Int) @@ -351,7 +325,6 @@ f(; 2) """ msgs = lintstr(s) @test msgs[1].code == :E133 -@test contains(msgs[1].message, "unknown keyword pattern") s = """ function () end @@ -376,7 +349,6 @@ s=""" """ msgs = lintstr(s) @test msgs[1].code == :E132 -@test contains(msgs[1].message, "Lint does not understand argument") s = """ (==)((1, 1)...) diff --git a/test/globals.jl b/test/globals.jl index 4601ddc..4e7e448 100644 --- a/test/globals.jl +++ b/test/globals.jl @@ -24,8 +24,8 @@ end """) @test msgs[1].code == :I341 @test msgs[1].variable == "yyyyyy" -@test contains(msgs[1].message, "local variable") -@test contains(msgs[1].message, "shadows global variable") +@test occursin("local variable", msgs[1].message) +@test occursin("shadows global variable", msgs[1].message) s = """ y = 1 @@ -56,14 +56,14 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E111 -@test contains(msgs[1].message, "expected assignment after \\\"const\\\"") +@test occursin("expected assignment after \\\"const\\\"", msgs[1].message) s = """ global 5 """ msgs = lintstr(s) @test msgs[1].code == :E134 -@test contains(msgs[1].message, "unknown global pattern") +@test occursin("unknown global pattern", msgs[1].message) s = """ f() = x @@ -78,7 +78,7 @@ x = 5 """ msgs = lintstr(s) @test msgs[1].code == :E321 -@test contains(msgs[1].message, "use of undeclared symbol") +@test occursin("use of undeclared symbol", msgs[1].message) # Test gloabls defined in other files # File in package src diff --git a/test/guesstype.jl b/test/guesstype.jl index bce82d6..a6dc692 100644 --- a/test/guesstype.jl +++ b/test/guesstype.jl @@ -1,6 +1,20 @@ -s = """ -a = begin end -""" -@test Lint.guesstype(parse(s), LintContext()) == Void -msgs = lintstr(s) -@test isempty(msgs) +@testset "correctly detect stdlib objects" begin + @test Lint.stdlibobject(:var) == nothing + @test Lint.stdlibobject(:axes) !== nothing +end + +@testset "Handle empty code block" begin + let s = """ + a = begin end + """, + ex=Meta.parse(s) + @test Lint.get_tag_per_condition(ex) == Lint.AssignTag + @test Lint.guesstype(ex, LintContext()) == Nothing + msgs = lintstr(s) + @test isempty(msgs) + end +end +@testset "Colon (no parameters)" begin + @test Lint.iscolon(:(:)) + @test Lint.iscolon(:(Colon())) +end diff --git a/test/ifstmt.jl b/test/ifstmt.jl index 7301687..e06b2a0 100644 --- a/test/ifstmt.jl +++ b/test/ifstmt.jl @@ -5,11 +5,11 @@ wrap(pos::Int, len::Int) = true ? 1 : (pos > len ? len : pos) msgs = lintstr(s) @test length(msgs) == 3 @test msgs[1].code == :W643 -@test contains(msgs[1].message, "false branch is unreachable") +@test occursin("false branch is unreachable", msgs[1].message) @test msgs[2].code == :I340 -@test contains(msgs[2].message, "unused local variable") +@test occursin("unused local variable", msgs[2].message) @test msgs[3].code == :I340 -@test contains(msgs[3].message, "unused local variable") +@test occursin("unused local variable", msgs[3].message) s = """ wrap(pos::Int, len::Int) = false ? 1 : (pos > len ? len : pos) @@ -17,7 +17,7 @@ wrap(pos::Int, len::Int) = false ? 1 : (pos > len ? len : pos) msgs = lintstr(s) @test length(msgs) == 1 @test msgs[1].code == :W642 -@test contains(msgs[1].message, "true branch is unreachable") +@test occursin("true branch is unreachable", msgs[1].message) @test Lint.line(msgs[1]) == 1 s = """ @@ -25,21 +25,21 @@ f(x) = (x=1) ? 1 : 2 # clearly not what we want """ msgs = lintstr(s) @test msgs[1].code == :I472 -@test contains(msgs[1].message, "assignment in the if-predicate clause") +@test occursin("assignment in the if-predicate clause", msgs[1].message) s = """ f(x) = ifelse(length(x), 1 , 2) # clearly not what we want """ msgs = lintstr(s) @test msgs[1].code == :E431 -@test contains(msgs[1].message, "use of length() in a Boolean context, use isempty()") +@test occursin("use of length(, msgs[1].message) in a Boolean context, use isempty()") s = """ f(x,y) = (0 <= x < y = 6) ? 1 : 2 # clearly not what we want """ msgs = lintstr(s) @test msgs[1].code == :I472 -@test contains(msgs[1].message, "assignment in the if-predicate clause") +@test occursin("assignment in the if-predicate clause", msgs[1].message) s = """ function f() @@ -50,7 +50,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :W644 -@test contains(msgs[1].message, "redundant if-true statement") +@test occursin("redundant if-true statement", msgs[1].message) s = """ function f() @@ -62,7 +62,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E431 -@test contains(msgs[1].message, "use of length() in a Boolean context, use isempty()") +@test occursin("use of length(, msgs[1].message) in a Boolean context, use isempty()") s = """ function f(b::Bool, x::Int, y::Int) @@ -73,7 +73,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(a) == Int") +@test occursin("typeof(a, msgs[1].message) == Int") s = """ function f(b::Bool, x::Int, y::Any) @@ -84,7 +84,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(a) == Any") +@test occursin("typeof(a, msgs[1].message) == Any") s = """ function f() @@ -98,10 +98,10 @@ end msgs = lintstr(s) @test msgs[1].code == :E512 @test msgs[1].variable == ":a" -@test contains(msgs[1].message, "Lint doesn't understand in a boolean context") +@test occursin("Lint doesn't understand in a boolean context", msgs[1].message) @test msgs[2].code == :E512 @test msgs[2].variable == ":b" -@test contains(msgs[2].message, "Lint doesn't understand in a boolean context") +@test occursin("Lint doesn't understand in a boolean context", msgs[2].message) s = """ function f(a, b) @@ -115,7 +115,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I571 -@test contains(msgs[1].message, "the 1st statement under the true-branch is a boolean expression") +@test occursin("the 1st statement under the true-branch is a boolean expression", msgs[1].message) s = """ function f(a, b) @@ -129,7 +129,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I571 -@test contains(msgs[1].message, "the 1st statement under the true-branch is a boolean expression") +@test occursin("the 1st statement under the true-branch is a boolean expression", msgs[1].message) s = """ function f(a, b) diff --git a/test/incomplete.jl b/test/incomplete.jl index e3f0c7d..91b8f1f 100644 --- a/test/incomplete.jl +++ b/test/incomplete.jl @@ -3,11 +3,11 @@ module test; if true; end """ msgs = lintstr(s) @test msgs[1].code == :E112 -@test contains(msgs[1].message, "incomplete:") +@test occursin("incomplete:", msgs[1].message) s = """ 2 + """ msgs = lintstr(s) @test msgs[1].code == :E112 -@test contains(msgs[1].message, "incomplete:") +@test occursin("incomplete:", msgs[1].message) diff --git a/test/lambda.jl b/test/lambda.jl index b739209..279f0ef 100644 --- a/test/lambda.jl +++ b/test/lambda.jl @@ -9,7 +9,7 @@ end msgs = lintstr(s) @test_broken msgs[1].code == :W352 -@test_broken contains(msgs[1].message, "lambda argument conflicts with a local variable") +@test_broken occursin("lambda argument conflicts with a local variable", msgs[1].message) s = """ function f(x) @@ -18,7 +18,7 @@ end """ msgs = lintstr(s) @test_broken msgs[1].code == :W353 -@test_broken contains(msgs[1].message, "lambda argument conflicts with an argument") +@test_broken occursin("lambda argument conflicts with an argument", msgs[1].message) s = """ x = 1 @@ -29,7 +29,7 @@ end """ msgs = lintstr(s) @test_broken msgs[1].code == :W354 -@test_broken contains(msgs[1].message, "lambda argument conflicts with an declared global") +@test_broken occursin("lambda argument conflicts with an declared global", msgs[1].message) s = """ function f() diff --git a/test/linthelper.jl b/test/linthelper.jl index 04c9a08..e6fd752 100644 --- a/test/linthelper.jl +++ b/test/linthelper.jl @@ -9,6 +9,6 @@ try msgs = lintfile("DEMOMODULE3.jl") @test_broken msgs[1].code == :E311 - @test_broken contains(msgs[1].message, "cannot find include file") + @test_broken occursin("cannot find include file", msgs[1].message) end end diff --git a/test/macro.jl b/test/macro.jl index 282cd24..9df9370 100644 --- a/test/macro.jl +++ b/test/macro.jl @@ -50,7 +50,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E522 -@test contains(msgs[1].message, "macro arguments can only be Symbol/Expr") +@test occursin("macro arguments can only be Symbol/Expr", msgs[1].message) =# s = """ @@ -74,7 +74,7 @@ msgs = lintstr(s) """ msgs = lintstr(s) @test_broken msgs[1].code == :E141 - @test_broken contains(msgs[1].message, "invalid macro syntax") + @test_broken occursin("invalid macro syntax", msgs[1].message) end s = """ diff --git a/test/misuse.jl b/test/misuse.jl index 6f8e76b..5341d88 100644 --- a/test/misuse.jl +++ b/test/misuse.jl @@ -4,7 +4,7 @@ s = """ """ msgs = lintstr(s) @test msgs[1].code == :I171 -@test contains(msgs[1].message, "LHS in assignment not understood by Lint") +@test occursin("LHS in assignment not understood by Lint", msgs[1].message) s = """ local 5 @@ -12,7 +12,7 @@ local 5 msgs = lintstr(s) @test msgs[1].code == :E135 @test msgs[1].variable == "5" -@test contains(msgs[1].message, "local declaration not understood by Lint") +@test occursin("local declaration not understood by Lint", msgs[1].message) s = """ a = 5 @@ -22,7 +22,7 @@ end msgs = lintstr(s) @test msgs[1].code == :E511 @test msgs[1].variable == "a" -@test contains(msgs[1].message, "apparent non-Bool type") +@test occursin("apparent non-Bool type", msgs[1].message) s = """ d = Dict{float, float}() @@ -34,7 +34,7 @@ if VERSION < v"0.5.0-dev+2959" @test msgs[2].code == :W441 else @test msgs[1].code == :W447 -@test contains(msgs[1].message, "it should be of type Type") +@test occursin("it should be of type Type", msgs[1].message) @test msgs[2].code == :W447 -@test contains(msgs[2].message, "it should be of type Type") +@test occursin("it should be of type Type", msgs[2].message) end diff --git a/test/pragma.jl b/test/pragma.jl index 6423784..bc39d8c 100644 --- a/test/pragma.jl +++ b/test/pragma.jl @@ -32,7 +32,7 @@ end msgs = lintstr(s) @test length(msgs) == 1 @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(b) == Int") +@test occursin("typeof(b, msgs[1].message) == Int") s = """ function f(x) @@ -44,7 +44,7 @@ end msgs = lintstr(s) @test length(msgs) == 1 @test msgs[1].code == :W241 -@test contains(msgs[1].message, "typeof(b) == Int") +@test occursin("typeof(b, msgs[1].message) == Int") s = """ function f(x) @@ -56,7 +56,7 @@ end msgs = lintstr(s) @test length(msgs) == 1 @test msgs[1].code == :E221 -@test contains(msgs[1].message, "typeof(b) == Int") +@test occursin("typeof(b, msgs[1].message) == Int") s = """ function f(x) @@ -77,7 +77,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E138 -@test contains(msgs[1].message, "incomplete pragma expression") +@test occursin("incomplete pragma expression", msgs[1].message) s = """ function f(x) @@ -88,7 +88,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "my own reminder") +@test occursin("my own reminder", msgs[1].message) s = """ function f(x) @@ -99,7 +99,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E137 -@test contains(msgs[1].message, "lintpragma must be called using only string literals") +@test occursin("lintpragma must be called using only string literals", msgs[1].message) s = """ function f(x) @@ -112,11 +112,11 @@ end msgs = lintstr(s) @test_broken msgs[1].code == :I381 @test_broken msgs[1].variable == "Ignore unused a" -@test_broken contains(msgs[1].message, "unused lintpragma") +@test_broken occursin("unused lintpragma", msgs[1].message) s = """ @lintpragma() """ msgs = lintstr(s) @test msgs[1].code == :E137 -@test contains(msgs[1].message, "lintpragma must be called using only string literals") +@test occursin("lintpragma must be called using only string literals", msgs[1].message) diff --git a/test/range.jl b/test/range.jl index a3b6df9..56c7026 100644 --- a/test/range.jl +++ b/test/range.jl @@ -3,7 +3,7 @@ r = 5:1 """ msgs = lintstr(s) @test msgs[1].code == :E433 -@test contains(msgs[1].message, "for a decreasing range, use a negative step e.g. 10:-1:1") +@test occursin("for a decreasing range, use a negative step e.g. 10:-1:1", msgs[1].message) s = """ x = [1,2,7,8] @@ -33,8 +33,8 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(r) == UnitRange") +@test occursin("typeof(r, msgs[1].message) == UnitRange") @test msgs[2].code == :I271 -@test contains(msgs[2].message, "typeof(a) == Any") +@test occursin("typeof(a, msgs[2].message) == Any") @test msgs[3].code == :I271 -@test contains(msgs[3].message, "typeof(b) == UnitRange") +@test occursin("typeof(b, msgs[3].message) == UnitRange") diff --git a/test/ref.jl b/test/ref.jl index 33a213c..25712a8 100644 --- a/test/ref.jl +++ b/test/ref.jl @@ -5,8 +5,8 @@ """ msgs = lintstr(s) @test messageset(msgs) == Set([:I473]) - @test contains(msgs[1].message, "value at position #1 is the referenced r") - @test contains(msgs[1].message, "OK if it represents permutations") + @test occursin("value at position #1 is the referenced r", msgs[1].message) + @test occursin("OK if it represents permutations", msgs[1].message) end @testset "E434" begin @@ -15,5 +15,5 @@ end r[1;r] """) @test messageset(msgs) == Set([:E434]) - @test contains(msgs[1].message, "value at position #2 is the referenced r. Possible typo?") + @test occursin("value at position #2 is the referenced r. Possible typo?", msgs[1].message) end diff --git a/test/runtests.jl b/test/runtests.jl index 859aa8f..8b4d566 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,11 @@ using Lint using Compat -using Base.Test +using Test messageset(msgs) = Set(x.code for x in msgs) +include("variables.jl") +include("expression_iterator.jl") include("exprutils.jl") include("statictype.jl") @@ -11,50 +13,48 @@ include("statictype.jl") include("messages.jl") end -try - @testset "AST Linting" begin - include("basics.jl") - include("array.jl") - include("badvars.jl") - include("bitopbool.jl") - include("comprehensions.jl") - include("curly.jl") - include("deadcode.jl") - include("deprecate.jl") - include("dictkey.jl") - include("doc.jl") - include("dupexport.jl") - include("forloop.jl") - include("funcall.jl") - include("globals.jl") - include("ifstmt.jl") - include("import.jl") - include("lambda.jl") - include("macro.jl") - include("meta.jl") - include("pragma.jl") - include("range.jl") - include("ref.jl") - include("similarity.jl") - include("strings.jl") - include("style.jl") - include("I340.jl") - include("I341.jl") - include("I343.jl") - include("I481.jl") - include("W361.jl") - include("E100.jl") - include("throw.jl") - include("tuple.jl") - include("type.jl") - include("typecheck.jl") - include("undeclare.jl") - include("using.jl") - include("versions.jl") - include("stagedfuncs.jl") - include("incomplete.jl") - include("misuse.jl") - end +@testset "AST Linting" begin + include("basics.jl") + include("array.jl") + include("badvars.jl") + include("bitopbool.jl") + include("comprehensions.jl") + include("curly.jl") + include("deadcode.jl") + include("deprecate.jl") + include("dictkey.jl") + include("doc.jl") + include("dupexport.jl") + include("forloop.jl") + include("funcall.jl") + include("globals.jl") + include("ifstmt.jl") + include("import.jl") + include("lambda.jl") + include("macro.jl") + include("meta.jl") + include("pragma.jl") + include("range.jl") + include("ref.jl") + include("similarity.jl") + include("strings.jl") + include("style.jl") + include("I340.jl") + include("I341.jl") + include("I343.jl") + include("I481.jl") + include("W361.jl") + include("E100.jl") + include("throw.jl") + include("tuple.jl") + include("type.jl") + include("typecheck.jl") + include("undeclare.jl") + include("using.jl") + include("versions.jl") + include("stagedfuncs.jl") + include("incomplete.jl") + include("misuse.jl") end @testset "Lint File" begin diff --git a/test/server.jl b/test/server.jl index 158c90e..541d0ff 100644 --- a/test/server.jl +++ b/test/server.jl @@ -47,7 +47,7 @@ end write(conn, "4\n") write(conn, "bad\n") - @test contains(readline(conn), "use of undeclared symbol") + @test occursin("use of undeclared symbol", readline(conn)) @test readline(conn) == "" end diff --git a/test/similarity.jl b/test/similarity.jl index b735ebc..c4bcd13 100644 --- a/test/similarity.jl +++ b/test/similarity.jl @@ -25,4 +25,4 @@ filter!(i->!(i==Lint.LintIgnore(:W651, "")), ctx.ignore) msgs = lintstr(s, ctx) @test length(msgs) == 1 @test msgs[1].code == :W651 -@test contains(msgs[1].message, "looks different") +@test occursin("looks different", msgs[1].message) diff --git a/test/stagedfuncs.jl b/test/stagedfuncs.jl index 03b2a9b..923fe2f 100644 --- a/test/stagedfuncs.jl +++ b/test/stagedfuncs.jl @@ -12,7 +12,7 @@ end msgs = lintstr(s) @test msgs[1].code == :W542 -@test contains(msgs[1].message, "incompatible types (#1)") +@test occursin("incompatible types (#1, msgs[1].message)") # if it is not a staged function, it would have no lint message s = """ @@ -22,7 +22,7 @@ end """ msgs = lintstr(s) @test_broken msgs[1].code == :I371 -@test_broken contains(msgs[1].message, "use of undeclared symbol") +@test_broken occursin("use of undeclared symbol", msgs[1].message) s = """ @generated function f(args::Int...) @@ -34,6 +34,6 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(args) == Tuple{Vararg{Type") +@test occursin("typeof(args, msgs[1].message) == Tuple{Vararg{Type") @test msgs[2].code == :I271 -@test contains(msgs[2].message, "typeof(x) == ") +@test occursin("typeof(x, msgs[2].message) == ") diff --git a/test/statictype.jl b/test/statictype.jl index ceddc96..595dbf3 100644 --- a/test/statictype.jl +++ b/test/statictype.jl @@ -2,18 +2,17 @@ using Lint: StaticTypeAnalysis @testset "Static Type" begin -@test get(StaticTypeAnalysis.canequal(Int, Int)) -@test get(StaticTypeAnalysis.canequal(Int, Float64)) -@test get(StaticTypeAnalysis.canequal(Int, Real)) -@test get(StaticTypeAnalysis.canequal(Symbol, Symbol)) -@test !get(StaticTypeAnalysis.canequal(Int, Symbol)) -@test !get(StaticTypeAnalysis.canequal(Int, String)) -@test !get(StaticTypeAnalysis.canequal(Vector{Char}, String)) +@test StaticTypeAnalysis.canequal(Int, Int) +@test StaticTypeAnalysis.canequal(Int, Float64) +@test StaticTypeAnalysis.canequal(Int, Real) +@test StaticTypeAnalysis.canequal(Symbol, Symbol) +@test !StaticTypeAnalysis.canequal(Int, Symbol) +@test !StaticTypeAnalysis.canequal(Int, String) +@test !StaticTypeAnalysis.canequal(Vector{Char}, String) @test StaticTypeAnalysis.infertype(String, (Int,)) == Union{} @test StaticTypeAnalysis.infertype(String, (String,)) == String @test StaticTypeAnalysis.infertype(String, (Vector{UInt8},)) == String -@test StaticTypeAnalysis.infertype(String, (IOBuffer,)) == String @test StaticTypeAnalysis.eltype(Tuple{Int,Int}) == Int @test StaticTypeAnalysis.eltype(Tuple{Int,String}) == Any @@ -22,13 +21,13 @@ using Lint: StaticTypeAnalysis @test StaticTypeAnalysis.eltype(Vector{Int}) == Int @test StaticTypeAnalysis.eltype(Array{Int}) == Int -@test isnull(StaticTypeAnalysis.length(Tuple)) -@test isnull(StaticTypeAnalysis.length(Array)) -@test isnull(StaticTypeAnalysis.length(Vector{Int})) -@test isnull(StaticTypeAnalysis.length(Array{Int})) -@test get(StaticTypeAnalysis.length(Pair)) == 2 -@test get(StaticTypeAnalysis.length(Tuple{Int,String})) == 2 -@test get(StaticTypeAnalysis.length(NTuple{10, Integer})) == 10 +@test StaticTypeAnalysis.length(Tuple) === nothing +@test StaticTypeAnalysis.length(Array) === nothing +@test StaticTypeAnalysis.length(Vector{Int}) === nothing +@test StaticTypeAnalysis.length(Array{Int}) === nothing +@test StaticTypeAnalysis.length(Pair) == 2 +@test StaticTypeAnalysis.length(Tuple{Int,String}) == 2 +@test StaticTypeAnalysis.length(NTuple{10, Integer}) == 10 @test StaticTypeAnalysis.typeof_nth(Tuple{Int,String}, 1) == Int @test StaticTypeAnalysis.typeof_nth(Tuple{Int,String}, 2) == String diff --git a/test/strings.jl b/test/strings.jl index 78be408..a0e55ac 100644 --- a/test/strings.jl +++ b/test/strings.jl @@ -3,7 +3,7 @@ s = "a" + "b" """ msgs = lintstr(s) @test msgs[1].code == :E422 -@test contains(msgs[1].message, "string uses * to concatenate") +@test occursin("string uses * to concatenate", msgs[1].message) s = """ function f(x) @@ -12,7 +12,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E422 -@test contains(msgs[1].message, "string uses * to concatenate") +@test occursin("string uses * to concatenate", msgs[1].message) @test messageset(lintstr(""" s = String(1) @@ -24,7 +24,7 @@ s = "a" + b """ msgs = lintstr(s) @test msgs[1].code == :E422 -@test contains(msgs[1].message, "string uses * to concatenate") +@test occursin("string uses * to concatenate", msgs[1].message) s = """ function f() @@ -35,7 +35,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(b) == $(Compat.String)") +@test occursin("typeof(b, msgs[1].message) == $(Compat.String)") u = """ 안녕하세요 = "Hello World" diff --git a/test/throw.jl b/test/throw.jl index 7dd4ed5..75b7848 100644 --- a/test/throw.jl +++ b/test/throw.jl @@ -9,4 +9,4 @@ MethodError("blah") """ msgs = lintstr(s) @test msgs[1].code == :W448 -@test contains(msgs[1].message, "MethodError is an Exception but it is not enclosed in a throw") +@test occursin("MethodError is an Exception but it is not enclosed in a throw", msgs[1].message) diff --git a/test/tuple.jl b/test/tuple.jl index d0e6159..595d3b3 100644 --- a/test/tuple.jl +++ b/test/tuple.jl @@ -5,14 +5,14 @@ s = """ """ msgs = lintstr(s) @test msgs[1].code == :W546 -@test contains(msgs[1].message, "implicitly discarding values, 2 of 3 used") +@test occursin("implicitly discarding values, 2 of 3 used", msgs[1].message) s = """ a, = (1,2,3) """ msgs = lintstr(s) @test msgs[1].code == :W546 -@test contains(msgs[1].message, "implicitly discarding values, 1 of 3 used") +@test occursin("implicitly discarding values, 1 of 3 used", msgs[1].message) @test isempty(lintstr("a = (1, 2, 3)")) @@ -30,6 +30,6 @@ s = """ """ msgs = lintstr(s) @test msgs[1].code == :E418 -@test contains(msgs[1].message, "RHS is a tuple, 3 of 2 variables used") +@test occursin("RHS is a tuple, 3 of 2 variables used", msgs[1].message) end diff --git a/test/type.jl b/test/type.jl index 6b388ec..22389a8 100644 --- a/test/type.jl +++ b/test/type.jl @@ -6,7 +6,7 @@ msgs = lintstr(s) @test msgs[1].code == :I393 @test msgs[1].variable == "Int64" - @test contains(msgs[1].message, "using an existing type as type parameter name is probably a typo") + @test occursin(msgs[1].message, "using an existing type as type parameter name is probably a typo") @test messageset(lintstr(""" type MyType{Int64} <: Float64 @@ -21,7 +21,7 @@ end """) @test messageset(msgs) == Set([:E513]) @test msgs[1].variable == "T <: Int" - @test contains(msgs[1].message, "leaf type as a type constraint makes no sense") + @test occursin(msgs[1].message, "leaf type as a type constraint makes no sense") msgs = lintstr(""" type MyType{T<:Int, Int<:Real} @@ -29,9 +29,9 @@ end """) @test messageset(msgs) == Set([:E513, :E538]) @test msgs[1].variable == "T <: Int" - @test contains(msgs[1].message, "leaf type as a type constraint makes no sense") + @test occursin(msgs[1].message, "leaf type as a type constraint makes no sense") @test msgs[2].variable == "Int" - @test contains(msgs[2].message, "known type in parametric data type, use {T<:...}") + @test occursin(msgs[2].message, "known type in parametric data type, use {T<:...}") end @testset "E538" begin @@ -40,7 +40,7 @@ end end """) @test messageset(msgs) == Set([:E538]) - @test contains(msgs[1].message, "known type in parametric data type, use {T<:...}") + @test occursin(msgs[1].message, "known type in parametric data type, use {T<:...}") msgs = lintstr(""" type SomeType @@ -49,7 +49,7 @@ end end """) @test messageset(msgs) == Set([:E538]) - @test contains(msgs[1].message, "known type in parametric data type, use {T<:...}") + @test occursin(msgs[1].message, "known type in parametric data type, use {T<:...}") end # TODO: this inner constructor syntax is deprecated @@ -79,7 +79,7 @@ end msgs = lintstr(s) @test_broken msgs[1].code == :E517 @test_broken msgs[1].variable == "MyTypo" - @test_broken contains(msgs[1].message, "constructor-like function name doesn't match type MyType") + @test_broken occursin(msgs[1].message, "constructor-like function name doesn't match type MyType") @test_broken isempty(lintstr(""" type MyType{T} @@ -123,7 +123,7 @@ end """ msgs = lintstr(s) @test_broken msgs[1].code == :E611 -@test_broken contains(msgs[1].message, "constructor doesn't seem to return the constructed object") +@test_broken occursin(msgs[1].message, "constructor doesn't seem to return the constructed object") s = """ type MyType{T<:Integer} @@ -146,7 +146,7 @@ end msgs = lintstr(s) @test msgs[1].code == :I691 @test msgs[1].variable == "a" -@test contains(msgs[1].message, "a type is not given to the field which can be slow") +@test occursin(msgs[1].message, "a type is not given to the field which can be slow") s = """ type MyType @@ -165,7 +165,7 @@ end msgs = lintstr(s) @test msgs[1].code == :I692 @test msgs[1].variable == "a" -@test contains(msgs[1].message, "array field has no dimension which can be slow") +@test occursin(msgs[1].message, "array field has no dimension which can be slow") s = """ type MyType @@ -184,14 +184,14 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E425 -@test contains(msgs[1].message, "use lintpragma macro inside type declaration") +@test occursin(msgs[1].message, "use lintpragma macro inside type declaration") s = """ @compat primitive type 8 a end """ msgs = lintstr(s) @test msgs[1].code == :E101 -@test contains(msgs[1].message, "this expression must be a Symbol") +@test occursin(msgs[1].message, "this expression must be a Symbol") s = """ type MyType @@ -220,10 +220,10 @@ end """ msgs = lintstr(s) @test msgs[1].code == :E417 -@test contains(msgs[1].message, "anonymous function inside type definition") +@test occursin(msgs[1].message, "anonymous function inside type definition") @test_broken msgs[2].code == :E517 @test_broken msgs[2].variable == "" -@test_broken contains(msgs[2].message, "constructor-like function name doesn't match type MyType") +@test_broken occursin(msgs[2].message, "constructor-like function name doesn't match type MyType") s = """ type MyType @@ -234,7 +234,7 @@ end """ msgs = lintstr(s) @test_broken msgs[1].code == :I671 -@test_broken contains(msgs[1].message, "new is provided with fewer arguments than fields") +@test_broken occursin(msgs[1].message, "new is provided with fewer arguments than fields") @testset "Inner Constructors" begin @test isempty(lintstr(""" @@ -261,7 +261,7 @@ end """ msgs = lintstr(s) @test_broken msgs[1].code == :E435 -@test_broken contains(msgs[1].message, "new is provided with more arguments than fields") +@test_broken occursin(msgs[1].message, "new is provided with more arguments than fields") s = """ type MyType @@ -305,4 +305,4 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I771 -@test contains(msgs[1].message, "type names should start with an upper case") +@test occursin(msgs[1].message, "type names should start with an upper case") diff --git a/test/typecheck.jl b/test/typecheck.jl index c4fad60..4a72f4f 100644 --- a/test/typecheck.jl +++ b/test/typecheck.jl @@ -21,9 +21,9 @@ end """ msgs = lintstr(s) @test_broken msgs[1].code == :W545 -@test_broken contains(msgs[1].message, "previously used variable has apparent type") +@test_broken occursin("previously used variable has apparent type", msgs[1].message) @test msgs[end].code == :I271 -@test_broken contains(msgs[end].message, "typeof(x) == Complex") +@test_broken occursin("typeof(x, msgs[end].message) == Complex") s = """ function f() @@ -34,7 +34,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x) == Float64") +@test occursin("typeof(x, msgs[1].message) == Float64") s = """ function f() @@ -47,9 +47,9 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x) == Array{Float64,1}") +@test occursin("typeof(x, msgs[1].message) == Array{Float64,1}") @test msgs[2].code == :I271 -@test contains(msgs[2].message, "typeof(y) == Array{Bool,2}") +@test occursin("typeof(y, msgs[2].message) == Array{Bool,2}") s = """ function f() @@ -61,8 +61,8 @@ end """ msgs = lintstr(s) @test_broken msgs[1].code == :W545 -@test_broken contains(msgs[1].message, "previously used variable has apparent type Int64, but " * - "now assigned Float64") +@test_broken occursin("previously used variable has apparent type Int64, but " * + "now assigned Float64", msgs[1].message) s = """ function f(arr::Array{Any,1}) @@ -73,7 +73,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x) == Int") +@test occursin("typeof(x, msgs[1].message) == Int") s = """ g(x) = x @@ -86,7 +86,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x) == Function") +@test occursin("typeof(x, msgs[1].message) == Function") s = """ module MyModule @@ -100,7 +100,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(x) == Module") +@test occursin("typeof(x, msgs[1].message) == Module") s = """ function f() @@ -112,7 +112,7 @@ end msgs = lintstr(s) @test msgs[1].code == :E525 @test msgs[1].variable == "z" -@test contains(msgs[1].message, "is of an immutable type Complex") +@test occursin("is of an immutable type Complex", msgs[1].message) #= TODO: the warning here should be on a = Array{Int32, n}, not the E521 s = """ @@ -125,7 +125,7 @@ end msgs = lintstr(s) @test msgs[1].code == :E521 @test msgs[1].variable == "a" -@test contains(msgs[1].message, "apparent type Type") +@test occursin("apparent type Type", msgs[1].message) =# include("E522.jl") @@ -139,7 +139,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(d) == Dict") +@test occursin("typeof(d, msgs[1].message) == Dict") s = """ function f() @@ -150,7 +150,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(d) == Dict") +@test occursin("typeof(d, msgs[1].message) == Dict") s = """ function f(n) @@ -166,10 +166,10 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(a) == Array{Float64,3}") -@test contains(msgs[2].message, "typeof(c) == Array{Float64,3}") -@test contains(msgs[3].message, "typeof(d) == Array{Float64,2}") -@test contains(msgs[4].message, "typeof(e1) == Array{Float64,1}") +@test occursin("typeof(a, msgs[1].message) == Array{Float64,3}") +@test occursin("typeof(c, msgs[2].message) == Array{Float64,3}") +@test occursin("typeof(d, msgs[3].message) == Array{Float64,2}") +@test occursin("typeof(e1, msgs[4].message) == Array{Float64,1}") s = """ function f() @@ -180,7 +180,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(s) == Tuple{Int64,Int64,Int64}") +@test occursin("typeof(s, msgs[1].message) == Tuple{Int64,Int64,Int64}") s = """ function f() @@ -190,7 +190,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "typeof(a) == Array{Complex{Float64},1}") +@test occursin("typeof(a, msgs[1].message) == Array{Complex{Float64},1}") s = """ Complex(0.0,0.0) == 0 @@ -203,7 +203,7 @@ s = """ """ msgs = lintstr(s) @test msgs[1].code == :W542 -@test contains(msgs[1].message, "comparing apparently incompatible type") +@test occursin("comparing apparently incompatible type", msgs[1].message) s = """ s = Union(Int,Double) diff --git a/test/undeclare.jl b/test/undeclare.jl index 639214c..400d639 100644 --- a/test/undeclare.jl +++ b/test/undeclare.jl @@ -5,7 +5,7 @@ end """) @test messageset(msgs) == Set([:E321]) - @test contains(msgs[1].message, "use of undeclared symbol") + @test occursin("use of undeclared symbol", msgs[1].message) @test isempty(lintstr(""" function f(x) @@ -51,7 +51,7 @@ end """ msgs = lintstr(s) @test_broken msgs[1].code == :I482 -@test_broken contains(msgs[1].message, "used in a local scope") +@test_broken occursin("used in a local scope", msgs[1].message) s = """ function f() diff --git a/test/variables.jl b/test/variables.jl new file mode 100644 index 0000000..4ffa3d8 --- /dev/null +++ b/test/variables.jl @@ -0,0 +1,19 @@ +@testset "Variables basics" begin + + let s = """ + function f() + x = 0 + x + end + """ + @test lintstr(s) == [] + end + let s = """ + function f() + x = zeros(10, 10) + x + end + """ + @test lintstr(s) == [] + end +end diff --git a/test/versions.jl b/test/versions.jl index 977eeda..ee46eca 100644 --- a/test/versions.jl +++ b/test/versions.jl @@ -9,10 +9,10 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "Reachable by 0.3") -@test contains(msgs[2].message, "Unreachable by 0.4") -@test contains(msgs[3].message, "Unreachable by 0.3") -@test contains(msgs[4].message, "Reachable by 0.4") +@test occursin("Reachable by 0.3", msgs[1].message) +@test occursin("Unreachable by 0.4", msgs[2].message) +@test occursin("Unreachable by 0.3", msgs[3].message) +@test occursin("Reachable by 0.4", msgs[4].message) s = """ test() = true @@ -27,10 +27,10 @@ end msgs = lintstr(s) @test length(msgs) == 4 @test msgs[1].code == :I271 -@test contains(msgs[1].message, "Reachable by 0.3") -@test contains(msgs[2].message, "Unreachable by 0.4") -@test contains(msgs[3].message, "Reachable by 0.3") -@test contains(msgs[4].message, "Reachable by 0.4") # we cannot prove unreachable +@test occursin("Reachable by 0.3", msgs[1].message) +@test occursin("Unreachable by 0.4", msgs[2].message) +@test occursin("Reachable by 0.3", msgs[3].message) +@test occursin("Reachable by 0.4", msgs[4].message) # we cannot prove unreachable s = """ test() = true @@ -45,10 +45,10 @@ end msgs = lintstr(s) @test length(msgs) == 4 @test msgs[1].code == :I271 -@test contains(msgs[1].message, "Reachable by 0.3") -@test contains(msgs[2].message, "Reachable by 0.4") -@test contains(msgs[3].message, "Unreachable by 0.3") -@test contains(msgs[4].message, "Reachable by 0.4") +@test occursin("Reachable by 0.3", msgs[1].message) +@test occursin("Reachable by 0.4", msgs[2].message) +@test occursin("Unreachable by 0.3", msgs[3].message) +@test occursin("Reachable by 0.4", msgs[4].message) # testing `||`, should be rare s = """ @@ -63,10 +63,10 @@ end msgs = lintstr(s) @test length(msgs) == 4 @test msgs[1].code == :I271 -@test contains(msgs[1].message, "Unreachable by 0.3") -@test contains(msgs[2].message, "Reachable by 0.4") -@test contains(msgs[3].message, "Reachable by 0.3") -@test contains(msgs[4].message, "Unreachable by 0.4") +@test occursin("Unreachable by 0.3", msgs[1].message) +@test occursin("Reachable by 0.4", msgs[2].message) +@test occursin("Reachable by 0.3", msgs[3].message) +@test occursin("Unreachable by 0.4", msgs[4].message) s = """ if !(VERSION >= v"0.4-") @@ -79,7 +79,7 @@ end """ msgs = lintstr(s) @test msgs[1].code == :I271 -@test contains(msgs[1].message, "Reachable by 0.3") -@test contains(msgs[2].message, "Unreachable by 0.4") -@test contains(msgs[3].message, "Unreachable by 0.3") -@test contains(msgs[4].message, "Reachable by 0.4") +@test occursin("Reachable by 0.3", msgs[1].message) +@test occursin("Unreachable by 0.4", msgs[2].message) +@test occursin("Unreachable by 0.3", msgs[3].message) +@test occursin("Reachable by 0.4", msgs[4].message)