first steps for 'not nil' annotation

This commit is contained in:
Araq 2012-11-25 11:03:14 +01:00
commit ccd2934e4a
7 changed files with 127 additions and 69 deletions

View file

@ -715,6 +715,8 @@ const
tyFloat..tyFloat128, tyUInt..tyUInt64} tyFloat..tyFloat128, tyUInt..tyUInt64}
ConstantDataTypes*: TTypeKinds = {tyArrayConstr, tyArray, tySet, ConstantDataTypes*: TTypeKinds = {tyArrayConstr, tyArray, tySet,
tyTuple, tySequence} tyTuple, tySequence}
NilableTypes*: TTypeKinds = {tyPointer, tyCString, tyRef, tyPtr, tySequence,
tyProc, tyString, tyError}
ExportableSymKinds* = {skVar, skConst, skProc, skMethod, skType, skIterator, ExportableSymKinds* = {skVar, skConst, skProc, skMethod, skType, skIterator,
skMacro, skTemplate, skConverter, skEnumField, skLet, skStub} skMacro, skTemplate, skConverter, skEnumField, skLet, skStub}
PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16, nfAllConst} PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16, nfAllConst}

View file

@ -183,14 +183,15 @@ proc getPrecedence(tok: TToken): int =
of '?': result = 2 of '?': result = 2
else: considerAsgn(2) else: considerAsgn(2)
of tkDiv, tkMod, tkShl, tkShr: result = 9 of tkDiv, tkMod, tkShl, tkShr: result = 9
of tkIn, tkNotIn, tkIs, tkIsNot, tkNot, tkOf, tkAs: result = 5 of tkIn, tkNotIn, tkIs, tkIsNot, tkOf, tkAs: result = 5
of tkDotDot: result = 6 of tkDotDot: result = 6
of tkAnd: result = 4 of tkAnd: result = 4
of tkOr, tkXor: result = 3 of tkOr, tkXor: result = 3
of tkNot: result = -2
else: result = - 10 else: result = - 10
proc isOperator(tok: TToken): bool = proc isOperator(tok: TToken): bool =
result = getPrecedence(tok) >= 0 result = getPrecedence(tok) >= -2
proc parseSymbol(p: var TParser): PNode = proc parseSymbol(p: var TParser): PNode =
case p.tok.tokType case p.tok.tokType
@ -509,10 +510,13 @@ proc primarySuffix(p: var TParser, r: PNode): PNode =
result = indexExprList(p, result, nkCurlyExpr, tkCurlyRi) result = indexExprList(p, result, nkCurlyExpr, tkCurlyRi)
else: break else: break
proc primary(p: var TParser, skipSuffix = false): PNode type
TPrimaryMode = enum pmNormal, pmIsType, pmSkipSuffix
proc lowestExprAux(p: var TParser, limit: int): PNode = proc primary(p: var TParser, mode: TPrimaryMode): PNode
result = primary(p)
proc lowestExprAux(p: var TParser, limit: int, mode: TPrimaryMode): PNode =
result = primary(p, mode)
# expand while operators have priorities higher than 'limit' # expand while operators have priorities higher than 'limit'
var opPrec = getPrecedence(p.tok) var opPrec = getPrecedence(p.tok)
while opPrec >= limit: while opPrec >= limit:
@ -522,15 +526,15 @@ proc lowestExprAux(p: var TParser, limit: int): PNode =
getTok(p) getTok(p)
optInd(p, opNode) optInd(p, opNode)
# read sub-expression with higher priority: # read sub-expression with higher priority:
var b = lowestExprAux(p, opPrec + leftAssoc) var b = lowestExprAux(p, opPrec + leftAssoc, mode)
addSon(a, opNode) addSon(a, opNode)
addSon(a, result) addSon(a, result)
addSon(a, b) addSon(a, b)
result = a result = a
opPrec = getPrecedence(p.tok) opPrec = getPrecedence(p.tok)
proc lowestExpr(p: var TParser): PNode = proc lowestExpr(p: var TParser, mode = pmNormal): PNode =
result = lowestExprAux(p, -1) result = lowestExprAux(p, -1, mode)
proc parseIfExpr(p: var TParser, kind: TNodeKind): PNode = proc parseIfExpr(p: var TParser, kind: TNodeKind): PNode =
result = newNodeP(kind, p) result = newNodeP(kind, p)
@ -738,12 +742,20 @@ proc isExprStart(p: TParser): bool =
result = true result = true
else: result = false else: result = false
when false:
proc parseTypeDescNoSuffix(p: var TParser): PNode =
if p.tok.toktype == tkProc: result = parseProcExpr(p, false)
elif p.tok.toktype == tkIterator:
result = parseProcExpr(p, false)
result.kind = nkIteratorTy
else: result = primary(p)
proc parseTypeDescKAux(p: var TParser, kind: TNodeKind): PNode = proc parseTypeDescKAux(p: var TParser, kind: TNodeKind): PNode =
result = newNodeP(kind, p) result = newNodeP(kind, p)
getTok(p) getTok(p)
optInd(p, result) optInd(p, result)
if not isOperator(p.tok) and isExprStart(p): if not isOperator(p.tok) and isExprStart(p):
addSon(result, parseTypeDesc(p)) addSon(result, primary(p, pmIsType))
proc parseExpr(p: var TParser): PNode = proc parseExpr(p: var TParser): PNode =
# #
@ -759,7 +771,7 @@ proc parseExpr(p: var TParser): PNode =
# XXX needs proper support: # XXX needs proper support:
#of tkTry: result = parseTry(p) #of tkTry: result = parseTry(p)
proc primary(p: var TParser, skipSuffix = false): PNode = proc primary(p: var TParser, mode: TPrimaryMode): PNode =
# prefix operator? # prefix operator?
if isOperator(p.tok): if isOperator(p.tok):
let isSigil = IsSigilLike(p.tok) let isSigil = IsSigilLike(p.tok)
@ -770,10 +782,10 @@ proc primary(p: var TParser, skipSuffix = false): PNode =
optInd(p, a) optInd(p, a)
if isSigil: if isSigil:
#XXX prefix operators #XXX prefix operators
addSon(result, primary(p, true)) addSon(result, primary(p, pmSkipSuffix))
result = primarySuffix(p, result) result = primarySuffix(p, result)
else: else:
addSon(result, primary(p)) addSon(result, primary(p, pmNormal))
return return
case p.tok.tokType: case p.tok.tokType:
@ -782,7 +794,16 @@ proc primary(p: var TParser, skipSuffix = false): PNode =
of tkPtr: result = parseTypeDescKAux(p, nkPtrTy) of tkPtr: result = parseTypeDescKAux(p, nkPtrTy)
of tkType: result = parseTypeDescKAux(p, nkTypeOfExpr) of tkType: result = parseTypeDescKAux(p, nkTypeOfExpr)
of tkTuple: result = parseTuple(p) of tkTuple: result = parseTuple(p)
of tkProc: result = parseProcExpr(p, true) of tkProc: result = parseProcExpr(p, mode != pmIsType)
of tkIterator:
if mode == pmIsType:
result = parseProcExpr(p, false)
result.kind = nkIteratorTy
else:
# no anon iterators for now:
parMessage(p, errExprExpected, p.tok)
getTok(p) # we must consume a token here to prevend endless loops!
result = ast.emptyNode
of tkEnum: of tkEnum:
result = newNodeP(nkEnumTy, p) result = newNodeP(nkEnumTy, p)
getTok(p) getTok(p)
@ -795,27 +816,35 @@ proc primary(p: var TParser, skipSuffix = false): PNode =
of tkAddr: of tkAddr:
result = newNodeP(nkAddr, p) result = newNodeP(nkAddr, p)
getTok(p) getTok(p)
addSon(result, primary(p)) addSon(result, primary(p, pmNormal))
of tkStatic: of tkStatic:
result = newNodeP(nkStaticExpr, p) result = newNodeP(nkStaticExpr, p)
getTok(p) getTok(p)
addSon(result, primary(p)) addSon(result, primary(p, pmNormal))
of tkBind: of tkBind:
result = newNodeP(nkBind, p) result = newNodeP(nkBind, p)
getTok(p) getTok(p)
optInd(p, result) optInd(p, result)
addSon(result, primary(p)) addSon(result, primary(p, pmNormal))
else: else:
result = identOrLiteral(p) result = identOrLiteral(p)
if not skipSuffix: if mode != pmSkipSuffix:
result = primarySuffix(p, result) result = primarySuffix(p, result)
proc parseTypeDesc(p: var TParser): PNode = proc parseTypeDesc(p: var TParser): PNode =
if p.tok.toktype == tkProc: result = parseProcExpr(p, false) result = lowestExpr(p, pmIsType)
elif p.tok.toktype == tkIterator: when false:
result = parseProcExpr(p, false) result = parseTypeDescNoSuffix(p)
result.kind = nkIteratorTy # optional 'not nil' suffix?
else: result = parseExpr(p) if p.tok.tokType == tkNot:
# we don't call 'getTok' here, so 'parseExpr' includes the 'not' in the
# expression; this will yield an nkPrefix AST ;-)
let ex = parseExpr(p)
let a = newNodeI(nkInfix, result.info, 3)
a.sons[0] = ex.sons[0]
a.sons[1] = result
a.sons[2] = ex.sons[1]
result = a
proc parseExprStmt(p: var TParser): PNode = proc parseExprStmt(p: var TParser): PNode =
var a = lowestExpr(p) var a = lowestExpr(p)

