reference implementation of a vector swizzle library

This also provides the initial steps towards support for type class "filtered" type inference
fixes an "ordinal type expected" ICE, related to the use of static params
This commit is contained in:
Zahary Karadjov
2014-03-19 02:52:48 +02:00
parent a66d059acc
commit 4b7655fd10
12 changed files with 225 additions and 58 deletions

View File

@@ -435,6 +435,8 @@ type
tfUnresolved, # marks unresolved typedesc/static params: e.g.
# proc foo(T: typedesc, list: seq[T]): var T
# proc foo(L: static[int]): array[L, int]
# can be attached to ranges to indicate that the range
# depends on unresolved static params.
tfRetType, # marks return types in proc (used to detect type classes
# used as return types for return type inference)
tfCapturesEnv, # whether proc really captures some environment

View File

@@ -66,6 +66,9 @@ proc fitNode(c: PContext, formal: PType, arg: PNode): PNode =
result = copyTree(arg)
result.typ = formal
proc inferWithMetatype(c: PContext, formal: PType,
arg: PNode, coerceDistincts = false): PNode
var commonTypeBegin = PType(kind: tyExpr)
proc commonType*(x, y: PType): PType =

View File

@@ -228,12 +228,25 @@ proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode =
if m.genericConverter and result != nil:
instGenericConvertersArg(c, result, m)
proc convertTo*(c: PContext, f: PType, n: PNode): PNode =
proc inferWithMetatype(c: PContext, formal: PType,
arg: PNode, coerceDistincts = false): PNode =
var m: TCandidate
initCandidate(c, m, f)
result = paramTypesMatch(m, f, n.typ, n, nil)
initCandidate(c, m, formal)
m.coerceDistincts = coerceDistincts
result = paramTypesMatch(m, formal, arg.typ, arg, nil)
if m.genericConverter and result != nil:
instGenericConvertersArg(c, result, m)
if result != nil:
# This almost exactly replicates the steps taken by the compiler during
# param matching. It performs an embarassing ammount of back-and-forth
# type jugling, but it's the price to pay for consistency and correctness
result.typ = generateTypeInstance(c, m.bindings, arg.info,
formal.skipTypes({tyCompositeTypeClass}))
else:
typeMismatch(arg, formal, arg.typ)
# error correction:
result = copyTree(arg)
result.typ = formal
proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode =
assert x.state == csMatch

View File

@@ -251,6 +251,27 @@ proc makeNotType*(c: PContext, t1: PType): PType =
propagateToOwner(result, t1)
result.flags.incl(t1.flags * {tfHasStatic})
proc nMinusOne*(n: PNode): PNode =
result = newNode(nkCall, n.info, @[
newSymNode(getSysMagic("<", mUnaryLt)),
n])
# Remember to fix the procs below this one when you make changes!
proc makeRangeWithStaticExpr*(c: PContext, n: PNode): PType =
let intType = getSysType tyInt
result = newTypeS(tyRange, c)
result.sons = @[intType]
result.n = newNode(nkRange, n.info, @[
newIntTypeNode(nkIntLit, 0, intType),
makeStaticExpr(c, n.nMinusOne)])
template rangeHasStaticIf*(t: PType): bool =
# this accepts the ranges's node
t.n[1].kind == nkStaticExpr
template getStaticTypeFromRange*(t: PType): PType =
t.n[1][0][1].typ
proc newTypeS(kind: TTypeKind, c: PContext): PType =
result = newType(kind, getCurrOwner())

View File

