Skip to content

Commit

Permalink
Merge pull request #969 from shamanas/extend_generic
Browse files Browse the repository at this point in the history
Rock can now extend generic classes
  • Loading branch information
alexnask committed Apr 25, 2016
2 parents 14b586e + ad079bd commit 43bc454
Show file tree
Hide file tree
Showing 12 changed files with 356 additions and 31 deletions.
253 changes: 237 additions & 16 deletions source/rock/middle/Addon.ooc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import structs/[ArrayList, HashMap, MultiMap]
import Node, Type, TypeDecl, FunctionDecl, FunctionCall, Visitor, VariableAccess, PropertyDecl, ClassDecl, CoverDecl
import structs/[List, ArrayList, HashMap, MultiMap]
import Node, Type, TypeDecl, FunctionDecl, FunctionCall, Visitor, VariableAccess, PropertyDecl, ClassDecl, CoverDecl, BaseType, VariableDecl
import tinker/[Trail, Resolver, Response, Errors]
import ../frontend/Token

/**
* An addon is a collection of methods added to a type via the 'extend'
Expand All @@ -24,10 +25,22 @@ Addon: class extends Node {

base: TypeDecl { get set }

// Map of name -> VariableDecl of our typeArgs to our classe's
// For example, if we 'extend Foo <K>' and Foo was defined as a class <T>
// we will have a "K" -> T: Class mapping
typeArgMapping: HashMap<String, VariableDecl>

// Illegal generics are the symbols that appear in the generics of our ref
// AND do not appear in the generics of our baseType.
illegalGenerics: ArrayList<String>

functions := MultiMap<String, FunctionDecl> new()

properties := HashMap<String, PropertyDecl> new()

_stoppedAt := -1
dummy: VariableDecl

init: func (=baseType, .token) {
super(token)
}
Expand Down Expand Up @@ -91,23 +104,131 @@ Addon: class extends Node {
}

resolve: func (trail: Trail, res: Resolver) -> Response {
retry? := _stoppedAt != -1
if(base == null || retry?) {
// So, if we have typeArgs, we need to make sure that the generic ones are not actual types
// because we cannot extend a realized generic type but we must rather extend the whole type.
typeArgs? := baseType getTypeArgs() != null && !baseType getTypeArgs() empty?()

// To get our base type's ref while using undefined typeArg types, we will point those to a
// dummy declaration.
// At the moment, we let the rest, that have a ref be, until we can get our base ref and do some checking.
if (!dummy) dummy = VariableDecl new(null, "dummy", token)

if (typeArgs? && !retry?) {
for (typeArg in baseType getTypeArgs()) {
typeArg resolve(trail, res)

if (!typeArg getRef()) {
typeArg setRef(dummy)
}
}
}

if(base == null) {
baseType resolve(trail, res)
if(baseType isResolved()) {

if (baseType isResolved()) {
base = baseType getRef() as TypeDecl
checkRedefinitions(trail, res)
base addons add(this)

for(fDecl in functions) {
if (!retry?) {
checkRedefinitions(trail, res)
base addons add(this)
}

if (typeArgs?) {
// We will now check that all our generic typeArgs point to the dummy.
// In addition to that, we build the typeArg mapping as described on its decl.

// Only go through generics, not templates
genSize := base typeArgs size

if (!retry?) {
typeArgMapping = HashMap<String, VariableDecl> new()

// Start off by making all the ref generics illegal, remove as we go
illegalGenerics = base typeArgs map(|ta|
ta name
)
}

for ((i, typeArg) in baseType getTypeArgs()) {
if (i >= genSize) {
break
}

if (i < _stoppedAt) {
continue
}

// Matched against a declaration, not good
if (typeArg getRef() != dummy) {
res throwError(ExtendRealizedGeneric new(baseType, typeArg, token))
return Response OK
}

// We "link" the type ourselves, to our base's generics.
// Here, we go up the base class's hierarchy and fidn where the generic declaration originally came from.

superRef := base
genDecl := base typeArgs get(i)

while (superRef) {
superType := superRef getSuperType()
superRef = superRef getSuperRef()
if (superRef && superRef isMeta) {
superRef = superRef getNonMeta()
}

if (!superType || !superType isResolved() || !superRef) {
_stoppedAt = i
res wholeAgain(this, "Need all super refs and super types of addon base")
return Response OK
}

if (superRef isObjectClass()) {
break
}

// Extract the next index from the supertype
found? := false

if (superType) {
targs := superType getTypeArgs()
if (targs) for ((j, ta) in targs) {
if (ta getName() == genDecl name) {
found? = true
genDecl = superRef typeArgs get(j)
}
}
}

if (!found?) {
break
}
}


_stoppedAt = i + 1
typeArg setRef(genDecl)
typeArgMapping put(typeArg getName(), genDecl)

index := illegalGenerics indexOf(typeArg getName())
if (index != -1) {
illegalGenerics removeAt(index)
}

}
}

for (fDecl in functions) {
if(fDecl name == "init" && (base instanceOf?(ClassDecl) || base instanceOf?(CoverDecl))) {
if(base instanceOf?(ClassDecl)) base as ClassDecl addInit(fDecl)
else base getMeta() addInit(fDecl)
}
fDecl setOwner(base)
}

for(prop in properties) {
for (prop in properties) {
old := base getVariable(prop name)
if(old) token module params errorHandler onError(DuplicateField new(old, prop))
prop owner = base
Expand All @@ -123,29 +244,34 @@ Addon: class extends Node {
}

finalResponse := Response OK
trail push(base getMeta())
for(f in functions) {

trail push(this)

for (f in functions) {
response := f resolve(trail, res)
if(!response ok()) {
finalResponse = response
}
}
for(p in properties) {

for (p in properties) {
response := p resolve(trail, res)
if(!response ok()) {
finalResponse = response
} else {
// all functions of an addon are final, because we *definitely* don't have a 'class' field
if(p getter) p getter isFinal = true
if(p setter) p setter isFinal = true
if (p getter) p getter isFinal = true
if (p setter) p setter isFinal = true
}
}
trail pop(base getMeta())

trail pop(this)

return finalResponse
}

resolveCall: func (call : FunctionCall, res: Resolver, trail: Trail) -> Int {
// These resolve methods are called back from TypeDecl.
resolveCallFromClass: func (call : FunctionCall, res: Resolver, trail: Trail) -> Int {
if(base == null) return 0

functions getEach(call name, |fDecl|
Expand All @@ -165,7 +291,7 @@ Addon: class extends Node {
return 0
}

resolveAccess: func (access: VariableAccess, res: Resolver, trail: Trail) -> Int {
resolveAccessFromClass: func (access: VariableAccess, res: Resolver, trail: Trail) -> Int {
if(base == null) return 0

vDecl := properties[access name]
Expand All @@ -182,6 +308,85 @@ Addon: class extends Node {
0
}

_checkInIllegal: func (name: String, tok: Token, res: Resolver) -> Bool {
if (illegalGenerics && illegalGenerics contains?(name)) {
// If we got up the trail so match, we didn't get any match sooner, so we error out immediately.
ours: String = "<unknown>"
typeArgMapping each(|src, dist|
if (dist name == name) {
ours = src
}
)

res throwError(IllegalGenericAccess new(name, ours, tok))
return true
}

false
}

// These resolve functions are here to intercept attempts to use illegal generics or
// generics we have a mapping for.
// Then, they give up and let the base do its thing.

resolveAccess: func (access: VariableAccess, res: Resolver, trail: Trail) -> Int {
// This is true if we are accessing a field, false otherwise
thisAccess? := access expr == null || match (access expr) {
case va: VariableAccess =>
va name == "this" || va name == "This"
case =>
false
}

if (thisAccess? && _checkInIllegal(access name, access token, res)) {
return -1
}

if (thisAccess? && typeArgMapping && typeArgMapping contains?(access name)) {
if (access suggest(typeArgMapping[access name])) {
// This is a pretty bad hack but by setting the expr to 'this' we force it to be seen as a member
access expr = VariableAccess new("this", access token)
return 0
}
}

if (base) {
return base resolveAccess(access, res, trail)
}

0
}

resolveType: func (type: BaseType, res: Resolver, trail: Trail) -> Int {
if (_checkInIllegal(type name, type token, res)) {
return -1
}

if (typeArgMapping && typeArgMapping contains?(type name)) {
vDecl := typeArgMapping[type name]
if (type suggest(vDecl)) {
// We replace our name so that rock's generic inference can do its thing.
// This is very much a hack, all of rock's typeArg systems very much need a rewrite
type name = vDecl name
return 0
}
}

if (base) {
return base resolveType(type, res, trail)
}

0
}

resolveCall: func (call: FunctionCall, res: Resolver, trail: Trail) -> Int {
if (base) {
return base getMeta() resolveCall(call, res, trail)
}

0
}

toString: func -> String {
"Addon of %s in module %s" format(baseType toString(), token module getFullName())
}
Expand All @@ -191,3 +396,19 @@ Addon: class extends Node {
ExtendFieldDefinition: class extends Error {
init: super func ~tokenMessage
}

ExtendRealizedGeneric: class extends Error {
baseType, realizedType: Type

init: func (=baseType, =realizedType, .token) {
super(token, "Trying to extend type #{baseType} with realized generic #{realizedType}, which is unsupported.")
}
}

IllegalGenericAccess: class extends Error {
illegalGeneric, ourGeneric: String

init: func (=illegalGeneric, =ourGeneric, .token) {
super(token, "Trying to use generic argument '#{illegalGeneric}', which is defined in base declaration but not the addon. You should use '#{ourGeneric}' instead.")
}
}
2 changes: 1 addition & 1 deletion source/rock/middle/PropertyDecl.ooc
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ PropertyDecl: class extends VariableDecl {
res wholeAgain(this, "need addon's base type")
return Response OK
}
node = ad base
node = ad base getMeta()
case td: TypeDecl =>
// Everything ok
case =>
Expand Down
4 changes: 2 additions & 2 deletions source/rock/middle/TypeDecl.ooc
Original file line number Diff line number Diff line change
Expand Up @@ -1279,7 +1279,7 @@ TypeDecl: abstract class extends Declaration {
}

if (has) {
if (addon resolveCall(call, res, trail) == -1) return -1
if (addon resolveCallFromClass(call, res, trail) == -1) return -1
}

0
Expand All @@ -1299,7 +1299,7 @@ TypeDecl: abstract class extends Declaration {
}

if (has) {
if (addon resolveAccess(access, res, trail) == -1) return -1
if (addon resolveAccessFromClass(access, res, trail) == -1) return -1
}

0
Expand Down
25 changes: 24 additions & 1 deletion source/rock/middle/VariableAccess.ooc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import BinaryOp, Visitor, Expression, VariableDecl, FunctionDecl,
TypeDecl, Declaration, Type, Node, ClassDecl, NamespaceDecl,
EnumDecl, PropertyDecl, FunctionCall, Module, Import, FuncType,
NullLiteral, AddressOf, BaseType, StructLiteral, Return,
Argument, Scope, CoverDecl, StringLiteral, Cast
Argument, Scope, CoverDecl, StringLiteral, Cast, Addon

import tinker/[Resolver, Response, Trail, Errors]
import structs/ArrayList
Expand Down Expand Up @@ -372,6 +372,29 @@ VariableAccess: class extends Expression {
return BranchResult BREAK
}

// If we still don't have a ref, we're going to try to go up the trail and find an Addon.
if (!ref) {
depth := trail getSize() - 1
while (depth >= 0) {
node := trail get(depth)

match node {
case addon: Addon =>
status := node resolveAccess(this, res, trail)

if (status == -1) {
res wholeAgain(this, "asked to wait while resolving access")
return BranchResult BREAK
}

if (ref) {
break
}
}

depth -= 1
}
}
} else {
/*
* Try resolving as a builtin, e.g. __BUILD_DATE__, etc.
Expand Down
Loading

0 comments on commit 43bc454

Please sign in to comment.