View file

@ -799,16 +799,18 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
of nkPar: of nkPar:
if sonsLen(n) == 1: result = semTypeNode(c, n.sons[0], prev) if sonsLen(n) == 1: result = semTypeNode(c, n.sons[0], prev)
else: else:
# XXX support anon tuple here
LocalError(n.info, errTypeExpected) LocalError(n.info, errTypeExpected)
result = newOrPrevType(tyError, prev, c) result = newOrPrevType(tyError, prev, c)
of nkCallKinds: of nkCallKinds:
if n[0].kind == nkIdent: if n[0].kind == nkIdent:
let op = n.sons[0].ident let op = n.sons[0].ident
if op.id in {ord(wAnd), ord(wOr)} or op.s == "|": if op.id in {ord(wAnd), ord(wOr)} or op.s == "|":
checkSonsLen(n, 3)
var var
t1 = semTypeNode(c, n.sons[1], nil) t1 = semTypeNode(c, n.sons[1], nil)
t2 = semTypeNode(c, n.sons[2], nil) t2 = semTypeNode(c, n.sons[2], nil)
if t1 == nil: if t1 == nil:
LocalError(n.sons[1].info, errTypeExpected) LocalError(n.sons[1].info, errTypeExpected)
result = newOrPrevType(tyError, prev, c) result = newOrPrevType(tyError, prev, c)
elif t2 == nil: elif t2 == nil:
@ -819,6 +821,13 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
result.addSonSkipIntLit(t1) result.addSonSkipIntLit(t1)
result.addSonSkipIntLit(t2) result.addSonSkipIntLit(t2)
result.flags.incl(if op.id == ord(wAnd): tfAll else: tfAny) result.flags.incl(if op.id == ord(wAnd): tfAll else: tfAny)
elif op.id == ord(wNot):
checkSonsLen(n, 3)
result = semTypeNode(c, n.sons[1], prev)
if result.kind in NilableTypes and n.sons[2].kind == nkNilLit:
result.flags.incl(tfNotNil)
else:
LocalError(n.info, errGenerated, "invalid type")
else: else:
result = semTypeExpr(c, n) result = semTypeExpr(c, n)
else: else:
@ -883,25 +892,33 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
of nkVarTy: result = semVarType(c, n, prev) of nkVarTy: result = semVarType(c, n, prev)
of nkDistinctTy: result = semDistinct(c, n, prev) of nkDistinctTy: result = semDistinct(c, n, prev)
of nkProcTy, nkIteratorTy: of nkProcTy, nkIteratorTy:
if n.sonsLen == 0: return newConstraint(c, tyProc) if n.sonsLen == 0:
checkSonsLen(n, 2) result = newConstraint(c, tyProc)
openScope(c.tab)
result = semProcTypeNode(c, n.sons[0], nil, prev, skProc)
# dummy symbol for `pragma`:
var s = newSymS(skProc, newIdentNode(getIdent("dummy"), n.info), c)
s.typ = result
if n.sons[1].kind == nkEmpty or n.sons[1].len == 0:
if result.callConv == ccDefault:
result.callConv = ccClosure
#Message(n.info, warnImplicitClosure, renderTree(n))
else: else:
pragma(c, s, n.sons[1], procTypePragmas) checkSonsLen(n, 2)
when useEffectSystem: SetEffectsForProcType(result, n.sons[1]) openScope(c.tab)
closeScope(c.tab) result = semProcTypeNode(c, n.sons[0], nil, prev, skProc)
# dummy symbol for `pragma`:
var s = newSymS(skProc, newIdentNode(getIdent("dummy"), n.info), c)
s.typ = result
if n.sons[1].kind == nkEmpty or n.sons[1].len == 0:
if result.callConv == ccDefault:
result.callConv = ccClosure
#Message(n.info, warnImplicitClosure, renderTree(n))
else:
pragma(c, s, n.sons[1], procTypePragmas)
when useEffectSystem: SetEffectsForProcType(result, n.sons[1])
closeScope(c.tab)
if n.kind == nkIteratorTy:
result.flags.incl(tfIterator)
of nkEnumTy: result = semEnum(c, n, prev) of nkEnumTy: result = semEnum(c, n, prev)
of nkType: result = n.typ of nkType: result = n.typ
of nkStmtListType: result = semStmtListType(c, n, prev) of nkStmtListType: result = semStmtListType(c, n, prev)
of nkBlockType: result = semBlockType(c, n, prev) of nkBlockType: result = semBlockType(c, n, prev)
of nkSharedTy:
checkSonsLen(n, 1)
result = semTypeNode(c, n.sons[0], prev)
result.flags.incl(tfShared)
else: else:
LocalError(n.info, errTypeExpected) LocalError(n.info, errTypeExpected)
result = newOrPrevType(tyError, prev, c) result = newOrPrevType(tyError, prev, c)

