introduce a pre-processing pass for the concept bodies

fixes #4982
fixes #3805

close #3414
This commit is contained in:
Zahary Karadjov
2017-06-05 05:20:13 +03:00
committed by Andreas Rumpf
parent 30ccadfe4c
commit cd02561368
13 changed files with 138 additions and 60 deletions

View File

@@ -442,6 +442,7 @@ proc semConstBoolExpr(c: PContext, n: PNode): PNode =
result = nn
proc semGenericStmt(c: PContext, n: PNode): PNode
proc semConceptBody(c: PContext, n: PNode): PNode
include semtypes, semtempl, semgnrc, semstmts, semexprs

View File

@@ -40,6 +40,11 @@ type
bracketExpr*: PNode # current bracket expression (for ^ support)
mapping*: TIdTable
TMatchedConcept* = object
candidateType*: PType
prev*: ptr TMatchedConcept
depth*: int
TInstantiationPair* = object
genericSym*: PSym
inst*: PInstantiation
@@ -75,6 +80,7 @@ type
importTable*: PScope # scope for all imported symbols
topLevelScope*: PScope # scope for all top-level symbols
p*: PProcCon # procedure context
matchedConcept*: ptr TMatchedConcept # the current concept being matched
friendModules*: seq[PSym] # friend modules; may access private data;
# this is used so that generic instantiations
# can access private object fields
@@ -82,7 +88,6 @@ type
ambiguousSymbols*: IntSet # ids of all ambiguous symbols (cannot
# store this info in the syms themselves!)
inTypeClass*: int # > 0 if we are in a user-defined type class
inGenericContext*: int # > 0 if we are in a generic type
inUnrolledContext*: int # > 0 if we are unrolling a loop
compilesContextId*: int # > 0 if we are in a ``compiles`` magic
@@ -277,7 +282,7 @@ proc makeTypeFromExpr*(c: PContext, n: PNode): PType =
assert n != nil
result.n = n
proc newTypeWithSons2*(kind: TTypeKind, owner: PSym, sons: seq[PType]): PType =
proc newTypeWithSons*(owner: PSym, kind: TTypeKind, sons: seq[PType]): PType =
result = newType(kind, owner)
result.sons = sons

View File

@@ -705,7 +705,7 @@ proc afterCallActions(c: PContext; n, orig: PNode, flags: TExprFlags): PNode =
analyseIfAddressTakenInCall(c, result)
if callee.magic != mNone:
result = magicsAfterOverloadResolution(c, result, flags)
if c.inTypeClass == 0:
if c.matchedConcept == nil:
result = evalAtCompileTime(c, result)
proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
@@ -2147,7 +2147,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
let checks = if efNoEvaluateGeneric in flags: {checkUndeclared}
else: {checkUndeclared, checkModule, checkAmbiguity}
var s = qualifiedLookUp(c, n, checks)
if c.inTypeClass == 0: semCaptureSym(s, c.p.owner)
if c.matchedConcept == nil: semCaptureSym(s, c.p.owner)
result = semSym(c, n, s, flags)
if s.kind in {skProc, skMethod, skConverter, skIterator}:
#performProcvarCheck(c, n, s)

View File

@@ -594,8 +594,11 @@ proc foldConStrStr(m: PSym, n: PNode): PNode =
proc newSymNodeTypeDesc*(s: PSym; info: TLineInfo): PNode =
result = newSymNode(s, info)
result.typ = newType(tyTypeDesc, s.owner)
result.typ.addSonSkipIntLit(s.typ)
if s.typ.kind != tyTypeDesc:
result.typ = newType(tyTypeDesc, s.owner)
result.typ.addSonSkipIntLit(s.typ)
else:
result.typ = s.typ
proc getConstExpr(m: PSym, n: PNode): PNode =
result = nil

View File

