# This file is a part of Julia. License is MIT: https://julialang.org/license

# Tests for deprecated functionality.
#
# These can't be run with --depwarn=error, so currently require special
# treatment when run inside the test system.

using Test
using Logging

module DeprecationTests # to test @deprecate
    f() = true

    const source_file = Symbol(@__FILE__)

    # test the Symbol path of @deprecate
    const f1_method_line = @__LINE__() + 1
    @deprecate f1 f
    @deprecate f2 f false # test that f2 is not exported

    # test the Expr path of @deprecate
    const f3_method_line = @__LINE__() + 1
    @deprecate f3() f()
    @deprecate f4() f() false # test that f4 is not exported
    @deprecate f5(x::T) where T f()

    # test deprecation of a constructor
    struct A{T} end
    @deprecate A{T}(x::S) where {T, S} f()

    module Sub
    f1() = true
    function f2 end
    end
    @deprecate Sub.f1() f() false
    @deprecate Sub.f2 f false

    # test that @deprecate_moved can be overridden by an import
    const foo1234_method_line = @__LINE__() + 1
    Base.@deprecate_moved foo1234 "Foo"
    Base.@deprecate_moved bar "Bar" false

    # test that positional and keyword arguments are forwarded when
    # there is no explicit type annotation
    new_return_args(args...; kwargs...) = args, NamedTuple(kwargs)
    @deprecate old_return_args new_return_args
end # module
module Foo1234
    export foo1234
    foo1234(x) = x+1
end

# issue #21972
struct T21972
    @noinline function T21972()
        Base.depwarn("something", :T21972)
        new()
    end
end

# Create a consistent call frame for nowarn tests
@noinline call(f, args...) = @noinline f(args...)

# Given this is a sub-processed test file, not using @testsets avoids
# leaking the report print into the Base test runner report
begin # @deprecate
    using .DeprecationTests
    using .Foo1234
    @test foo1234(3) == 4
    @test_throws ErrorException DeprecationTests.bar(3)

    # 22845
    ex = :(module M22845; import ..DeprecationTests: bar;
                          bar(x::Number) = x + 3; end)
    @test_warn "importing deprecated binding" eval(ex)
    @Core.latestworld
    @test @test_nowarn(DeprecationTests.bar(4)) == 7

    @test @test_warn "`f1` is deprecated, use `f` instead." f1()

    @test_throws UndefVarError f2() # not exported
    @test @test_warn "`f2` is deprecated, use `f` instead." DeprecationTests.f2()

    @test @test_warn "`f3()` is deprecated, use `f()` instead." f3()

    @test_throws UndefVarError f4() # not exported
    @test @test_warn "`f4()` is deprecated, use `f()` instead." DeprecationTests.f4()

    @test @test_warn "`f5(x::T) where T` is deprecated, use `f()` instead." f5(1)

    @test @test_warn "`A{T}(x::S) where {T, S}` is deprecated, use `f()` instead." A{Int}(1.)

    @test @test_warn "`Sub.f1()` is deprecated, use `f()` instead." DeprecationTests.Sub.f1()

    # @deprecate-generated methods should report the macro call site.
    let m = only(methods(DeprecationTests.f1))
        @test m.file == DeprecationTests.source_file
        @test m.line == DeprecationTests.f1_method_line
    end
    let m = only(methods(DeprecationTests.f3))
        @test m.file == DeprecationTests.source_file
        @test m.line == DeprecationTests.f3_method_line
    end
    let m = only(methods(DeprecationTests.foo1234))
        @test m.file == DeprecationTests.source_file
        @test m.line == DeprecationTests.foo1234_method_line
    end

    redirect_stderr(devnull) do
        @test call(f1)
        @test call(DeprecationTests.f2)
        @test call(f3)
        @test call(DeprecationTests.f4)
        @test call(f5, 1)
        @test call(A{Int}, 1.)
        @test call(DeprecationTests.Sub.f1)
        @test call(DeprecationTests.Sub.f2)
    end

    @test @test_nowarn call(f1)
    @test @test_nowarn call(DeprecationTests.f2)
    @test @test_nowarn call(f3)
    @test @test_nowarn call(DeprecationTests.f4)
    @test @test_nowarn call(f5, 1)
    @test @test_nowarn call(A{Int}, 1.)
    @test @test_nowarn call(DeprecationTests.Sub.f1)
    @test @test_nowarn call(DeprecationTests.Sub.f2)

    # issue #21972
    @noinline function f21972()
        T21972()
    end
    @test_deprecated "something" f21972()

    @noinline deprecated_typename(T) = Base.typename(T)
    @noinline deprecated_nameof(T) = nameof(T)
    @noinline deprecated_parentmodule(T) = parentmodule(T)
    @noinline deprecated_isabstracttype(T) = isabstracttype(T)
    deprecated_type_ref = Ref{Any}(Type{Int})
    @test @test_warn "calling `typename` on `Type` is deprecated" deprecated_typename(deprecated_type_ref[]) === TypeEq.name
    @test @test_warn "calling `nameof` on `Type` is deprecated" deprecated_nameof(deprecated_type_ref[]) === :Type
    @test @test_warn "calling `parentmodule` on `Type` is deprecated" deprecated_parentmodule(deprecated_type_ref[]) === Core
    @test @test_warn "calling `isabstracttype` on a `Type{...}` is deprecated" deprecated_isabstracttype(deprecated_type_ref[]) === true

    # test that positional and keyword arguments are forwarded when
    # there is no explicit type annotation
    @test_logs (:warn,) @test DeprecationTests.old_return_args(1, 2, 3) == ((1, 2, 3),(;))
    @test_logs (:warn,) @test DeprecationTests.old_return_args(1, 2, 3; a = 4, b = 5) == ((1, 2, 3), (a = 4, b = 5))
