Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ branches:
- /v(\d+)\.(\d+)\.(\d+)/
matrix:
allow_failures:
- julia: nightly
- julia: 1.0
codecov: true
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ of methods while enabling shared structure without duplicative code.
A "class" is a concrete type with a defined relationship to a hierarchy of automatically
generated abstract types. The `@class` macro saves the field definitions for each class
so that subclasses receive all their parent's fields in addition to those defined locally.
Inner constructors are passed through unchanged.
Inner constructors are passed through unchanged. `@class` supports parametric classes (similar to parametric structs) and also super class constructor inheritance when the subclass does not have additional fields.

`Classes.jl` constructs a "shadow" abstract type hierarchy to represent the relationships among
the defined classes. For each class `Foo`, the abstract type `AbstractFoo` is defined, where `AbstractFoo`
Expand Down Expand Up @@ -133,4 +133,26 @@ For example, give the class `Bar`, you can write a function that applies to
my_method(obj::AbstractBar, other, stuff) = do_something(obj, other, args)
```

## Super constructor inheritance
When a subclass does not have any additional fields you can call its super constructors:
```julia
## super constructor inheritance
@class Animal begin
x
Animal(x, y) = new(x)
end

function Animal(x, y, z)
return Animal(x+y+z)
end

@class Dog <: Animal begin
end

Dog(1,2) # 1
Dog(1,2,3) # 6
```



See the online [documentation](https://github.com/rjplevin/Classes.jl/blob/master/docs/src/index.md) for further details.
64 changes: 64 additions & 0 deletions src/Classes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,62 @@ function _initializer(class, fields, wheres)
return funcdef
end

function super_constructor_inheritance(clsname, super, super_fields, has_params, params)
guide_constructor = Expr[]
super_fields_len = length(super_fields)
# new{T...} is not supported on Julia 1.0.5
if has_params
function_return = :(new{(getfield(typeof(super_inctance), :parameters)...)})
else
function_return = :(new)
end
# paramsless constructor
if super_fields_len != 0
argnames = _argnames(super_fields)
paramsless_guide_constructor = quote
function $clsname(arguments...)
super_inctance = $super(arguments...) # super's extra constructor
# setting subclass fields
subcls_fields = getfield.(Ref(super_inctance), $argnames)
return $function_return(subcls_fields...)
end
end
else
# call the super constructor that may do some stuff
paramsless_guide_constructor = quote
function $clsname(arguments...)
super_inctance = $super(arguments...) # does something
return $function_return()
end
end
end
push!(guide_constructor, paramsless_guide_constructor)
# paramsful constructor
if has_params
if super_fields_len != 0
argnames = _argnames(super_fields)
paramsful_guide_constructor = quote
function $clsname{$(params...)}(arguments...) where {$(params...)}
super_inctance = $super{$(params...)}(arguments...) # super's extra constructor
# setting subclass fields
subcls_fields = getfield.(Ref(super_inctance), $argnames)
return $function_return(subcls_fields...)
end
end
else
# call the super constructor that may do some stuff
paramsful_guide_constructor = quote
function $clsname{$(params...)}(arguments...) where {$(params...)}
super_inctance = $super{$(params...)}(arguments...) # does something
return $function_return()
end
end
end
push!(guide_constructor, paramsful_guide_constructor)
end
return guide_constructor
end

function _constructors(clsname, super, super_info, local_fields, all_fields, wheres)
all_wheres = [super_info.wheres; wheres]
init_all = _initializer(clsname, all_fields, all_wheres)
Expand Down Expand Up @@ -242,6 +298,14 @@ function _constructors(clsname, super, super_info, local_fields, all_fields, whe
push!(methods, immut_init)
end

# super constructor inheritance: when the class and superclass have the same fields,
# the constructors of the superclass are valid for the subclass,
# so we make an outter constructor for subclass pointing to those.
if super !== Class && length(local_fields) === 0
guide_constructor = super_constructor_inheritance(clsname, super, super_fields, has_params, params)
push!(methods, guide_constructor...)
end

return methods, inits
end

Expand Down
78 changes: 78 additions & 0 deletions test/test_classes.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Test
using Classes
using Suppressor

@test superclass(Class) === nothing

Expand Down Expand Up @@ -175,3 +176,80 @@ end

@test Cat(1).x == 1
@test Cat("a").x == "a"

## super constructor inheritance
@class Animal begin
x
Animal(x, y) = new(x)
end

function Animal(x, y, z)
return Animal(x+y+z)
end

@class Dog <: Animal begin
end

@test Dog(1,2).x == 1
@test Dog(1,2,3).x == 6


# fieldless class
@class Animal2 begin
end

test_num = 1
function Animal2(x, y)
println("Animal is instantiated")
return Animal2()
end

@class Dog2 <: Animal2 begin
end

@test @capture_out( Dog2(1,2) ) == "Animal is instantiated\n"

# super constructor inheritance with parameters
@class Animal3{T} begin
x::T
Animal3(x, y) = new{typeof(x)}(x)
Animal3{T}(x, y) where {T} = new{T}(x)
end

function Animal3(x, y, z)
return Animal3(x*y, z)
end

function Animal3{T}(x, y, z) where {T}
return Animal3{T}(x*y, z)
end

@class Dog3 <: Animal3 begin
end

@test Dog3(1,2).x == 1
@test Dog3{Int64}(1,2).x == 1
@test Dog3(1,2,3).x == 2
@test Dog3{Int64}(1,2,3).x == 2

# fieldless class with parameters
@class Animal4{T} begin
Animal4(x, y) = new{typeof(x)}()
Animal4{T}(x, y) where {T} = new{T}()
end

function Animal4(x, y, z)
return Animal4(x*y, z)
end

function Animal4{T}(x, y, z) where {T}
return Animal4{T}(x*y, z)
end

@class Dog4 <: Animal4 begin
end

@test typeof(Dog4(1,2)) == Dog4{Int64}
@test typeof(Dog4{Float64}(1,2)) == Dog4{Float64}
@test typeof(Dog4(1,2,3)) == Dog4{Int64}
@test typeof(Dog4{Int32}(1,2,3)) == Dog4{Int32}