View file

@ -257,12 +257,14 @@ proc tupleRel(c: var TCandidate, f, a: PType): TTypeRelation =
var y = a.n.sons[i].sym var y = a.n.sons[i].sym
if x.name.id != y.name.id: return isNone if x.name.id != y.name.id: return isNone
proc allowsNil(f: PType): TTypeRelation {.inline.} =
result = if tfNotNil notin f.flags: isSubtype else: isNone
proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
proc inconsistentVarTypes(f, a: PType): bool {.inline.} = proc inconsistentVarTypes(f, a: PType): bool {.inline.} =
result = f.kind != a.kind and (f.kind == tyVar or a.kind == tyVar) result = f.kind != a.kind and (f.kind == tyVar or a.kind == tyVar)
case a.kind case a.kind
of tyNil: result = isSubtype
of tyProc: of tyProc:
if sonsLen(f) != sonsLen(a): return if sonsLen(f) != sonsLen(a): return
# Note: We have to do unification for the parameters before the # Note: We have to do unification for the parameters before the
@ -299,6 +301,7 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation =
return isNone return isNone
when useEffectSystem: when useEffectSystem:
if not compatibleEffects(f, a): return isNone if not compatibleEffects(f, a): return isNone
of tyNil: result = f.allowsNil
else: nil else: nil
proc matchTypeClass(c: var TCandidate, f, a: PType): TTypeRelation = proc matchTypeClass(c: var TCandidate, f, a: PType): TTypeRelation =
@ -388,16 +391,15 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
elif typeRel(c, base(f), a.sons[0]) >= isGeneric: elif typeRel(c, base(f), a.sons[0]) >= isGeneric:
result = isConvertible result = isConvertible
else: nil else: nil
of tySequence: of tySequence:
case a.Kind case a.Kind
of tyNil: of tySequence:
result = isSubtype if (f.sons[0].kind != tyGenericParam) and (a.sons[0].kind == tyEmpty):
of tySequence:
if (f.sons[0].kind != tyGenericParam) and (a.sons[0].kind == tyEmpty):
result = isSubtype result = isSubtype
else: else:
result = typeRel(c, f.sons[0], a.sons[0]) result = typeRel(c, f.sons[0], a.sons[0])
if result < isGeneric: result = isNone if result < isGeneric: result = isNone
of tyNil: result = f.allowsNil
else: nil else: nil
of tyOrdinal: of tyOrdinal:
if isOrdinalType(a): if isOrdinalType(a):
@ -432,21 +434,21 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
of tyPtr: of tyPtr:
result = typeRel(c, base(f), base(a)) result = typeRel(c, base(f), base(a))
if result <= isConvertible: result = isNone if result <= isConvertible: result = isNone
of tyNil: result = isSubtype of tyNil: result = f.allowsNil
else: nil else: nil
of tyRef: of tyRef:
case a.kind case a.kind
of tyRef: of tyRef:
result = typeRel(c, base(f), base(a)) result = typeRel(c, base(f), base(a))
if result <= isConvertible: result = isNone if result <= isConvertible: result = isNone
of tyNil: result = isSubtype of tyNil: result = f.allowsNil
else: nil else: nil
of tyProc: of tyProc:
result = procTypeRel(c, f, a) result = procTypeRel(c, f, a)
of tyPointer: of tyPointer:
case a.kind case a.kind
of tyPointer: result = isEqual of tyPointer: result = isEqual
of tyNil: result = isSubtype of tyNil: result = f.allowsNil
of tyProc: of tyProc:
if a.callConv != ccClosure: result = isConvertible if a.callConv != ccClosure: result = isConvertible
of tyPtr, tyCString: result = isConvertible of tyPtr, tyCString: result = isConvertible
@ -454,15 +456,15 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
of tyString: of tyString:
case a.kind case a.kind
of tyString: result = isEqual of tyString: result = isEqual
of tyNil: result = isSubtype of tyNil: result = f.allowsNil
else: nil else: nil
of tyCString: of tyCString:
# conversion from string to cstring is automatic: # conversion from string to cstring is automatic:
case a.Kind case a.Kind
of tyCString: result = isEqual of tyCString: result = isEqual
of tyNil: result = isSubtype of tyNil: result = f.allowsNil
of tyString: result = isConvertible of tyString: result = isConvertible
of tyPtr: of tyPtr:
if a.sons[0].kind == tyChar: result = isConvertible if a.sons[0].kind == tyChar: result = isConvertible
of tyArray: of tyArray:
if (firstOrd(a.sons[0]) == 0) and if (firstOrd(a.sons[0]) == 0) and
@ -706,8 +708,7 @@ proc ParamTypesMatch(c: PContext, m: var TCandidate, f, a: PType,
z.calleeSym = m.calleeSym z.calleeSym = m.calleeSym
var best = -1 var best = -1
for i in countup(0, sonsLen(arg) - 1): for i in countup(0, sonsLen(arg) - 1):
# iterators are not first class yet, so ignore them if arg.sons[i].sym.kind in {skProc, skIterator, skMethod, skConverter}:
if arg.sons[i].sym.kind in {skProc, skMethod, skConverter}:
copyCandidate(z, m) copyCandidate(z, m)
var r = typeRel(z, f, arg.sons[i].typ) var r = typeRel(z, f, arg.sons[i].typ)
if r != isNone: if r != isNone:

View file

@ -791,6 +791,9 @@ proc SameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
else: else:
if containsOrIncl(c, a, b): return true if containsOrIncl(c, a, b): return true
proc sameFlags(a, b: PType): bool {.inline.} =
result = eqTypeFlags*a.flags == eqTypeFlags*b.flags
if x == y: return true if x == y: return true
var a = skipTypes(x, {tyGenericInst}) var a = skipTypes(x, {tyGenericInst})
var b = skipTypes(y, {tyGenericInst}) var b = skipTypes(y, {tyGenericInst})
@ -809,36 +812,37 @@ proc SameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
case a.Kind case a.Kind
of tyEmpty, tyChar, tyBool, tyNil, tyPointer, tyString, tyCString, of tyEmpty, tyChar, tyBool, tyNil, tyPointer, tyString, tyCString,
tyInt..tyBigNum, tyStmt: tyInt..tyBigNum, tyStmt:
result = true result = sameFlags(a, b)
of tyExpr: of tyExpr:
result = ExprStructuralEquivalent(a.n, b.n) result = ExprStructuralEquivalent(a.n, b.n) and sameFlags(a, b)
of tyObject: of tyObject:
IfFastObjectTypeCheckFailed(a, b): IfFastObjectTypeCheckFailed(a, b):
CycleCheck() CycleCheck()
result = sameObjectStructures(a, b, c) result = sameObjectStructures(a, b, c) and sameFlags(a, b)
of tyDistinct: of tyDistinct:
CycleCheck() CycleCheck()
if c.cmp == dcEq: result = sameDistinctTypes(a, b) if c.cmp == dcEq: result = sameDistinctTypes(a, b) and sameFlags(a, b)
else: result = sameTypeAux(a.sons[0], b.sons[0], c) else: result = sameTypeAux(a.sons[0], b.sons[0], c) and sameFlags(a, b)
of tyEnum, tyForward, tyProxy: of tyEnum, tyForward, tyProxy:
# XXX generic enums do not make much sense, but require structural checking # XXX generic enums do not make much sense, but require structural checking
result = a.id == b.id result = a.id == b.id and sameFlags(a, b)
of tyTuple: of tyTuple:
CycleCheck() CycleCheck()
result = sameTuple(a, b, c) result = sameTuple(a, b, c) and sameFlags(a, b)
of tyGenericInst: result = sameTypeAux(lastSon(a), lastSon(b), c) of tyGenericInst:
result = sameTypeAux(lastSon(a), lastSon(b), c)
of tyTypeDesc: of tyTypeDesc:
if TypeDescExactMatch in c.flags: if TypeDescExactMatch in c.flags:
CycleCheck() CycleCheck()
result = sameChildrenAux(x, y, c) result = sameChildrenAux(x, y, c) and sameFlags(a, b)
else: else:
result = true result = sameFlags(a, b)
of tyGenericParam, tyGenericInvokation, tyGenericBody, tySequence, of tyGenericParam, tyGenericInvokation, tyGenericBody, tySequence,
tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyArrayConstr, tyOpenArray, tySet, tyRef, tyPtr, tyVar, tyArrayConstr,
tyArray, tyProc, tyConst, tyMutable, tyVarargs, tyIter, tyArray, tyProc, tyConst, tyMutable, tyVarargs, tyIter,
tyOrdinal, tyTypeClass: tyOrdinal, tyTypeClass:
CycleCheck() CycleCheck()
result = sameChildrenAux(a, b, c) result = sameChildrenAux(a, b, c) and sameFlags(a, b)
if result and (a.kind == tyProc): if result and (a.kind == tyProc):
result = a.callConv == b.callConv result = a.callConv == b.callConv
of tyRange: of tyRange:

View file

@ -78,9 +78,10 @@ exprList ::= [expr (comma expr)* [comma]]
qualifiedIdent ::= symbol ['.' symbol] qualifiedIdent ::= symbol ['.' symbol]
typeDesc ::= exprOrType typeDesc ::= (exprOrType
| 'proc' paramList [pragma] | 'proc' paramList [pragma]
| 'iterator' paramList [pragma] | 'iterator' paramList [pragma] )
['not' expr] # for now only 'not nil' suffix is supported
macroStmt ::= ':' [stmt] ('of' [exprList] ':' stmt macroStmt ::= ':' [stmt] ('of' [exprList] ':' stmt
|'elif' expr ':' stmt |'elif' expr ':' stmt

View file

@ -47,6 +47,10 @@ Language Additions
exceptions for you. exceptions for you.
- User defined effects ("tags") tracking has been added and the ``doc2`` - User defined effects ("tags") tracking has been added and the ``doc2``
command annotates possible tags for you. command annotates possible tags for you.
- Types can be annotated with the new syntax ``not nil`` to explictly state
that ``nil`` is not allowed. However currently the compiler performs no
advanced static checking for this; for now it's merely for documentation
purposes.
2012-09-23 Version 0.9.0 released 2012-09-23 Version 0.9.0 released