diff --git a/src/parsing.jl b/src/parsing.jl index 09d237e6..68a6f68f 100644 --- a/src/parsing.jl +++ b/src/parsing.jl @@ -421,6 +421,31 @@ fun_to_vec(ex::QuoteNode; outer_flags::Union{NamedTuple, Nothing}=nothing, allow_multicol::Bool = false) = ex +# Catch-all method for literal values (Bool, Int, String, etc.). +# Behaviour mirrors the underlying `DataFrames.subset` exactly, keyed on the +# `@byrow` flag: +# * byrow -> `[] => ByRow(Returns(ex))`, a per-row constant vector, same +# as `subset(df, [] => ByRow(Returns(true)))` (which works). +# * !byrow -> a bare literal is a scalar predicate, which `subset` rejects +# (`subset(df, [] => Returns(true))` errors). Raise a clear +# ArgumentError in place of the #259 MethodError. +# ByRow is applied only when byrow is set; applying it unconditionally would +# make `@subset(df, true)` accept what the function rejects. +function fun_to_vec(ex; + no_dest::Bool=false, + gensym_names::Bool=false, + outer_flags::Union{NamedTuple, Nothing}=nothing, + allow_multicol::Bool = false) + byrow = outer_flags !== nothing && outer_flags[BYROW_SYM][] + if byrow + return :([] => $ByRow($(Base.Returns)($ex))) + end + throw(ArgumentError( + "literal value `$ex` is not a valid column expression; pass a " * + "column reference or a vectorised predicate (e.g. `:x .> 0`), " * + "or use `@rsubset` for a per-row literal")) +end + """ rename_kw_to_pair(ex::Expr) diff --git a/test/subset.jl b/test/subset.jl index 505f1f9b..05f2067d 100644 --- a/test/subset.jl +++ b/test/subset.jl @@ -173,4 +173,24 @@ end @test @subset!(groupby(copy(df), :g), :c .== :g) ≅ df[[], :] end +@testset "@subset / @rsubset with literal values" begin + df = DataFrame(A = [1, 2, 3], B = [4, 5, 6]) + + # @subset (not byrow): a bare literal is a scalar predicate, which + # subset rejects (subset(df, [] => Returns(true)) errors). It must error, + # not be silently broadcast. The error is raised during macro expansion + # (as the #259 MethodError was), so it surfaces via @eval as LoadError. + @test_throws LoadError @eval @subset($df, true) + @test_throws LoadError @eval @subset($df, false) + @test_throws LoadError @eval @subset($df, true, :A .> 1) + @test_throws LoadError @eval @subset!(copy($df), true) + @test_throws LoadError @eval @subset(groupby($df, :B), true) + + # @rsubset (byrow): a per-row literal is well-defined and matches + # subset(df, [] => ByRow(Returns(true))), which works. + @test @rsubset(df, true) ≅ df + @test @rsubset(df, false) ≅ df[Int[], :] + @test @rsubset(groupby(df, :B), true) ≅ df +end + end # module