From 0fb5806d84d85efc9abaa876bc10378d4aef8951 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Mon, 20 Jul 2020 11:47:40 -0700 Subject: [PATCH 1/7] Add curried methods ===(T) and isa(T) --- src/builtins.c | 12 ++++++++++-- test/operators.jl | 13 +++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/builtins.c b/src/builtins.c index 6d8591ea25b72..3afc14d937a58 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -364,7 +364,11 @@ JL_DLLEXPORT uintptr_t jl_object_id(jl_value_t *v) JL_NOTSAFEPOINT JL_CALLABLE(jl_f_is) { - JL_NARGS(===, 2, 2); + JL_NARGS(===, 1, 2); + if (nargs == 1) { + jl_function_t *fix2 = jl_get_function(jl_base_module, "Fix2"); + return jl_call2(fix2, jl_builtin_is, args[0]); + } if (args[0] == args[1]) return jl_true; return jl_egal(args[0], args[1]) ? jl_true : jl_false; @@ -429,7 +433,11 @@ JL_CALLABLE(jl_f_issubtype) JL_CALLABLE(jl_f_isa) { - JL_NARGS(isa, 2, 2); + JL_NARGS(isa, 1, 2); + if (nargs == 1) { + jl_function_t *fix2 = jl_get_function(jl_base_module, "Fix2"); + return jl_call2(fix2, jl_builtin_isa, args[0]); + } JL_TYPECHK(isa, type, args[1]); return (jl_isa(args[0],args[1]) ? jl_true : jl_false); } diff --git a/test/operators.jl b/test/operators.jl index fb0af5b552047..6e96ad42d9846 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -1,5 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +using Base: Fix1, Fix2 using Random: randstring @testset "ifelse" begin @@ -243,3 +244,15 @@ end @test gt5(6) && !gt5(5) @test lt5(4) && !lt5(5) end + +@testset "curried comparisons (builtins)" begin + isaSymbol = isa(Symbol) + is5 = (===)(5) + + @test isaSymbol(:yes) && !isaSymbol("no") + @test is5(5) && !is5(5.0) + + # Make sure they are dispatchable: + @test isaSymbol isa Fix2{typeof(isa),Type{Symbol}} + @test is5 isa Fix2{typeof(===),Int} +end From f50a4a0097957fcd3309ae82d2643d1867d1673e Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Mon, 20 Jul 2020 12:21:21 -0700 Subject: [PATCH 2/7] Sharper type parameters for Fix1 and Fix2 on types --- base/operators.jl | 4 ++++ test/operators.jl | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/base/operators.jl b/base/operators.jl index 5402f068ed7e4..8f8a199dba2e2 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -925,7 +925,9 @@ struct Fix1{F,T} <: Function x::T Fix1(f::F, x::T) where {F,T} = new{F,T}(f, x) + Fix1(f::F, x::Type{T}) where {F,T} = new{F,Type{T}}(f, x) Fix1(f::Type{F}, x::T) where {F,T} = new{Type{F},T}(f, x) + Fix1(f::Type{F}, x::Type{T}) where {F,T} = new{Type{F},Type{T}}(f, x) end (f::Fix1)(y) = f.f(f.x, y) @@ -942,7 +944,9 @@ struct Fix2{F,T} <: Function x::T Fix2(f::F, x::T) where {F,T} = new{F,T}(f, x) + Fix2(f::F, x::Type{T}) where {F,T} = new{F,Type{T}}(f, x) Fix2(f::Type{F}, x::T) where {F,T} = new{Type{F},T}(f, x) + Fix2(f::Type{F}, x::Type{T}) where {F,T} = new{Type{F},Type{T}}(f, x) end (f::Fix2)(y) = f.f(y, f.x) diff --git a/test/operators.jl b/test/operators.jl index 6e96ad42d9846..f1bba2027fd5a 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -256,3 +256,12 @@ end @test isaSymbol isa Fix2{typeof(isa),Type{Symbol}} @test is5 isa Fix2{typeof(===),Int} end + +@testset "Fix1 and Fix2 on types" begin + @test Fix1(tuple, Fix1) isa Fix1{typeof(tuple),Type{Fix1}} + @test Fix1(Fix1, tuple) isa Fix1{Type{Fix1},typeof(tuple)} + @test Fix1(Fix1, Fix1) isa Fix1{Type{Fix1},Type{Fix1}} + @test Fix2(tuple, Fix2) isa Fix2{typeof(tuple),Type{Fix2}} + @test Fix2(Fix2, tuple) isa Fix2{Type{Fix2},typeof(tuple)} + @test Fix2(Fix2, Fix2) isa Fix2{Type{Fix2},Type{Fix2}} +end From 4be5c9a0fb4f117c11563f16ccad678ae4fcb80e Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 21 Jul 2020 13:12:54 -0700 Subject: [PATCH 3/7] Check fix2 == NULL --- src/builtins.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/builtins.c b/src/builtins.c index 3afc14d937a58..edb3ddca4c3ab 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -367,6 +367,8 @@ JL_CALLABLE(jl_f_is) JL_NARGS(===, 1, 2); if (nargs == 1) { jl_function_t *fix2 = jl_get_function(jl_base_module, "Fix2"); + if (fix2 == NULL) + jl_undefined_var_error(jl_symbol("Fix2")); return jl_call2(fix2, jl_builtin_is, args[0]); } if (args[0] == args[1]) @@ -436,6 +438,8 @@ JL_CALLABLE(jl_f_isa) JL_NARGS(isa, 1, 2); if (nargs == 1) { jl_function_t *fix2 = jl_get_function(jl_base_module, "Fix2"); + if (fix2 == NULL) + jl_undefined_var_error(jl_symbol("Fix2")); return jl_call2(fix2, jl_builtin_isa, args[0]); } JL_TYPECHK(isa, type, args[1]); From 0e7a5fac8af4a2c7dd0047aec1963e2b3635aa6b Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 21 Jul 2020 13:43:05 -0700 Subject: [PATCH 4/7] Add !==(x) --- base/operators.jl | 2 ++ test/operators.jl | 2 ++ 2 files changed, 4 insertions(+) diff --git a/base/operators.jl b/base/operators.jl index 8f8a199dba2e2..5d8e9eadab3df 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -986,6 +986,8 @@ used to implement specialized methods. """ !=(x) = Fix2(!=, x) +!==(x) = Fix2(!==, x) + """ >=(x) diff --git a/test/operators.jl b/test/operators.jl index f1bba2027fd5a..f8867a4bcedb5 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -230,6 +230,7 @@ end end @testset "curried comparisons" begin + isnot5 = (!==)(5) eql5 = (==)(5) neq5 = (!=)(5) gte5 = (>=)(5) @@ -237,6 +238,7 @@ end gt5 = (>)(5) lt5 = (<)(5) + @test isnot5(5.0) && !isnot5(5) @test eql5(5) && !eql5(0) @test neq5(6) && !neq5(5) @test gte5(5) && gte5(6) From e48f18373a1d242108f8b522653470e70264c22d Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 21 Jul 2020 13:43:12 -0700 Subject: [PATCH 5/7] Add docstrings --- base/docs/basedocs.jl | 30 +++++++++++++++++++++++++++++- base/operators.jl | 9 +++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index a331f9b5855de..5b5cb2a706385 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -1576,6 +1576,17 @@ Integer """ invoke +""" + ===(x) + +Create a function that compares its argument to `x` using [`===`](@ref), i.e. +a function equivalent to `y -> y == x`. + +!!! compat "Julia 1.6" + The partially applied form `===(x)` requires Julia 1.6 or later. +""" +===(x) + """ isa(x, type) -> Bool @@ -1600,7 +1611,24 @@ julia> 1 isa Number true ``` """ -isa +isa(x, T) + +""" + isa(type) + +Create a function that compares its argument to `x` using [`isa`](@ref), i.e. +a function equivalent to `type -> x isa type`. + +!!! compat "Julia 1.6" + The partially applied form `isa(type)` requires Julia 1.6 or later. + +# Examples +```jldoctest +julia> isa(Int)(1) +true +``` +""" +isa(T) """ DivideError() diff --git a/base/operators.jl b/base/operators.jl index 5d8e9eadab3df..1cb55e637ca52 100644 --- a/base/operators.jl +++ b/base/operators.jl @@ -986,6 +986,15 @@ used to implement specialized methods. """ !=(x) = Fix2(!=, x) +""" + !==(x) + +Create a function that compares its argument to `x` using [`!==`](@ref), i.e. +a function equivalent to `y -> y !== x`. + +!!! compat "Julia 1.6" + This functionality requires at least Julia 1.6. +""" !==(x) = Fix2(!==, x) """ From a0bf2d89a28fd7f1c2320f0568b3e1f87794b4d1 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 21 Jul 2020 13:57:04 -0700 Subject: [PATCH 6/7] Support the unary methods in the compiler --- base/compiler/abstractinterpretation.jl | 4 ++-- base/compiler/ssair/passes.jl | 1 + base/compiler/tfuncs.jl | 23 +++++++++++++++++++++-- test/compiler/inference.jl | 14 ++++++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 03a8530c7aead..f0cd95539ec46 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -806,7 +806,7 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, fargs::U end elseif (rt === Bool || (isa(rt, Const) && isa(rt.val, Bool))) && isa(fargs, Vector{Any}) # perform very limited back-propagation of type information for `is` and `isa` - if f === isa + if f === isa && la == 3 a = ssa_def_slot(fargs[2], sv) if isa(a, Slot) aty = widenconst(argtypes[2]) @@ -825,7 +825,7 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, fargs::U end end end - elseif f === (===) + elseif f === (===) && la == 3 a = ssa_def_slot(fargs[2], sv) b = ssa_def_slot(fargs[3], sv) aty = argtypes[2] diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index a2dc6d6e75f60..1ed1ae278c310 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -570,6 +570,7 @@ function getfield_elim_pass!(ir::IRCode, domtree::DomTree) compact.ssa_rename[compact.idx-1] = pi continue elseif is_known_call(stmt, (===), compact) + length(stmt.args) == 3 || continue c1 = compact_exprtype(compact, stmt.args[2]) c2 = compact_exprtype(compact, stmt.args[3]) if !(isa(c1, Const) || isa(c2, Const)) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index a6007e0cfac62..eb1fb45b52791 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -218,6 +218,23 @@ function ifelse_tfunc(@nospecialize(cnd), @nospecialize(x), @nospecialize(y)) end add_tfunc(ifelse, 3, 3, ifelse_tfunc, 1) +# Note: `x` is manually specified at the moment so that it's always a `DataType` +function fix2_tfunc(x::DataType, y) + if !(isdefined(Main, :Base) && isdefined(Main.Base, :Fix2) && isconst(Main.Base, :Fix2)) + return Any + end + y = widenconst(y) + Fix2 = Main.Base.Fix2 + if isconcretetype(y) || isType(y) + return Fix2{x,y} + elseif y isa UnionAll + return rewrap_unionall(Fix2{x,unwrap_unionall(y)}, y) + elseif y isa Union + return Union{fix2_tfunc(x, y.a),fix2_tfunc(x, y.b)} + end + return Fix2{x} +end + function egal_tfunc(@nospecialize(x), @nospecialize(y)) xx = widenconditional(x) yy = widenconditional(y) @@ -239,7 +256,8 @@ function egal_tfunc(@nospecialize(x), @nospecialize(y)) end return Bool end -add_tfunc(===, 2, 2, egal_tfunc, 1) +egal_tfunc(y) = fix2_tfunc(typeof(===), y) +add_tfunc(===, 1, 2, egal_tfunc, 1) function isdefined_nothrow(argtypes::Array{Any, 1}) length(argtypes) == 2 || return false @@ -555,7 +573,8 @@ function isa_tfunc(@nospecialize(v), @nospecialize(tt)) # TODO: handle non-leaftype(t) by testing against lower and upper bounds return Bool end -add_tfunc(isa, 2, 2, isa_tfunc, 0) +isa_tfunc(y) = fix2_tfunc(typeof(isa), y) +add_tfunc(isa, 1, 2, isa_tfunc, 0) function subtype_tfunc(@nospecialize(a), @nospecialize(b)) a, isexact_a = instanceof_tfunc(a) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 3a268c8bb638d..5bfddc7f02d94 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -2734,3 +2734,17 @@ end f_generator_splat(t::Tuple) = tuple((identity(l) for l in t)...) @test Base.return_types(f_generator_splat, (Tuple{Symbol, Int64, Float64},)) == Any[Tuple{Symbol, Int64, Float64}] + +f_is2(x) = ===(x) +@test @inferred(f_is2(1)) === Base.Fix2(===, 1) + +f_is2_ref(x) = ===(x[]) +@test Base.return_types(f_is2_ref, Tuple{Base.RefValue{Vector}}) == + Any[Base.Fix2{typeof(===),Vector{T}} where {T}] + +f_isa2(T) = isa(T) +@test @inferred(f_isa2(Int)) === Base.Fix2(isa, Int) + +f_isa2(b, T, S) = isa(b ? T : S) +@test Base.return_types(f_isa2, Tuple{Bool,Type{Int64},Type{Float64}}) == + Any[Union{Base.Fix2{typeof(isa),Type{Float64}},Base.Fix2{typeof(isa),Type{Int64}}}] From 6951577f99a6e97505cf527fd23b806df227dada Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 21 Jul 2020 21:54:42 -0700 Subject: [PATCH 7/7] Annotate builtins with JL_GLOBALLY_ROOTED --- src/builtin_proto.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 8021c404bd5e7..970cdf98c4944 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -12,11 +12,11 @@ extern "C" { #ifdef DEFINE_BUILTIN_GLOBALS #define DECLARE_BUILTIN(name) \ JL_CALLABLE(jl_f_##name); \ - jl_value_t *jl_builtin_##name + jl_value_t *jl_builtin_##name JL_GLOBALLY_ROOTED #else #define DECLARE_BUILTIN(name) \ JL_CALLABLE(jl_f_##name); \ - extern jl_value_t *jl_builtin_##name + extern jl_value_t *jl_builtin_##name JL_GLOBALLY_ROOTED #endif DECLARE_BUILTIN(throw); DECLARE_BUILTIN(is);