• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kunzaatko / InterfaceFunctions.jl / 15812244037

23 Jun 2025 12:09AM UTC coverage: 74.0% (+74.0%) from 0.0%
15812244037

push

github

kunzaatko
add(test): Basic tests for the @interface macro

37 of 50 relevant lines covered (74.0%)

39.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.0
/src/InterfaceFunctions.jl
1
module InterfaceFunctions
2

3
using MacroTools
4

5
# NOTE: `@interface` uses covered
6
# - `@interface f(t::T)`
7
# - `@interface f(t::T1, ::T2)`
8
# - `@interface f(t::Type{<:T})`
9
# - `@interface f(t::T1, ::Type{<:T2})`
10
# - `@interface function f(t::T1) end`
11

12
"""
13
    gatherwheres(ex) 
14
Separate the function signature from the `where` statement and return tuple of `(function_call_signature, where_parameters_tuple)`
15

16
Taken from [`MacroTools.jl`](@extref)
17
"""
18
function gatherwheres(ex)
78✔
19
    if @capture(ex, (f_ where {params1__}))
90✔
20
        f2, params2 = gatherwheres(f)
12✔
21
        (f2, (params1..., params2...))
12✔
22
    else
23
        (ex, ())
66✔
24
    end
25
end
26

27
"""
28
    UnimplementedInterface <: Exception 
29
Thrown when a subtype does not implement an obligatory interface.
30
"""
31
struct UnimplementedInterface{T} <: Exception
32
    t::T
60✔
33
    function_dict::Dict{Symbol,Any}
34
end
35
function Base.showerror(io::IO, err::UnimplementedInterface{T}) where {T}
×
36
    kwargs, name, args, whereparams, rtype = err.function_dict[:kwargs],
×
37
    err.function_dict[:name], err.function_dict[:args], err.function_dict[:whereparams],
38
    err.function_dict[:rtype]
39
    return print(
×
40
        io,
41
        "UnimplementedInterface{$(nameof(T))}: `$(nameof(typeof(err.t)))` does not implement the obligatory interface `$name($(join(args, ", "))$(length(kwargs) > 0 ? "; " : "")$(join(kwargs, ", ")))$(rtype !== nothing ? "::$rtype" : "")$(length(whereparams) > 0 ? " where {$(join(whereparams, ", "))}" : "")`",
42
    )
43
end
44

45
"""
46
    @interface fn_expr
47
Define an interface function for the abstract type in the first argument of the function signature in `fn_expr`.
48

49
It is common to define empty functions for interfaces
50
```julia
51
function fn(a::A, b::B) end
52
```
53
This macro creates the function and adds an informative error message to the user so that he knows what went wrong and how to fix it.
54
```julia
55
@inteface fn(a::A, b::B)
56
```
57
roughly translates to
58
```julia
59
function fn(a::A, b::B)
60
    throw(UnimplementedInterface{A}(#=signature=#))
61
end
62
```
63

64
An interface on an abstract type is a way to indicate that every subtype has to provide a custom method for this
65
function. Interfaces are useful for code re-usability in higher order methods. Higher order methods of abstract types
66
can use the interfaces defined on these types to provide implementations for their subtypes.
67
"""
68
macro interface(ex)
66✔
69
    func = nothing
66✔
70
    fdict = Dict{Symbol,Any}(
66✔
71
        (t => nothing for t in (:name, :args, :rtype, :whereparams, :body))...
72
    )
73
    fdict[:kwargs] = []
66✔
74
    fcall = if isexpr(ex, :function)
66✔
75
        @capture(longdef(ex), function (fcall_ | fcall_)
6✔
76
            return body_
77
        end)
78
        fcall
6✔
79
    else
80
        ex
126✔
81
    end
82
    fcall_nowhere, fdict[:whereparams] = gatherwheres(fcall)
66✔
83

84
    # Note: Taken from `MacroTools.jl`
85
    if @capture(
126✔
86
        fcall_nowhere,
87
        (
88
            (func_(args__; kwargs__)) |
89
            (func_(args__; kwargs__)::rtype_) |
90
            (func_(args__)) |
91
            (func_(args__)::rtype_)
92
        )
93
    )
20✔
94
    elseif isexpr(fcall_nowhere, :tuple)
6✔
95
        if length(fcall_nowhere.args) > 0 && isexpr(fcall_nowhere.args[1], :parameters)
×
96
            # Handle both cases: parameters with args and parameters only
97
            if length(fcall_nowhere.args) > 1
×
98
                fdict[:args] = fcall_nowhere.args[2:end]
×
99
            else
100
                fdict[:args] = []
×
101
            end
102
            fdict[:kwargs] = fcall_nowhere.args[1].args
×
103
        else
104
            fdict[:args] = fcall_nowhere.args
×
105
        end
106
    elseif isexpr(fcall_nowhere, :(::))
6✔
107
        fdict[:args] = Any[fcall_nowhere]
×
108
    else
109
        throw(
6✔
110
            ArgumentError(
111
                lazy"`@interface` may only be used on functions or `:call` expressions. Got $ex",
112
            ),
113
        )
114
    end
115

116
    if func !== nothing
60✔
117
        @capture(func, (fname_{params__} | fname_))
120✔
118
        fdict[:name] = fname
60✔
119
    end
120
    if kwargs !== nothing
60✔
121
        fdict[:kwargs] = kwargs
12✔
122
    end
123
    if args !== nothing
60✔
124
        fdict[:args] = args
60✔
125
    end
126
    if rtype !== nothing
60✔
127
        fdict[:rtype] = rtype
6✔
128
    end
129
    if params !== nothing
60✔
130
        fdict[:params] = params
×
131
    end
132

133
    length(fdict[:args]) > 0 || throw(
66✔
134
        ArgumentError(lazy"In `@interface`, an interface must have atleast one argument."),
135
    )
136

137
    isexpr(first(fdict[:args]), :(::)) || throw(
60✔
138
        ArgumentError(
139
            lazy"In `@interface`, the first argument `$(first(fdict[:args]))` of the interface function must have a known type otherwise the interfacing type is unknown.",
140
        ),
141
    )
142
    @capture(first(fdict[:args]), (t_::T_ | ::T_))
96✔
143
    isabstracttype(@eval(@__MODULE__, $T)) || throw(
54✔
144
        ArgumentError(lazy"In `@interface`, the interfacing type `$T` must be abstract."),
145
    )
146
    if t === nothing
42✔
147
        t = Symbol(lowercase(first(string(T))))
×
148
        fdict[:args][1] = :($t::$T)
×
149
    end
150

151
    fdict[:body] = :(return throw($UnimplementedInterface{$T}($(t), $(fdict))))
42✔
152

153
    return esc(combinedef(fdict))
42✔
154
end
155

156
export @interface
157

158
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc