diff --git a/compiler/sem.nim b/compiler/sem.nim index 9f80e13993..cd0df0de04 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -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 diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 5da2b70fa4..d422646a85 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -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 diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 67a718b91c..7c4fcd8815 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -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) diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 58239d23e7..0aceaac4ce 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -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 diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 3938259ad1..7e55b266a8 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -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) diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 486065563d..196dcdbb83 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -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) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index eb6259df09..c664f735c9 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -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", "|": diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 7ea7763c3f..b00e9a9c07 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -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 diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 1e0a710c23..c0974022a0 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -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 = diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 4797366f85..5280c27a1d 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -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): diff --git a/compiler/trees.nim b/compiler/trees.nim index aa5738195d..c77dab349e 100644 --- a/compiler/trees.nim +++ b/compiler/trees.nim @@ -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 = diff --git a/tests/concepts/t3414.nim b/tests/concepts/t3414.nim new file mode 100644 index 0000000000..d45973034e --- /dev/null +++ b/tests/concepts/t3414.nim @@ -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) + diff --git a/tests/concepts/t4982.nim b/tests/concepts/t4982.nim new file mode 100644 index 0000000000..9d82c83c9f --- /dev/null +++ b/tests/concepts/t4982.nim @@ -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) +