@@ -34,7 +34,7 @@ type
type
TSemGenericFlag = enum
withinBind, withinTypeDesc, withinMixin
withinBind, withinTypeDesc, withinMixin, withinConcept
TSemGenericFlags = set[TSemGenericFlag]
proc semGenericStmt(c: PContext, n: PNode,
@@ -200,12 +200,13 @@ proc semGenericStmt(c: PContext, n: PNode,
checkMinSonsLen(n, 1)
let fn = n.sons[0]
var s = qualifiedLookUp(c, fn, {})
if s == nil and withinMixin notin flags and
if s == nil and
{withinMixin, withinConcept}*flags == {} and
fn.kind in {nkIdent, nkAccQuoted} and
considerQuotedIdent(fn).id notin ctx.toMixin:
errorUndeclaredIdentifier(c, n.info, fn.renderTree)
var first = 0
var first = ord(withinConcept in flags)
var mixinContext = false
if s != nil:
incl(s.flags, sfUsed)
@@ -471,3 +472,9 @@ proc semGenericStmt(c: PContext, n: PNode): PNode =
ctx.toMixin = initIntset()
result = semGenericStmt(c, n, {}, ctx)
semIdeForTemplateOrGeneric(c, result, ctx.cursorInBody)
proc semConceptBody(c: PContext, n: PNode): PNode =
var ctx: GenericCtx
ctx.toMixin = initIntset()
result = semGenericStmt(c, n, {withinConcept}, ctx)
semIdeForTemplateOrGeneric(c, result, ctx.cursorInBody)

View File

@@ -257,8 +257,8 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
# NOTE: for access of private fields within generics from a different module
# we set the friend module:
c.friendModules.add(getModule(fn))
let oldInTypeClass = c.inTypeClass
c.inTypeClass = 0
let oldMatchedConcept = c.matchedConcept
c.matchedConcept = nil
let oldScope = c.currentScope
while not isTopLevel(c): c.currentScope = c.currentScope.parent
result = copySym(fn, false)
@@ -319,5 +319,5 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
c.currentScope = oldScope
discard c.friendModules.pop()
dec(c.instCounter)
c.inTypeClass = oldInTypeClass
c.matchedConcept = oldMatchedConcept
if result.kind == skMethod: finishMethod(c, result)

View File

@@ -110,7 +110,7 @@ proc uninstantiate(t: PType): PType =
else: t
proc evalTypeTrait(traitCall: PNode, operand: PType, context: PSym): PNode =
const skippedTypes = {tyTypeDesc}
const skippedTypes = {tyTypeDesc, tyAlias}
let trait = traitCall[0]
internalAssert trait.kind == nkSym
var operand = operand.skipTypes(skippedTypes)
@@ -119,7 +119,7 @@ proc evalTypeTrait(traitCall: PNode, operand: PType, context: PSym): PNode =
traitCall.sons[2].typ.skipTypes({tyTypeDesc})
template typeWithSonsResult(kind, sons): PNode =
newTypeWithSons2(kind, context, sons).toNode(traitCall.info)
newTypeWithSons(context, kind, sons).toNode(traitCall.info)
case trait.sym.name.s
of "or", "|":

View File

@@ -144,7 +144,7 @@ proc fixNilType(n: PNode) =
n.typ = nil
proc discardCheck(c: PContext, result: PNode) =
if c.inTypeClass > 0: return
if c.matchedConcept != nil: return
if result.typ != nil and result.typ.kind notin {tyStmt, tyVoid}:
if result.kind == nkNilLit:
result.typ = nil
@@ -1761,7 +1761,7 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
else:
var expr = semExpr(c, n.sons[i], flags)
n.sons[i] = expr
if c.inTypeClass > 0 and expr.typ != nil:
if c.matchedConcept != nil and expr.typ != nil:
case expr.typ.kind
of tyBool:
if expr.kind == nkInfix and
@@ -1800,7 +1800,7 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode =
if result.len == 1 and
# concept bodies should be preserved as a stmt list:
c.inTypeClass == 0 and
c.matchedConcept == nil and
# also, don't make life complicated for macros.
# they will always expect a proper stmtlist:
nfBlockArg notin n.flags and

View File

@@ -1202,17 +1202,51 @@ proc semTypeClass(c: PContext, n: PNode, prev: PType): PType =
# if n.sonsLen == 0: return newConstraint(c, tyTypeClass)
if nfBase2 in n.flags:
message(n.info, warnDeprecated, "use 'concept' instead; 'generic'")
result = newOrPrevType(tyUserTypeClass, prev, c)
result.n = n
let
pragmas = n[1]
inherited = n[2]
result = newOrPrevType(tyUserTypeClass, prev, c)
var owner = getCurrOwner(c)
var candidateTypeSlot = newTypeWithSons(owner, tyAlias, @[c.errorType])
result.sons = @[candidateTypeSlot]
result.n = n
if inherited.kind != nkEmpty:
for n in inherited.sons:
let typ = semTypeNode(c, n, nil)
result.sons.safeAdd(typ)
result.sons.add(typ)
openScope(c)
for param in n[0]:
var
dummyName: PNode
dummyType: PType
let modifier = case param.kind
of nkVarTy: tyVar
of nkRefTy: tyRef
of nkPtrTy: tyPtr
of nkStaticTy: tyStatic
of nkTypeOfExpr: tyTypeDesc
else: tyNone
if modifier != tyNone:
dummyName = param[0]
dummyType = c.makeTypeWithModifier(modifier, candidateTypeSlot)
if modifier == tyTypeDesc: dummyType.flags.incl tfExplicit
else:
dummyName = param
dummyType = candidateTypeSlot
internalAssert dummyName.kind == nkIdent
var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar,
dummyName.ident, owner, owner.info)
dummyParam.typ = dummyType
addDecl(c, dummyParam)
result.n.sons[3] = semConceptBody(c, n[3])
closeScope(c)
proc semProcTypeWithScope(c: PContext, n: PNode,
prev: PType, kind: TSymKind): PType =

View File

@@ -607,14 +607,24 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
var
typeClass = ff.skipTypes({tyUserTypeClassInst})
body = typeClass.n[3]
if c.inTypeClass > 4:
localError(body.info, $body & " too nested for type matching")
return nil
matchedConceptContext: TMatchedConcept
prevMatchedConcept = c.matchedConcept
prevCandidateType = typeClass[0][0]
if prevMatchedConcept != nil:
matchedConceptContext.prev = prevMatchedConcept
matchedConceptContext.depth = prevMatchedConcept.depth + 1
if prevMatchedConcept.depth > 4:
localError(body.info, $body & " too nested for type matching")
return nil
openScope(c)
inc c.inTypeClass
matchedConceptContext.candidateType = a
typeClass[0].sons[0] = a
c.matchedConcept = addr(matchedConceptContext)
defer:
dec c.inTypeClass
c.matchedConcept = prevMatchedConcept
typeClass[0].sons[0] = prevCandidateType
closeScope(c)
var typeParams: seq[(PSym, PType)]
@@ -658,33 +668,6 @@ proc matchUserTypeClass*(c: PContext, m: var TCandidate,
addDecl(c, param)
for param in typeClass.n[0]:
var
dummyName: PNode
dummyType: PType
let modifier = case param.kind
of nkVarTy: tyVar
of nkRefTy: tyRef
of nkPtrTy: tyPtr
of nkStaticTy: tyStatic
of nkTypeOfExpr: tyTypeDesc
else: tyNone
if modifier != tyNone:
dummyName = param[0]
dummyType = c.makeTypeWithModifier(modifier, a)
if modifier == tyTypeDesc: dummyType.flags.incl tfExplicit
else:
dummyName = param
dummyType = a
internalAssert dummyName.kind == nkIdent
var dummyParam = newSym(if modifier == tyTypeDesc: skType else: skVar,
dummyName.ident, typeClass.sym, typeClass.sym.info)
dummyParam.typ = dummyType
addDecl(c, dummyParam)
var
oldWriteHook: type(writelnHook)
diagnostics: seq[string]
@@ -826,7 +809,7 @@ proc inferStaticParam*(c: var TCandidate, lhs: PNode, rhs: BiggestInt): bool =
var inferred = newTypeWithSons(c.c, tyStatic, lhs.typ.sons)
inferred.n = newIntNode(nkIntLit, rhs)
put(c, lhs.typ, inferred)
if c.c.inTypeClass > 0:
if c.c.matchedConcept != nil:
# inside concepts, binding is currently done with
# direct mutation of the involved types:
lhs.typ.n = inferred.n
@@ -916,7 +899,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
assert(aOrig != nil)
var
useTypeLoweringRuleInTypeClass = c.c.inTypeClass > 0 and
useTypeLoweringRuleInTypeClass = c.c.matchedConcept != nil and
not c.isNoCall and
f.kind != tyTypeDesc and
tfExplicit notin aOrig.flags
@@ -965,7 +948,10 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
# for example, but unfortunately `prepareOperand` is not called in certain
# situation when nkDotExpr are rotated to nkDotCalls
if a.kind in {tyGenericInst, tyAlias} and
if aOrig.kind == tyAlias:
return typeRel(c, f, lastSon(aOrig))
if a.kind == tyGenericInst and
skipTypes(f, {tyVar}).kind notin {
tyGenericBody, tyGenericInvocation,
tyGenericInst, tyGenericParam} + tyTypeClasses:
@@ -1106,7 +1092,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
if fRange.rangeHasUnresolvedStatic:
return inferStaticsInRange(c, fRange, a)
elif c.c.inTypeClass > 0 and aRange.rangeHasUnresolvedStatic:
elif c.c.matchedConcept != nil and aRange.rangeHasUnresolvedStatic:
return inferStaticsInRange(c, aRange, f)
else:
if lengthOrd(fRange) != lengthOrd(aRange):

View File

@@ -110,9 +110,11 @@ proc isDeepConstExpr*(n: PNode): bool =
proc isRange*(n: PNode): bool {.inline.} =
if n.kind in nkCallKinds:
if n[0].kind == nkIdent and n[0].ident.id == ord(wDotDot) or
n[0].kind in {nkClosedSymChoice, nkOpenSymChoice} and
n[0][1].sym.name.id == ord(wDotDot):
let callee = n[0]
if (callee.kind == nkIdent and callee.ident.id == ord(wDotDot)) or
(callee.kind == nkSym and callee.sym.name.id == ord(wDotDot)) or
(callee.kind in {nkClosedSymChoice, nkOpenSymChoice} and
callee[1].sym.name.id == ord(wDotDot)):
result = true
proc whichPragma*(n: PNode): TSpecialWord =

22
tests/concepts/t3414.nim Normal file
View File

@@ -0,0 +1,22 @@
type
View[T] = concept v
v.empty is bool
v.front is T
popFront v
proc find(view: View; target: View.T): View =
result = view
while not result.empty:
if view.front == target:
return
mixin popFront
popFront result
proc popFront[T](s: var seq[T]) = discard
proc empty[T](s: seq[T]): bool = false
var s1 = @[1, 2, 3]
let s2 = s1.find(10)

18
tests/concepts/t4982.nim Normal file
View File

@@ -0,0 +1,18 @@
discard """
errormsg: "undeclared identifier: 'x'"
line: 10
"""
import typetraits # without this import the program compiles (and echos false)
type
SomeTestConcept = concept t
x.name is string # typo: t.name was intended (which would result in echo true)
type
TestClass = ref object of RootObj
name: string
var test = TestClass(name: "mytest")
echo $(test is SomeTestConcept)