@@ -121,6 +121,8 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
return n
of skType:
markUsed(n, s)
if s.typ.kind == tyStatic and s.typ.n != nil:
return s.typ.n
result = newSymNode(s, n.info)
result.typ = makeTypeDesc(c, s.typ)
else:
@@ -191,15 +193,35 @@ proc isCastable(dst, src: PType): bool =
proc isSymChoice(n: PNode): bool {.inline.} =
result = n.kind in nkSymChoices
proc maybeLiftType(t: var PType, c: PContext, info: TLineInfo) =
# XXX: liftParamType started to perform addDecl
# we could do that instead in semTypeNode by snooping for added
# gnrc. params, then it won't be necessary to open a new scope here
openScope(c)
var lifted = liftParamType(c, skType, newNodeI(nkArgList, info),
t, ":anon", info)
closeScope(c)
if lifted != nil: t = lifted
proc semConv(c: PContext, n: PNode): PNode =
if sonsLen(n) != 2:
localError(n.info, errConvNeedsOneArg)
return n
result = newNodeI(nkConv, n.info)
result.typ = semTypeNode(c, n.sons[0], nil).skipTypes({tyGenericInst})
addSon(result, copyTree(n.sons[0]))
addSon(result, semExprWithType(c, n.sons[1]))
var op = result.sons[1]
var targetType = semTypeNode(c, n.sons[0], nil)
maybeLiftType(targetType, c, n[0].info)
result.addSon copyTree(n.sons[0])
var op = semExprWithType(c, n.sons[1])
if targetType.isMetaType:
let final = inferWithMetatype(c, targetType, op, true)
result.addSon final
result.typ = final.typ
return
result.typ = targetType
addSon(result, op)
if not isSymChoice(op):
let status = checkConvertible(c, result.typ, op.typ)
@@ -221,7 +243,7 @@ proc semConv(c: PContext, n: PNode): PNode =
for i in countup(0, sonsLen(op) - 1):
let it = op.sons[i]
let status = checkConvertible(c, result.typ, it.typ)
if status == convOK:
if status in {convOK, convNotNeedeed}:
markUsed(n, it.sym)
markIndirect(c, it.sym)
return it
@@ -325,14 +347,7 @@ proc isOpImpl(c: PContext, n: PNode): PNode =
tfIterator notin t.flags))
else:
var t2 = n[2].typ.skipTypes({tyTypeDesc})
# XXX: liftParamType started to perform addDecl
# we could do that instead in semTypeNode by snooping for added
# gnrc. params, then it won't be necessary to open a new scope here
openScope(c)
let lifted = liftParamType(c, skType, newNodeI(nkArgList, n.info),
t2, ":anon", n.info)
closeScope(c)
if lifted != nil: t2 = lifted
maybeLiftType(t2, c, n.info)
var m: TCandidate
initCandidate(c, m, t2)
let match = typeRel(m, t2, t1) != isNone
@@ -1202,7 +1217,7 @@ proc semAsgn(c: PContext, n: PNode): PNode =
if lhsIsResult: {efAllowDestructor} else: {})
if lhsIsResult:
n.typ = enforceVoidContext
if resultTypeIsInferrable(lhs.sym.typ):
if c.p.owner.kind != skMacro and resultTypeIsInferrable(lhs.sym.typ):
if cmpTypes(c, lhs.typ, rhs.typ) == isGeneric:
internalAssert c.p.resultSym != nil
lhs.typ = rhs.typ

View File

@@ -12,9 +12,6 @@
var enforceVoidContext = PType(kind: tyStmt)
proc semCommand(c: PContext, n: PNode): PNode =
result = semExprNoType(c, n)
proc semDiscard(c: PContext, n: PNode): PNode =
result = n
checkSonsLen(n, 1)
@@ -133,6 +130,7 @@ proc fixNilType(n: PNode) =
n.typ = nil
proc discardCheck(c: PContext, result: PNode) =
if c.inTypeClass > 0: return
if result.typ != nil and result.typ.kind notin {tyStmt, tyEmpty}:
if result.kind == nkNilLit:
result.typ = nil
@@ -143,11 +141,6 @@ proc discardCheck(c: PContext, result: PNode) =
while n.kind in skipForDiscardable:
n = n.lastSon
n.typ = nil
elif c.inTypeClass > 0:
if result.typ.kind == tyBool:
let verdict = semConstExpr(c, result)
if verdict.intVal == 0:
localError(result.info, "type class predicate failed")
elif result.typ.kind != tyError and gCmd != cmdInteractive:
if result.typ.kind == tyNil:
fixNilType(result)
@@ -1324,13 +1317,17 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
return
else:
n.sons[i] = semExpr(c, n.sons[i])
if c.inTypeClass > 0 and n[i].typ != nil and n[i].typ.kind == tyBool:
let verdict = semConstExpr(c, n[i])
if verdict.intVal == 0:
localError(result.info, "type class predicate failed")
if n.sons[i].typ == enforceVoidContext or usesResult(n.sons[i]):
voidContext = true
n.typ = enforceVoidContext
if i == last and (length == 1 or efWantValue in flags):
n.typ = n.sons[i].typ
if not isEmptyType(n.typ): n.kind = nkStmtListExpr
elif i != last or voidContext or c.inTypeClass > 0:
elif i != last or voidContext:
discardCheck(c, n.sons[i])
else:
n.typ = n.sons[i].typ