end

f24658() = depwarn24658()

depwarn24658() = Base.firstcaller(backtrace(), :_func_not_found_)

begin # firstcaller
    # issue #24658
    @test eval(:(if true; f24658(); end)) == (Ptr{Cvoid}(0),StackTraces.UNKNOWN)
end

# issue #25130
f25130() = Base.depwarn("f25130 message", :f25130)
# The following test is for the depwarn behavior of expressions evaluated at
# top-level, so we can't use the usual `collect_test_logs()` / `with_logger()`
testlogger = Test.TestLogger()
prev_logger = global_logger(testlogger)
# Each call at top level should be distinct. This won't be true if they're
# attributed to internal C frames (including generic dispatch machinery)
f25130()
f25130()
testlogs = testlogger.logs
@test length(testlogs) == 2
@test testlogs[1].id != testlogs[2].id
@test testlogs[1].kwargs[:caller].func === Symbol("top-level scope")
@test all(l.message == "f25130 message" for l in testlogs)
global_logger(prev_logger)


#-------------------------------------------------------------------------------
# BEGIN 1.14 deprecations

begin # SubString noshift deprecations
    @test_deprecated SubString{String}("abcd", 0, 1, Val(:noshift)) == "a"
    let ss = SubString("abcd", 2, 3)
        @test_deprecated (SubString{typeof(ss)}(ss, 0, 1, Val(:noshift)) == "b")
        @test_deprecated (SubString{typeof(ss)}(ss, 0, 1, Val(:noshift)) isa SubString{typeof(ss)})
    end
end


# END 1.14 deprecations

#-------------------------------------------------------------------------------
# BEGIN 0.7 deprecations

begin # parser syntax deprecations
    # #15524
    # @test (@test_deprecated Meta.parse("for a=b f() end")) == :(for a=b; f() end)
    @test_broken length(Test.collect_test_logs(()->Meta.parse("for a=b f() end"))[1]) > 0
end

# END 0.7 deprecations

begin # tuple indexed by float deprecation
    @test_deprecated getindex((1,), 1.0) === 1
    @test_deprecated getindex((1,2), 2.0) === 2
    @test Base.JLOptions().depwarn == 1
    @test_throws Exception @test_warn r"`getindex(t::Tuple, i::Real)` is deprecated" getindex((), 1.0)
    @test_throws Exception @test_warn r"`getindex(t::Tuple, i::Real)` is deprecated" getindex((1,2), 0.0)
    @test_throws Exception @test_warn r"`getindex(t::Tuple, i::Real)` is deprecated" getindex((1,2), -1.0)
end

begin #@deprecated error message
    @test_throws(
        "if the third `export_old` argument is not specified or `true`,",
        @eval @deprecate M.f() g()
    )
    @test_throws(
        "if the third `export_old` argument is not specified or `true`,",
        @eval @deprecate M.f() g() true
    )

    # Given `@deprecated Old{T} where {...} new`, it is unclear if we should generate
    # `Old{T}(args...) where {...} = new(args...)` or
    # `(Old{T} where {...})(args...) = new(args...)`.
    # Since nobody has requested this feature yet, make sure that it throws, until we
    # consciously define
    @test_throws(
        "invalid usage of @deprecate",
        @eval @deprecate Foo{T} where {T <: Int} g true
    )
end
