mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-28 17:04:41 +00:00
introduce a pre-processing pass for the concept bodies
fixes #4982 fixes #3805 close #3414
This commit is contained in:
committed by
Andreas Rumpf
parent
30ccadfe4c
commit
cd02561368
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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", "|":
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
22
tests/concepts/t3414.nim
Normal 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
18
tests/concepts/t4982.nim
Normal 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)
|
||||
|
||||
Reference in New Issue
Block a user