View File

@@ -198,19 +198,6 @@ proc semRange(c: PContext, n: PNode, prev: PType): PType =
localError(n.info, errXExpectsOneTypeParam, "range")
result = newOrPrevType(tyError, prev, c)
proc nMinusOne(n: PNode): PNode =
result = newNode(nkCall, n.info, @[
newSymNode(getSysMagic("<", mUnaryLt)),
n])
proc makeRangeWithStaticExpr(c: PContext, n: PNode): PType =
let intType = getSysType(tyInt)
result = newTypeS(tyRange, c)
result.sons = @[intType]
result.n = newNode(nkRange, n.info, @[
newIntTypeNode(nkIntLit, 0, intType),
makeStaticExpr(c, n.nMinusOne)])
proc semArray(c: PContext, n: PNode, prev: PType): PType =
var indx, base: PType
result = newOrPrevType(tyArray, prev, c)
@@ -229,6 +216,7 @@ proc semArray(c: PContext, n: PNode, prev: PType): PType =
if not isOrdinalType(e.typ.lastSon):
localError(n[1].info, errOrdinalTypeExpected)
indx = makeRangeWithStaticExpr(c, e)
indx.flags.incl tfUnresolved
elif e.kind in nkCallKinds and hasGenericArguments(e):
if not isOrdinalType(e.typ):
localError(n[1].info, errOrdinalTypeExpected)
@@ -628,17 +616,28 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType): PType =
if base == nil and tfInheritable notin result.flags:
incl(result.flags, tfFinal)
proc findEnforcedStaticType(t: PType): PType =
# This handles types such as `static[T] and Foo`,
# which are subset of `static[T]`, hence they could
# be treated in the same way
if t.kind == tyStatic: return t
if t.kind == tyAnd:
for s in t.sons:
let t = findEnforcedStaticType(s)
if t != nil: return t
proc addParamOrResult(c: PContext, param: PSym, kind: TSymKind) =
template addDecl(x) =
if sfGenSym notin x.flags: addDecl(c, x)
if kind == skMacro:
if param.typ.kind == tyTypeDesc:
addDecl(param)
elif param.typ.kind == tyStatic:
let staticType = findEnforcedStaticType(param.typ)
if staticType != nil:
var a = copySym(param)
a.typ = param.typ.base
a.typ = staticType.base
addDecl(a)
elif param.typ.kind == tyTypeDesc:
addDecl(param)
else:
# within a macro, every param has the type PNimrodNode!
let nn = getSysSym"PNimrodNode"
@@ -944,14 +943,18 @@ proc semBlockType(c: PContext, n: PNode, prev: PType): PType =
proc semGenericParamInInvokation(c: PContext, n: PNode): PType =
result = semTypeNode(c, n, nil)
proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
if s.typ == nil:
localError(n.info, "cannot instantiate the '$1' $2" %
[s.name.s, ($s.kind).substr(2).toLower])
return newOrPrevType(tyError, prev, c)
var t = s.typ
if t.kind == tyCompositeTypeClass and t.base.kind == tyGenericBody:
t = t.base
result = newOrPrevType(tyGenericInvokation, prev, c)
addSonSkipIntLit(result, s.typ)
addSonSkipIntLit(result, t)
template addToResult(typ) =
if typ.isNil:
@@ -959,23 +962,24 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
rawAddSon(result, typ)
else: addSonSkipIntLit(result, typ)
if s.typ.kind == tyForward:
if t.kind == tyForward:
for i in countup(1, sonsLen(n)-1):
var elem = semGenericParamInInvokation(c, n.sons[i])
addToResult(elem)
elif s.typ.kind != tyGenericBody:
return
elif t.kind != tyGenericBody:
#we likely got code of the form TypeA[TypeB] where TypeA is
#not generic.
localError(n.info, errNoGenericParamsAllowedForX, s.name.s)
return newOrPrevType(tyError, prev, c)
else:
var m = newCandidate(c, s, n)
var m = newCandidate(c, t)
matches(c, n, copyTree(n), m)
if m.state != csMatch:
var err = "cannot instantiate " & typeToString(s.typ) & "\n" &
var err = "cannot instantiate " & typeToString(t) & "\n" &
"got: (" & describeArgs(c, n) & ")\n" &
"but expected: (" & describeArgs(c, s.typ.n, 0) & ")"
"but expected: (" & describeArgs(c, t.n, 0) & ")"
localError(n.info, errGenerated, err)
return newOrPrevType(tyError, prev, c)
@@ -987,7 +991,8 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
addToResult(typ)
if isConcrete:
if s.ast == nil:
if s.ast == nil and s.typ.kind != tyCompositeTypeClass:
# XXX: What kind of error is this? is it still relevant?
localError(n.info, errCannotInstantiateX, s.name.s)
result = newOrPrevType(tyError, prev, c)
else:

View File

@@ -40,6 +40,8 @@ type
proxyMatch*: bool # to prevent instantiations
genericConverter*: bool # true if a generic converter needs to
# be instantiated
coerceDistincts*: bool # this is an explicit coercion that can strip away
# a distrinct type
typedescMatched: bool
inheritancePenalty: int # to prefer closest father object type
errors*: seq[string] # additional clarifications to be displayed to the
@@ -114,6 +116,9 @@ proc newCandidate*(ctx: PContext, callee: PSym,
binding: PNode, calleeScope = -1): TCandidate =
initCandidate(ctx, result, callee, binding, calleeScope)
proc newCandidate*(ctx: PContext, callee: PType): TCandidate =
initCandidate(ctx, result, callee)
proc copyCandidate(a: var TCandidate, b: TCandidate) =
a.c = b.c
a.exactMatches = b.exactMatches
@@ -460,7 +465,8 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
if param.kind == nkVarTy:
dummyName = param[0]
dummyType = makeVarType(c, a)
dummyType = if a.kind != tyVar: makeVarType(c, a)
else: a
else:
dummyName = param
dummyType = a
@@ -470,7 +476,7 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
dummyParam.typ = dummyType
addDecl(c, dummyParam)
var checkedBody = c.semTryExpr(c, copyTree(body.n[3]), bufferErrors = false)
var checkedBody = c.semTryExpr(c, body.n[3].copyTree, bufferErrors = false)
m.errors = bufferedMsgs
clearBufferedMsgs()
if checkedBody == nil: return isNone
@@ -623,6 +629,20 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
result = typeRel(c, f.sons[1], a.sons[1])
if result < isGeneric:
result = isNone
elif tfUnresolved in fRange.flags and
rangeHasStaticIf(fRange):
# This is a range from an array instantiated with a generic
# static param. We must extract the static param here and bind
# it to the size of the currently supplied array.
var
rangeStaticT = fRange.getStaticTypeFromRange
replacementT = newTypeWithSons(c.c, tyStatic, @[tyInt.getSysType])
inputUpperBound = a.sons[0].n[1].intVal
# we must correct for the off-by-one discrepancy between
# ranges and static params:
replacementT.n = newIntNode(nkIntLit, inputUpperBound + 1)
put(c.bindings, rangeStaticT, replacementT)
result = isGeneric
elif lengthOrd(fRange) != lengthOrd(a):
result = isNone
else: discard
@@ -686,6 +706,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
result = isSubtype
of tyDistinct:
if (a.kind == tyDistinct) and sameDistinctTypes(f, a): result = isEqual
elif c.coerceDistincts: result = typeRel(c, f.base, a)
of tySet:
if a.kind == tySet:
if (f.sons[0].kind != tyGenericParam) and (a.sons[0].kind == tyEmpty):
@@ -848,7 +869,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
of tyUserTypeClass, tyUserTypeClassInst:
considerPreviousT:
result = matchUserTypeClass(c.c, c, f, a)
result = matchUserTypeClass(c.c, c, f, aOrig)
if result == isGeneric:
put(c.bindings, f, a)
@@ -863,7 +884,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation =
of tyGenericParam:
var x = PType(idTableGet(c.bindings, f))
if x == nil:
if c.calleeSym != nil and c.calleeSym.kind == skType and
if c.calleeSym != nil and c.callee.kind == tyGenericBody and
f.kind == tyGenericParam and not c.typedescMatched:
# XXX: The fact that generic types currently use tyGenericParam for
# their parameters is really a misnomer. tyGenericParam means "match
@@ -1051,7 +1072,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
# XXX: When implicit statics are the default
# this will be done earlier - we just have to
# make sure that static types enter here
# XXX: weaken tyGenericParam and call it tyGenericPlaceholder
# and finally start using tyTypedesc for generic types properly.
if argType.kind == tyGenericParam and tfWildcard in argType.flags:
@@ -1060,7 +1081,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType,
return argSemantized
if argType.kind == tyStatic:
if m.calleeSym.kind == skType:
if m.callee.kind == tyGenericBody:
result = newNodeI(nkType, argOrig.info)
result.typ = makeTypeFromExpr(c, arg)
return

View File

@@ -436,6 +436,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string =
of tyStatic:
internalAssert t.len > 0
result = "static[" & typeToString(t.sons[0]) & "]"
if t.n != nil: result.add "(" & renderTree(t.n) & ")"
of tyUserTypeClass:
internalAssert t.sym != nil and t.sym.owner != nil
return t.sym.owner.name.s

View File

@@ -1,5 +1,5 @@
discard """
line: 11
line: 10
errormsg: "type mismatch: got (typedesc[float], string)"
"""

View File

@@ -0,0 +1,79 @@
discard """
output: '''3
[1, 3]
[2, 1, 2]
'''
"""
import macros, strutils
template accept(e: expr) =
static: assert(compiles(e))
template reject(e: expr) =
static: assert(not compiles(e))
proc swizzleIdx(c: char): int =
return case c
of 'x': 0
of 'y': 1
of 'z': 2
of 'w': 3
of 'r': 0
of 'g': 1
of 'b': 2
of 'a': 3
else: 0
proc isSwizzle(s: string): bool =
template trySet(name, set) =
block search:
for c in s:
if c notin set:
break search
return true
trySet coords, {'x', 'y', 'z', 'w'}
trySet colors, {'r', 'g', 'b', 'a'}
return false
type
StringIsSwizzle = generic value
value.isSwizzle
SwizzleStr = static[string] and StringIsSwizzle
proc foo(x: SwizzleStr) =
echo "sw"
accept foo("xx")
reject foo("xe")
type
Vec[N: static[int]; T] = array[N, T]
proc card(x: Vec): int = x.N
proc `$`(x: Vec): string = x.repr.strip
macro `.`(x: Vec, swizzle: SwizzleStr): expr =
var
cardinality = swizzle.len
values = newNimNode(nnkBracket)
v = genSym()
for c in swizzle:
values.add newNimNode(nnkBracketExpr).add(
v, c.swizzleIdx.newIntLitNode)
return quote do:
let `v` = `x`
Vec[`cardinality`, `v`.T](`values`)
var z = Vec([1, 2, 3])
echo z.card
echo z.xz
echo z.yxy

View File

@@ -0,0 +1,10 @@
import typetraits
type
Vec[N: static[int]; T] = distinct array[N, T]
var x = Vec([1, 2, 3])
static:
assert x.type.name == "Vec[static[int](3), int]"