From ee5870e82a010998df7276105cbc2f4dcd1e1aac Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Sun, 1 Dec 2024 22:33:28 -0500 Subject: [PATCH 1/3] stuff --- compiler/concepts.nim | 416 +++++++++++++----- compiler/sigmatch.nim | 39 -- compiler/types.nim | 39 ++ lib/system.nim | 3 + tests/concepts/conceptv2negative/tauto.nim | 14 + tests/concepts/conceptv2negative/tgenobj.nim | 14 + .../tnoimplicitbindingcontainer.nim | 19 + .../tnoimplicitbindingeach.nim | 19 + tests/concepts/conceptv2negative/tnottype.nim | 17 + tests/concepts/conceptv2negative/tor.nim | 13 + .../concepts/conceptv2negative/ttypedesc.nim | 13 + tests/concepts/tconceptsv2.nim | 175 +++++++- 12 files changed, 639 insertions(+), 142 deletions(-) create mode 100644 tests/concepts/conceptv2negative/tauto.nim create mode 100644 tests/concepts/conceptv2negative/tgenobj.nim create mode 100644 tests/concepts/conceptv2negative/tnoimplicitbindingcontainer.nim create mode 100644 tests/concepts/conceptv2negative/tnoimplicitbindingeach.nim create mode 100644 tests/concepts/conceptv2negative/tnottype.nim create mode 100644 tests/concepts/conceptv2negative/tor.nim create mode 100644 tests/concepts/conceptv2negative/ttypedesc.nim diff --git a/compiler/concepts.nim b/compiler/concepts.nim index 23f8c5d87ce7b..31e685da5177c 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -78,7 +78,8 @@ type magic: TMagic ## mArrGet and mArrPut is wrong in system.nim and ## cannot be fixed that easily. ## Thus we special case it here. - concpt: PType + concpt: PType ## current concept being evaluated + strict = true ## flag for `Self` and `each` and other scenareos were strict matching is off proc existingBinding(m: MatchCon; key: PType): PType = ## checks if we bound the type variable 'key' already to some @@ -89,41 +90,234 @@ proc existingBinding(m: MatchCon; key: PType): PType = proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool -proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool +proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool + +proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool + +proc defSignatureType(n: PNode): PType = n[0].sym.typ + +proc conceptBody(n: PType): PNode = n.n.lastSon + +proc isEach(t: PType): bool = t.kind == tyGenericInvocation and t.genericHead.sym.name.s == "TypeEach" + +proc acceptsAllTypes(t: PType): bool= + result = false + if t.kind == tyAnything: + result = true + elif t.kind == tyGenericParam: + if tfImplicitTypeParam in t.flags: + result = true + if not(t.hasElementType) or t.elementType.kind == tyNone: + result = true + +iterator travStmts(n: PNode): PNode {. closure .} = + if n.kind in {nkStmtList, nkStmtListExpr}: + for i in 0.. an.len: + return false + let + ft = fn.defSignatureType + at = an.defSignatureType + + for i in 1 ..< ft.n.len: + let oldLen = m.inferred.len + if not matchType(c, ft.n[i].typ, at.n[i].typ, m): + m.inferred.setLen oldLen + return false + result = true + let oldLen = m.inferred.len + if not matchReturnType(c, ft.returnType, at.returnType, m): + m.inferred.setLen oldLen + result = false + +proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): bool= + # XXX: In the future this may need extra parameters to carry info for container types + result = fc.n == ac.n + if result: + # This will have to take generic parameters into account at some point for container types + return + let + fn = fc.conceptBody + an = ac.conceptBody + for fdef in fn: + result = false + for ndef in an: + result = cmpConceptDefs(c, fdef, ndef, m) + if result: + break + if not result: + break + +proc matchImplicitDef(c: PContext; fn, an: PNode; aConpt: PNode, m: var MatchCon): bool= + let + ft = fn.defSignatureType + at = an.defSignatureType + if fn.kind != an.kind: + return false + if fn.len > an.len: + return false + + result = true + for i in 1 ..< ft.n.len: + var aType = at.n[i].typ + if aType.reduceToBase.isSelf: + # Self in `an` is always legal here + continue + var fType = ft.n[i].typ + if fType.reduceToBase.isSelf: + if fType.kind in {tyVar, tySink, tyLent, tyOwned} and aType.kind == fType.kind: + aType = aType.elementType + fType = m.potentialImplementation.skipTypes({tyVar, tySink, tyLent, tyOwned}) + else: + fType = m.potentialImplementation + if aType.kind == tyConcept and conceptsMatch(c, aType, m.concpt, m): + return true + let oldLen = m.inferred.len + if not matchType(c, aType, fType, m): + m.inferred.setLen oldLen + return false + +proc matchCodependentConcept(c: PContext; n: PNode; m: var MatchCon): bool = + result = false + let sig = n.defSignatureType.n + for i in 1 ..< sig.len: + let paramType = sig[i].typ + if paramType.kind == tyGenericParam: + # this may have to be changed to only parameters of the concept + # generic parameters of the proc that are bound by concepts are subject to substitution + continue + if paramType.reduceToBase.kind == tyConcept: + for aDef in travStmts(paramType.reduceToBase.conceptBody): + if n[namePos].sym.name == aDef[namePos].sym.name: + let oldStrict = m.strict + m.strict = false + result = matchImplicitDef(c, n, aDef, sig[i], m) + m.strict = oldStrict + +proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = ## The heart of the concept matching process. 'f' is the formal parameter of some ## routine inside the concept that we're looking for. 'a' is the formal parameter ## of a routine that might match. const ignorableForArgType = {tyVar, tySink, tyLent, tyOwned, tyGenericInst, tyAlias, tyInferred} + + template notStrict(body: untyped)= + let oldStrict = m.strict + m.strict = false + body + m.strict = oldStrict + + var + a = ao + f = fo + + if isEach(f): + notStrict: + return matchType(c, f.last, a, m) + + case a.kind + of tyGenericParam: + # I forget how bindings can end up here but they did once + let binding = m.existingBinding(a) + if binding != nil: + a = binding + of tyAnything: + if m.strict: + return true + of tyNot: + if m.strict: + if f.kind == tyNot: + return matchType(c, f.elementType, a.elementType, m) + else: + let oldLen = m.inferred.len + result = not matchType(c, f, a.elementType, m) + m.inferred.setLen oldLen + return + of tyCompositeTypeClass, tyGenericInst, tyConcept: + let + aBase = a.reduceToBase + fBase = f.reduceToBase + if aBase.kind == tyConcept: + if fBase.kind == tyConcept: + return conceptsMatch(c, fBase, aBase, m) + else: + return matchConceptToImpl(c, a, f, m) + else: + discard + case f.kind of tyAlias: result = matchType(c, f.skipModifier, a, m) of tyTypeDesc: if isSelf(f): #let oldLen = m.inferred.len - result = matchType(c, a, m.potentialImplementation, m) + notStrict: + result = matchType(c, a, m.potentialImplementation, m) #echo "self is? ", result, " ", a.kind, " ", a, " ", m.potentialImplementation, " ", m.potentialImplementation.kind #m.inferred.setLen oldLen #echo "A for ", result, " to ", typeToString(a), " to ", typeToString(m.potentialImplementation) else: - if a.kind == tyTypeDesc and f.hasElementType == a.hasElementType: - if f.hasElementType: + result = false + if a.kind == tyTypeDesc: + if not(a.hasElementType) or a.elementType.kind == tyNone: + result = true + elif f.hasElementType: result = matchType(c, f.elementType, a.elementType, m) - else: - result = true # both lack it - else: - result = false - of tyGenericInvocation: result = false - if a.kind == tyGenericInst and a.genericHead.kind == tyGenericBody: + if f.genericHead.elementType.kind == tyConcept: + result = matchType(c, f.genericHead.elementType, a, m) + elif a.kind == tyGenericInst and a.genericHead.kind == tyGenericBody: if sameType(f.genericHead, a.genericHead) and f.kidsLen == a.kidsLen-1: result = matchKids(c, f, a, m, start=FirstGenericParamAt) of tyGenericParam: @@ -136,22 +330,29 @@ proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool = if f.hasElementType and f.elementType.kind != tyNone: # also check the generic's constraints: let oldLen = m.inferred.len - result = matchType(c, f.elementType, a, m) + notStrict: + # XXX: this should only be not strict for the current concept's parameters NOT the proc's + result = matchType(c, f.elementType, a, m) m.inferred.setLen oldLen if result: when logBindings: echo "A adding ", f, " ", ak m.inferred.add((f, ak)) elif m.magic == mArrGet and ak.kind in {tyArray, tyOpenArray, tySequence, tyVarargs, tyCstring, tyString}: - when logBindings: echo "B adding ", f, " ", lastSon ak + when logBindings: echo "B adding ", f, " ", last ak m.inferred.add((f, last ak)) result = true else: - when logBindings: echo "C adding ", f, " ", ak - m.inferred.add((f, ak)) - #echo "binding ", typeToString(ak), " to ", typeToString(f) - result = true + if tfImplicitTypeParam in f.flags: + # this is another way of representing tyAnything? + result = not(m.strict) or a.acceptsAllTypes + else: + when logBindings: echo "C adding ", f, " ", ak + m.inferred.add((f, ak)) + #echo "binding ", typeToString(ak), " to ", typeToString(f) + result = true elif not m.marker.containsOrIncl(old.id): - result = matchType(c, old, ak, m) + notStrict: + result = matchType(c, old, ak, m) if m.magic == mArrPut and ak.kind == tyGenericParam: result = true else: @@ -174,36 +375,20 @@ proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool = let ak = a.skipTypes(ignorableForArgType) result = ak.kind == f.kind or ak.kind == tyOrdinal or (ak.kind == tyGenericParam and ak.hasElementType and ak.elementType.kind == tyOrdinal) - of tyConcept: - if a.kind == tyConcept and f.n == a.n: - result = true - elif m.concpt.size == szIllegalRecursion: - result = false - else: - let oldLen = m.inferred.len - let oldPotentialImplementation = m.potentialImplementation - m.potentialImplementation = a - m.concpt.size = szIllegalRecursion - let oldConcept = m.concpt - m.concpt = f - result = conceptMatchNode(c, f.n.lastSon, m) - m.potentialImplementation = oldPotentialImplementation - m.concpt = oldConcept - m.concpt.size = szUnknownSize - if not result: - m.inferred.setLen oldLen of tyGenericBody: var ak = a if a.kind == tyGenericBody: ak = last(a) result = matchType(c, last(f), ak, m) of tyCompositeTypeClass: - var ak = if a.kind == tyCompositeTypeClass: a.last else: a - result = matchType(c, last(f), ak, m) + if a.kind == tyCompositeTypeClass: + result = matchKids(c, f, a, m) + else: + result = matchType(c, last(f), a, m) of tyArray, tyTuple, tyVarargs, tyOpenArray, tyRange, tySequence, tyRef, tyPtr, tyGenericInst: # ^ XXX Rewrite this logic, it's more complex than it needs to be. - if f.kind == tyArray and f.kidsLen == 3: + if f.kind == tyArray and f.kidsLen == 3 and a.kind == tyArray: # XXX: this is a work-around! # system.nim creates these for the magic array typeclass result = true @@ -213,40 +398,66 @@ proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool = if ak.kind == f.kind and f.kidsLen == ak.kidsLen: result = matchKids(c, f, ak, m) of tyOr: - let oldLen = m.inferred.len - if a.kind == tyOr: - # say the concept requires 'int|float|string' if the potentialImplementation - # says 'int|string' that is good enough. - var covered = 0 - for ff in f.kids: - for aa in a.kids: - let oldLenB = m.inferred.len - let r = matchType(c, ff, aa, m) - if r: - inc covered + result = false + if m.strict: + let oldLen = m.inferred.len + if a.kind in {tyOr, tyGenericParam}: + result = true + for ff in traverseTyOr(f): + result = false + for aa in traverseTyOr(a): + let oldLenB = m.inferred.len + let r = matchType(c, ff, aa, m) + if r: + result = true + break + m.inferred.setLen oldLenB + if not result: break - m.inferred.setLen oldLenB - - result = covered >= a.kidsLen - if not result: - m.inferred.setLen oldLen - else: - result = false - for ff in f.kids: - result = matchType(c, ff, a, m) - if result: break # and remember the binding! - m.inferred.setLen oldLen - of tyNot: - if a.kind == tyNot: - result = matchType(c, f.elementType, a.elementType, m) else: let oldLen = m.inferred.len - result = not matchType(c, f.elementType, a, m) - m.inferred.setLen oldLen - of tyAnything: - result = true + if a.kind == tyOr: + # say the concept requires 'int|float|string' if the potentialImplementation + # says 'int|string' that is good enough. + var covered = 0 + for ff in f.kids: + for aa in a.kids: + let oldLenB = m.inferred.len + let r = matchType(c, ff, aa, m) + if r: + inc covered + break + m.inferred.setLen oldLenB + + result = covered >= a.kidsLen + if not result: + m.inferred.setLen oldLen + else: + result = false + for ff in f.kids: + result = matchType(c, ff, a, m) + if result: break # and remember the binding! + m.inferred.setLen oldLen + of tyNot: + result = false + if not m.strict: + if a.kind == tyNot: + result = matchType(c, f.elementType, a.elementType, m) + else: + let oldLen = m.inferred.len + result = not matchType(c, f.elementType, a, m) + m.inferred.setLen oldLen + of tyConcept: + # TODO: conceptsMatch's current logic is wrong rn, I think. fix with a test + result = a.kind == tyConcept and conceptsMatch(c, f, a, m) + if not (result or m.strict): + # this is for `each` parameters and generic parameters. Can search for a candidate iff + # the concept (f) is a constraint and not a requirement + result = matchConceptToImpl(c, f, a, m) of tyOrdinal: result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam + of tyAnything: + result = not(m.strict) or a.acceptsAllTypes of tyStatic: result = false var scomp = f.base @@ -278,7 +489,7 @@ proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool = let oldLen = m.inferred.len let can = candidate.typ.n - let con = n[0].sym.typ.n + let con = defSignatureType(n).n if can.len < con.len: # too few arguments, cannot be a match: @@ -290,7 +501,7 @@ proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool = m.inferred.setLen oldLen return false - if not matchReturnType(c, n[0].sym.typ.returnType, candidate.typ.returnType, m): + if not matchReturnType(c, n.defSignatureType.returnType, candidate.typ.returnType, m): m.inferred.setLen oldLen return false @@ -307,44 +518,47 @@ proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool = proc matchSyms(c: PContext, n: PNode; kinds: set[TSymKind]; m: var MatchCon): bool = ## Walk the current scope, extract candidates which the same name as 'n[namePos]', ## 'n' is the nkProcDef or similar from the concept that we try to match. + result = false var candidates = searchScopes(c, n[namePos].sym.name, kinds) searchImportsAll(c, n[namePos].sym.name, kinds, candidates) for candidate in candidates: #echo "considering ", typeToString(candidate.typ), " ", candidate.magic m.magic = candidate.magic - if matchSym(c, candidate, n, m): return true - result = false + if matchSym(c, candidate, n, m): + result = true + break + if not result: + # as a last resort we can assume that any inner concepts (not Self) are implemented + result = matchCodependentConcept(c, n, m) proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool = ## Traverse the concept's AST ('n') and see if every declaration inside 'n' ## can be matched with the current scope. - case n.kind - of nkStmtList, nkStmtListExpr: - for i in 0.. ptr object - A[N: static[int]] = array[N, int] ... A -> array - ]# - case f.kind: - of tyGenericParam: - if f.len <= 0 or f.skipModifier == nil: - result = f - else: - result = reduceToBase(f.skipModifier) - of tyGenericInvocation: - result = reduceToBase(f.baseClass) - of tyCompositeTypeClass, tyAlias: - if not f.hasElementType or f.elementType == nil: - result = f - else: - result = reduceToBase(f.elementType) - of tyGenericInst: - result = reduceToBase(f.skipModifier) - of tyGenericBody: - result = reduceToBase(f.typeBodyImpl) - of tyUserTypeClass: - if f.isResolvedUserTypeClass: - result = f.base # ?? idk if this is right - else: - result = f.skipModifier - of tyStatic, tyOwned, tyVar, tyLent, tySink: - result = reduceToBase(f.base) - of tyInferred: - # This is not true "After a candidate type is selected" - result = reduceToBase(f.base) - of tyRange: - result = f.elementType - else: - result = f - proc genericParamPut(c: var TCandidate; last, fGenericOrigin: PType) = if fGenericOrigin != nil and last.kind == tyGenericInst and last.kidsLen-1 == fGenericOrigin.kidsLen: diff --git a/compiler/types.nim b/compiler/types.nim index 18f90b75ac799..002c32f54a006 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -2038,3 +2038,42 @@ proc genericRoot*(t: PType): PType = result = t.sym.typ else: result = nil + +proc reduceToBase*(f: PType): PType = + #[ + Returns the lowest order (most general) type that that is compatible with the input. + E.g. + A[T] = ptr object ... A -> ptr object + A[N: static[int]] = array[N, int] ... A -> array + ]# + case f.kind: + of tyGenericParam: + if f.len <= 0 or f.skipModifier == nil: + result = f + else: + result = reduceToBase(f.skipModifier) + of tyGenericInvocation: + result = reduceToBase(f.baseClass) + of tyCompositeTypeClass, tyAlias: + if not f.hasElementType or f.elementType == nil: + result = f + else: + result = reduceToBase(f.elementType) + of tyGenericInst: + result = reduceToBase(f.skipModifier) + of tyGenericBody: + result = reduceToBase(f.typeBodyImpl) + of tyUserTypeClass: + if f.isResolvedUserTypeClass: + result = f.base + else: + result = f.skipModifier + of tyStatic, tyOwned, tyVar, tyLent, tySink: + result = reduceToBase(f.base) + of tyInferred: + # This is not true "After a candidate type is selected" + result = reduceToBase(f.base) + of tyRange: + result = f.elementType + else: + result = f diff --git a/lib/system.nim b/lib/system.nim index b2cf92ffb7572..ec9bfc28bd04c 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -48,6 +48,7 @@ type ## ## The coercion `type(x)` can be used to obtain the type of the given ## expression `x`. + TypeEach[T] = object type TypeOfMode* = enum ## Possible modes of `typeof`. @@ -76,6 +77,8 @@ proc typeof*(x: untyped; mode = typeOfIter): typedesc {. # since `typeOfProc` expects a typed expression and `myFoo2()` can # only be used in a `for` context. +template each*(a: untyped): untyped = TypeEach[a] + proc `or`*(a, b: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.} ## Constructs an `or` meta class. diff --git a/tests/concepts/conceptv2negative/tauto.nim b/tests/concepts/conceptv2negative/tauto.nim new file mode 100644 index 0000000000000..93de419d65c18 --- /dev/null +++ b/tests/concepts/conceptv2negative/tauto.nim @@ -0,0 +1,14 @@ +discard """ + outputsub: "type mismatch" + exitcode: "1" +""" +type + A = object + C1 = concept + proc p(s: Self, a: auto) + C1Impl = object + +proc p(x: C1Impl, a: A)= discard +proc spring(x: C1)= discard + +spring(C1Impl()) diff --git a/tests/concepts/conceptv2negative/tgenobj.nim b/tests/concepts/conceptv2negative/tgenobj.nim new file mode 100644 index 0000000000000..cf55a1c555377 --- /dev/null +++ b/tests/concepts/conceptv2negative/tgenobj.nim @@ -0,0 +1,14 @@ +discard """ + outputsub: "type mismatch" + exitcode: "1" +""" +type + A[T] = object + C1 = concept + proc p(s: Self, a: A) + C1Impl = object + +proc p(x: C1Impl, a: A[int])= discard +proc spring(x: C1)= discard + +spring(C1Impl()) diff --git a/tests/concepts/conceptv2negative/tnoimplicitbindingcontainer.nim b/tests/concepts/conceptv2negative/tnoimplicitbindingcontainer.nim new file mode 100644 index 0000000000000..6d936a28a1b59 --- /dev/null +++ b/tests/concepts/conceptv2negative/tnoimplicitbindingcontainer.nim @@ -0,0 +1,19 @@ +discard """ + outputsub: "type mismatch" + exitcode: "1" +""" +type + Sizeable = concept + proc size(s: Self): int + Buffer = concept + proc w(s: Self, data: Sizeable) + Serializable[T: Buffer] = concept + proc w(b: T, s: Self) + ArrayLike = concept + proc size(s: Self): int + ArrayImpl = object + +proc size(x: ArrayImpl): int= discard + +proc spring(data: Serializable)= discard +spring(ArrayImpl()) diff --git a/tests/concepts/conceptv2negative/tnoimplicitbindingeach.nim b/tests/concepts/conceptv2negative/tnoimplicitbindingeach.nim new file mode 100644 index 0000000000000..96a2255b730d7 --- /dev/null +++ b/tests/concepts/conceptv2negative/tnoimplicitbindingeach.nim @@ -0,0 +1,19 @@ +discard """ + outputsub: "type mismatch" + exitcode: "1" +""" +type + Sizeable = concept + proc size(s: Self): int + Buffer = concept + proc w(s: Self, data: Sizeable) + Serializable = concept + proc w(b: each Buffer, s: Self) + ArrayLike = concept + proc size(s: Self): int + ArrayImpl = object + +proc size(x: ArrayImpl): int= discard + +proc spring(data: Serializable)= discard +spring(ArrayImpl()) diff --git a/tests/concepts/conceptv2negative/tnottype.nim b/tests/concepts/conceptv2negative/tnottype.nim new file mode 100644 index 0000000000000..a06becdd0c5dd --- /dev/null +++ b/tests/concepts/conceptv2negative/tnottype.nim @@ -0,0 +1,17 @@ +discard """ + outputsub: "type mismatch" + exitcode: "1" +""" +type + Sizeable = concept + proc size(s: Self): int + Buffer = concept + proc w(s: Self, data: Sizeable) + Serializable = concept + proc w(b: each Buffer, s: Self) + ArrayImpl = object + +proc size(x: ArrayImpl): int= discard + +proc spring(data: Serializable)= discard +spring(ArrayImpl()) diff --git a/tests/concepts/conceptv2negative/tor.nim b/tests/concepts/conceptv2negative/tor.nim new file mode 100644 index 0000000000000..5f77910a43436 --- /dev/null +++ b/tests/concepts/conceptv2negative/tor.nim @@ -0,0 +1,13 @@ +discard """ + outputsub: "type mismatch" + exitcode: "1" +""" +type + C1 = concept + proc p(s: Self, a: int | float | string) + C1Impl = object + +proc p(x: C1Impl, a: int | float)= discard +proc spring(x: C1)= discard + +spring(C1Impl()) \ No newline at end of file diff --git a/tests/concepts/conceptv2negative/ttypedesc.nim b/tests/concepts/conceptv2negative/ttypedesc.nim new file mode 100644 index 0000000000000..ad3ef247a53a7 --- /dev/null +++ b/tests/concepts/conceptv2negative/ttypedesc.nim @@ -0,0 +1,13 @@ +discard """ + outputsub: "type mismatch" + exitcode: "1" +""" +type + C1 = concept + proc p(s: Self, a: typedesc) + C1Impl = object + +proc p(x: C1Impl, a: typedesc[SomeInteger])= discard +proc spring(x: C1)= discard + +spring(C1Impl()) \ No newline at end of file diff --git a/tests/concepts/tconceptsv2.nim b/tests/concepts/tconceptsv2.nim index f72f522ee6100..70966d1436a0f 100644 --- a/tests/concepts/tconceptsv2.nim +++ b/tests/concepts/tconceptsv2.nim @@ -103,7 +103,7 @@ block: # simple recursion WritableImpl = object proc launch(a: var Buffer, b: Writable)= discard - proc put(x: var BufferImpl, i: object)= discard + proc put[T](x: var BufferImpl, i: T)= discard proc second(x: BufferImpl)= discard proc put(x: var Buffer, y: WritableImpl)= discard @@ -121,10 +121,181 @@ block: # more complex recursion WritableImpl = object proc launch(a: var Buffer, b: Writable)= discard - proc put(x: var Buffer, i: object)= discard + proc put[T](x: var Buffer, i: T)= discard proc put(x: var BufferImpl, i: object)= discard proc second(x: BufferImpl)= discard proc put(x: var Buffer, y: WritableImpl)= discard var a = BufferImpl[5]() launch(a, WritableImpl()) + +block: # not type + type + C1 = concept + proc p(s: Self, a: int) + C1Impl = object + + proc p(x: C1Impl, a: not float)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # not type parameterized + type + C1[T: not int] = concept + proc p(s: Self, a: T) + C1Impl = object + + proc p(x: C1Impl, a: float)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # not type each + type + C1 = concept + proc p(s: Self, a: each typedesc[not int]) + C1Impl = object + + proc p(x: C1Impl, a: float)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # typedesc + type + C1 = concept + proc p(s: Self, a: typedesc[SomeInteger]) + C1Impl = object + + proc p(x: C1Impl, a: typedesc)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + + +block: # or + type + C1 = concept + proc p(s: Self, a: int | float) + C1Impl = object + + proc p(x: C1Impl, a: int | float | string)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # or mixed generic param + type + C1 = concept + proc p(s: Self, a: int | float) + C1Impl = object + + proc p[T: string | float](x: C1Impl, a: int | T)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # or parameterized + type + C1[T: int | float | string] = concept + proc p(s: Self, a: T) + C1Impl = object + + proc p(x: C1Impl, a: int | float)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # or each + type + C1 = concept + proc p(s: Self, a: each typedesc[int | float | string]) + C1Impl = object + + proc p(x: C1Impl, a: int | float)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # unconstrained param + type + A = object + C1[T] = concept + proc p(s: Self, a: T) + C1Impl = object + + proc p(x: C1Impl, a: A)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # unconstrained param sanity check + type + A = object + C1[T: auto] = concept + proc p(s: Self, a: T) + C1Impl = object + + proc p(x: C1Impl, a: A)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # parameterization via `each` + type + A = object + C1 = concept + proc p(s: Self, a: each auto) + C1Impl = object + + proc p(x: C1Impl, a: A)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # exact nested concept binding + #[ + prove ArrayImpl is serializable (spring) + prove Buffer is Buffer (w) + prove ArrayImpl is ArrayLike (w) + prove ArrayImpl is ArrayImpl (len) + ]# + type + Sizeable = concept + proc size(s: Self): int + Buffer = concept + proc w(s: Self, data: Sizeable) + Serializable = concept + proc w(b: Buffer, s: Self) + ArrayLike = concept + proc len(s: Self): int + ArrayImpl = object + + proc len(s: ArrayImpl): int = discard + proc w(x: Buffer, d: ArrayLike)=discard + + proc spring(data: Serializable)=discard + spring(ArrayImpl()) + +block: # co-dependent implicit + #[ + prove ArrayImpl is Serializable (spring) + fail to find a binding for Serializable.w + Serializable and Buffer are co-dependent, assume Buffer.w exists + prove ArrayImpl is Sizeable (Buffer.w) + prove ArrayImpl is ArrayImpl (len) + ]# + type + Sizeable = concept + proc size(s: Self): int + Buffer = concept + proc w(s: Self, data: Sizeable) + Serializable = concept + proc w(b: Buffer, s: Self) + ArrayImpl = object + + proc size(x: ArrayImpl): int= discard + + proc spring(data: Serializable)= discard + spring(ArrayImpl()) From 1d645280d657b93e73628b679f7ec81739473b0a Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Fri, 6 Dec 2024 00:59:42 -0500 Subject: [PATCH 2/3] stuff2 --- compiler/concepts.nim | 144 +++++++++++------- compiler/semtypinst.nim | 9 +- compiler/sigmatch.nim | 2 + compiler/types.nim | 11 ++ tests/concepts/conceptv2negative/tauto.nim | 3 +- tests/concepts/conceptv2negative/tgenobj.nim | 3 +- tests/concepts/conceptv2negative/tmarrget.nim | 12 ++ .../tnoimplicitbindingcontainer.nim | 3 +- .../tnoimplicitbindingeach.nim | 3 +- tests/concepts/conceptv2negative/tnottype.nim | 3 +- tests/concepts/conceptv2negative/tor.nim | 3 +- .../concepts/conceptv2negative/ttypedesc.nim | 3 +- tests/concepts/tconceptsv2.nim | 105 ++++++++++++- 13 files changed, 230 insertions(+), 74 deletions(-) create mode 100644 tests/concepts/conceptv2negative/tmarrget.nim diff --git a/compiler/concepts.nim b/compiler/concepts.nim index 31e685da5177c..d811da043d3c0 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -79,7 +79,9 @@ type ## cannot be fixed that easily. ## Thus we special case it here. concpt: PType ## current concept being evaluated - strict = true ## flag for `Self` and `each` and other scenareos were strict matching is off + genrcparam = true ## flag for `Self` concept generic parameters - this chenges matching rules + MatchKind = enum + mkNoMatch, mkSubset, mkSame, mkExact proc existingBinding(m: MatchCon; key: PType): PType = ## checks if we bound the type variable 'key' already to some @@ -96,7 +98,7 @@ proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool proc defSignatureType(n: PNode): PType = n[0].sym.typ -proc conceptBody(n: PType): PNode = n.n.lastSon +proc conceptBody*(n: PType): PNode = n.n.lastSon proc isEach(t: PType): bool = t.kind == tyGenericInvocation and t.genericHead.sym.name.s == "TypeEach" @@ -159,7 +161,7 @@ proc matchConceptToImpl(c: PContext, f, potentialImpl: PType; m: var MatchCon): m.inferred.setLen oldLen proc cmpConceptDefs(c: PContext, fn, an: PNode, m: var MatchCon): bool= - if an.kind notin {nkProcDef, nkFuncDef} and fn.kind != an.kind: + if fn.kind != an.kind: return false if fn[namePos].sym.name != an[namePos].sym.name: return false @@ -171,7 +173,14 @@ proc cmpConceptDefs(c: PContext, fn, an: PNode, m: var MatchCon): bool= for i in 1 ..< ft.n.len: let oldLen = m.inferred.len - if not matchType(c, ft.n[i].typ, at.n[i].typ, m): + + let aType = at.n[i].typ + let fType = ft.n[i].typ + + if aType.isSelf and fType.isSelf: + continue + + if not matchType(c, fType, aType, m): m.inferred.setLen oldLen return false result = true @@ -180,23 +189,28 @@ proc cmpConceptDefs(c: PContext, fn, an: PNode, m: var MatchCon): bool= m.inferred.setLen oldLen result = false -proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): bool= +proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): MatchKind = # XXX: In the future this may need extra parameters to carry info for container types - result = fc.n == ac.n - if result: - # This will have to take generic parameters into account at some point for container types - return + if fc.n == ac.n: + # This will have to take generic parameters into account at some point + return mkExact let fn = fc.conceptBody an = ac.conceptBody + sameLen = fc.len == ac.len + var ints = initIntSet() + var match = false for fdef in fn: - result = false - for ndef in an: - result = cmpConceptDefs(c, fdef, ndef, m) - if result: + var cmpResult = false + for ia, ndef in an: + match = cmpConceptDefs(c, fdef, ndef, m) + if match: + if sameLen: + ints.incl ia break - if not result: - break + if not match: + return mkNoMatch + return if sameLen and ints.len == fn.len: mkSame else: mkSubset proc matchImplicitDef(c: PContext; fn, an: PNode; aConpt: PNode, m: var MatchCon): bool= let @@ -208,26 +222,37 @@ proc matchImplicitDef(c: PContext; fn, an: PNode; aConpt: PNode, m: var MatchCon return false result = true - for i in 1 ..< ft.n.len: + for i in 0 ..< ft.n.len: var aType = at.n[i].typ + var fType = ft.n[i].typ + if aType == nil: + if fType == nil: + continue + else: + return false if aType.reduceToBase.isSelf: # Self in `an` is always legal here continue - var fType = ft.n[i].typ + echo aType + if fType.reduceToBase.isSelf: if fType.kind in {tyVar, tySink, tyLent, tyOwned} and aType.kind == fType.kind: aType = aType.elementType fType = m.potentialImplementation.skipTypes({tyVar, tySink, tyLent, tyOwned}) else: fType = m.potentialImplementation - if aType.kind == tyConcept and conceptsMatch(c, aType, m.concpt, m): - return true + if aType.kind == tyConcept: + return conceptsMatch(c, aType, m.concpt, m) >= mkSubset or matchType(c, aType, fType, m) let oldLen = m.inferred.len - if not matchType(c, aType, fType, m): + if i == 0: + if not matchReturnType(c, aType, fType, m): + m.inferred.setLen oldLen + return false + elif not matchType(c, aType, fType, m): m.inferred.setLen oldLen return false -proc matchCodependentConcept(c: PContext; n: PNode; m: var MatchCon): bool = +proc inferFromDependencies(c: PContext; n: PNode; m: var MatchCon): bool = result = false let sig = n.defSignatureType.n for i in 1 ..< sig.len: @@ -239,10 +264,12 @@ proc matchCodependentConcept(c: PContext; n: PNode; m: var MatchCon): bool = if paramType.reduceToBase.kind == tyConcept: for aDef in travStmts(paramType.reduceToBase.conceptBody): if n[namePos].sym.name == aDef[namePos].sym.name: - let oldStrict = m.strict - m.strict = false + let oldStrict = m.genrcparam + m.genrcparam = false result = matchImplicitDef(c, n, aDef, sig[i], m) - m.strict = oldStrict + m.genrcparam = oldStrict + if result: + return true proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = ## The heart of the concept matching process. 'f' is the formal parameter of some @@ -252,10 +279,10 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = ignorableForArgType = {tyVar, tySink, tyLent, tyOwned, tyGenericInst, tyAlias, tyInferred} template notStrict(body: untyped)= - let oldStrict = m.strict - m.strict = false + let oldStrict = m.genrcparam + m.genrcparam = false body - m.strict = oldStrict + m.genrcparam = oldStrict var a = ao @@ -264,32 +291,37 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = if isEach(f): notStrict: return matchType(c, f.last, a, m) + result = false case a.kind of tyGenericParam: - # I forget how bindings can end up here but they did once + # Binding table is shared between concept and definitions let binding = m.existingBinding(a) if binding != nil: a = binding + if a.acceptsAllTypes: + # have to be careful with these. Sometimes this is wrong I think + # Have to make sure same T for other generic params + return false of tyAnything: - if m.strict: + if m.genrcparam: return true of tyNot: - if m.strict: + if m.genrcparam: if f.kind == tyNot: return matchType(c, f.elementType, a.elementType, m) else: let oldLen = m.inferred.len result = not matchType(c, f, a.elementType, m) m.inferred.setLen oldLen - return + return result of tyCompositeTypeClass, tyGenericInst, tyConcept: let aBase = a.reduceToBase fBase = f.reduceToBase if aBase.kind == tyConcept: if fBase.kind == tyConcept: - return conceptsMatch(c, fBase, aBase, m) + return conceptsMatch(c, fBase, aBase, m) >= mkSubset else: return matchConceptToImpl(c, a, f, m) else: @@ -300,12 +332,14 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = result = matchType(c, f.skipModifier, a, m) of tyTypeDesc: if isSelf(f): - #let oldLen = m.inferred.len - notStrict: - result = matchType(c, a, m.potentialImplementation, m) - #echo "self is? ", result, " ", a.kind, " ", a, " ", m.potentialImplementation, " ", m.potentialImplementation.kind - #m.inferred.setLen oldLen - #echo "A for ", result, " to ", typeToString(a), " to ", typeToString(m.potentialImplementation) + if m.magic in {mArrPut, mArrGet}: + result = false + if m.potentialImplementation.reduceToBase.kind in arrPutGetMagicApplies: + m.inferred.add((a, last m.potentialImplementation)) + result = true + else: + notStrict: + result = matchType(c, a, m.potentialImplementation, m) else: result = false if a.kind == tyTypeDesc: @@ -314,7 +348,6 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = elif f.hasElementType: result = matchType(c, f.elementType, a.elementType, m) of tyGenericInvocation: - result = false if f.genericHead.elementType.kind == tyConcept: result = matchType(c, f.genericHead.elementType, a, m) elif a.kind == tyGenericInst and a.genericHead.kind == tyGenericBody: @@ -322,9 +355,7 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = result = matchKids(c, f, a, m, start=FirstGenericParamAt) of tyGenericParam: let ak = a.skipTypes({tyVar, tySink, tyLent, tyOwned}) - if ak.kind in {tyTypeDesc, tyStatic} and not isSelf(ak): - result = false - else: + if ak.kind notin {tyTypeDesc, tyStatic} and not isSelf(ak): let old = existingBinding(m, f) if old == nil: if f.hasElementType and f.elementType.kind != tyNone: @@ -344,7 +375,7 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = else: if tfImplicitTypeParam in f.flags: # this is another way of representing tyAnything? - result = not(m.strict) or a.acceptsAllTypes + result = not(m.genrcparam) or a.acceptsAllTypes else: when logBindings: echo "C adding ", f, " ", ak m.inferred.add((f, ak)) @@ -374,7 +405,7 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = of tyBool, tyChar, tyInt..tyUInt64: let ak = a.skipTypes(ignorableForArgType) result = ak.kind == f.kind or ak.kind == tyOrdinal or - (ak.kind == tyGenericParam and ak.hasElementType and ak.elementType.kind == tyOrdinal) + (ak.kind == tyGenericParam and ak.hasElementType and ak.elementType.kind == tyOrdinal) of tyGenericBody: var ak = a if a.kind == tyGenericBody: @@ -386,7 +417,7 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = else: result = matchType(c, last(f), a, m) of tyArray, tyTuple, tyVarargs, tyOpenArray, tyRange, tySequence, tyRef, tyPtr, - tyGenericInst: + tyGenericInst: # ^ XXX Rewrite this logic, it's more complex than it needs to be. if f.kind == tyArray and f.kidsLen == 3 and a.kind == tyArray: # XXX: this is a work-around! @@ -398,8 +429,7 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = if ak.kind == f.kind and f.kidsLen == ak.kidsLen: result = matchKids(c, f, ak, m) of tyOr: - result = false - if m.strict: + if m.genrcparam: let oldLen = m.inferred.len if a.kind in {tyOr, tyGenericParam}: result = true @@ -439,8 +469,7 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = if result: break # and remember the binding! m.inferred.setLen oldLen of tyNot: - result = false - if not m.strict: + if not m.genrcparam: if a.kind == tyNot: result = matchType(c, f.elementType, a.elementType, m) else: @@ -448,18 +477,16 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = result = not matchType(c, f.elementType, a, m) m.inferred.setLen oldLen of tyConcept: - # TODO: conceptsMatch's current logic is wrong rn, I think. fix with a test - result = a.kind == tyConcept and conceptsMatch(c, f, a, m) - if not (result or m.strict): + result = a.kind == tyConcept and f.n == a.n + if not (result or m.genrcparam): # this is for `each` parameters and generic parameters. Can search for a candidate iff # the concept (f) is a constraint and not a requirement result = matchConceptToImpl(c, f, a, m) of tyOrdinal: result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam of tyAnything: - result = not(m.strict) or a.acceptsAllTypes + result = not(m.genrcparam) or a.acceptsAllTypes of tyStatic: - result = false var scomp = f.base if scomp.kind == tyGenericParam: if f.base.kidsLen > 0: @@ -470,6 +497,8 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = result = matchType(c, scomp, a, m) else: result = false + if result and a.kind == tyGenericParam: + m.inferred.add((a, f)) proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool = ## Like 'matchType' but with extra logic dealing with proc return types @@ -529,7 +558,7 @@ proc matchSyms(c: PContext, n: PNode; kinds: set[TSymKind]; m: var MatchCon): bo break if not result: # as a last resort we can assume that any inner concepts (not Self) are implemented - result = matchCodependentConcept(c, n, m) + result = inferFromDependencies(c, n, m) proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool = ## Traverse the concept's AST ('n') and see if every declaration inside 'n' @@ -570,6 +599,11 @@ proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable ## for 'S' and 'T' inside 'bindings' on a successful match. It is very important that ## we do not add any bindings at all on an unsuccessful match! var m = MatchCon(inferred: @[], potentialImplementation: arg, concpt: concpt) + if arg.isConcept: + return conceptsMatch(c, concpt.reduceToBase, arg.reduceToBase, m) >= mkSubset + elif arg.containsUnresolvedType: + # XXX: I think this is wrong, or at least partially wrong. Can still test ambiguous types + return false result = conceptMatchNode(c, concpt.conceptBody, m) if result: for (a, b) in m.inferred: diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 217488cb8d091..720072a1ea814 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -608,10 +608,13 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false): result = t if t == nil: return + var et = t + if t.isConcept: + et = t.reduceToBase const lookupMetas = {tyStatic, tyGenericParam, tyConcept} + tyTypeClasses - {tyAnything} - if t.kind in lookupMetas or - (t.kind == tyAnything and tfRetType notin t.flags): - let lookup = cl.typeMap.lookup(t) + if et.kind in lookupMetas or + (et.kind == tyAnything and tfRetType notin et.flags): + let lookup = cl.typeMap.lookup(et) if lookup != nil: return lookup case t.kind diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 01eed9df4d73e..cccee65772263 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -363,6 +363,8 @@ proc sumGeneric(t: PType): int = result += sumGeneric(a) break else: + if t.isConcept: + result += t.reduceToBase.conceptBody.len break proc complexDisambiguation(a, b: PType): int = diff --git a/compiler/types.nim b/compiler/types.nim index 002c32f54a006..61d35ff527f44 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -102,6 +102,9 @@ const # typedescX is used if we're sure tyTypeDesc should be included (or skipped) typedescPtrs* = abstractPtrs + {tyTypeDesc} typedescInst* = abstractInst + {tyTypeDesc, tyOwned, tyUserTypeClass} + + # incorrect definition of `[]` and `[]=` for these types in + arrPutGetMagicApplies* = {tyArray, tyOpenArray, tyString, tySequence, tyCstring, tyTuple} proc invalidGenericInst*(f: PType): bool = result = f.kind == tyGenericInst and skipModifier(f) == nil @@ -2077,3 +2080,11 @@ proc reduceToBase*(f: PType): PType = result = f.elementType else: result = f + +proc isConcept*(t: PType): bool= + if t.kind == tyConcept: + true + elif t.kind in {tyGenericInst, tyCompositeTypeClass, tyGenericInvocation}: + t.reduceToBase.kind == tyConcept + else: + false diff --git a/tests/concepts/conceptv2negative/tauto.nim b/tests/concepts/conceptv2negative/tauto.nim index 93de419d65c18..a39a3750e5c21 100644 --- a/tests/concepts/conceptv2negative/tauto.nim +++ b/tests/concepts/conceptv2negative/tauto.nim @@ -1,6 +1,5 @@ discard """ - outputsub: "type mismatch" - exitcode: "1" + action: "reject" """ type A = object diff --git a/tests/concepts/conceptv2negative/tgenobj.nim b/tests/concepts/conceptv2negative/tgenobj.nim index cf55a1c555377..cec883daf6677 100644 --- a/tests/concepts/conceptv2negative/tgenobj.nim +++ b/tests/concepts/conceptv2negative/tgenobj.nim @@ -1,6 +1,5 @@ discard """ - outputsub: "type mismatch" - exitcode: "1" +action: "reject" """ type A[T] = object diff --git a/tests/concepts/conceptv2negative/tmarrget.nim b/tests/concepts/conceptv2negative/tmarrget.nim new file mode 100644 index 0000000000000..64d6b8c91a646 --- /dev/null +++ b/tests/concepts/conceptv2negative/tmarrget.nim @@ -0,0 +1,12 @@ +discard """ +action: "reject" +""" + +# stop mArrGet magic from giving everything `[]` +type + C[T] = concept + proc `[]`(b: Self, i: int): T + A = object + +proc p(a: C): int = assert false +discard p(A()) diff --git a/tests/concepts/conceptv2negative/tnoimplicitbindingcontainer.nim b/tests/concepts/conceptv2negative/tnoimplicitbindingcontainer.nim index 6d936a28a1b59..248f705ba232a 100644 --- a/tests/concepts/conceptv2negative/tnoimplicitbindingcontainer.nim +++ b/tests/concepts/conceptv2negative/tnoimplicitbindingcontainer.nim @@ -1,6 +1,5 @@ discard """ - outputsub: "type mismatch" - exitcode: "1" +action: "reject" """ type Sizeable = concept diff --git a/tests/concepts/conceptv2negative/tnoimplicitbindingeach.nim b/tests/concepts/conceptv2negative/tnoimplicitbindingeach.nim index 96a2255b730d7..a0b4d6de6a9b2 100644 --- a/tests/concepts/conceptv2negative/tnoimplicitbindingeach.nim +++ b/tests/concepts/conceptv2negative/tnoimplicitbindingeach.nim @@ -1,6 +1,5 @@ discard """ - outputsub: "type mismatch" - exitcode: "1" +action: "reject" """ type Sizeable = concept diff --git a/tests/concepts/conceptv2negative/tnottype.nim b/tests/concepts/conceptv2negative/tnottype.nim index a06becdd0c5dd..b239f327a1970 100644 --- a/tests/concepts/conceptv2negative/tnottype.nim +++ b/tests/concepts/conceptv2negative/tnottype.nim @@ -1,6 +1,5 @@ discard """ - outputsub: "type mismatch" - exitcode: "1" +action: "reject" """ type Sizeable = concept diff --git a/tests/concepts/conceptv2negative/tor.nim b/tests/concepts/conceptv2negative/tor.nim index 5f77910a43436..6d9fa3a99407a 100644 --- a/tests/concepts/conceptv2negative/tor.nim +++ b/tests/concepts/conceptv2negative/tor.nim @@ -1,6 +1,5 @@ discard """ - outputsub: "type mismatch" - exitcode: "1" +action: "reject" """ type C1 = concept diff --git a/tests/concepts/conceptv2negative/ttypedesc.nim b/tests/concepts/conceptv2negative/ttypedesc.nim index ad3ef247a53a7..6f14a29a204ef 100644 --- a/tests/concepts/conceptv2negative/ttypedesc.nim +++ b/tests/concepts/conceptv2negative/ttypedesc.nim @@ -1,6 +1,5 @@ discard """ - outputsub: "type mismatch" - exitcode: "1" +action: "reject" """ type C1 = concept diff --git a/tests/concepts/tconceptsv2.nim b/tests/concepts/tconceptsv2.nim index 70966d1436a0f..7e67ee98a3c1d 100644 --- a/tests/concepts/tconceptsv2.nim +++ b/tests/concepts/tconceptsv2.nim @@ -5,6 +5,13 @@ B[system.int] A[system.string] A[array[0..0, int]] A[seq[int]] +100 +a +b +c +a +b +c ''' """ import conceptsv2_helper @@ -129,6 +136,100 @@ block: # more complex recursion var a = BufferImpl[5]() launch(a, WritableImpl()) +block: # co-dependent concepts + type + Writable = concept + proc w(b: var Buffer; s: Self): int + Buffer = concept + proc w(s: var Self; data: Writable): int + SizedWritable = concept + proc size(x: Self): int + proc w(b: var Buffer, x: Self): int + BufferImpl = object + + proc w(x: var BufferImpl, d: int): int = return 100 + proc size(d: int): int = sizeof(int) + + proc p(b: var Buffer, data: SizedWritable): int = + b.w(data) + + var b = BufferImpl() + echo p(b, 5) + +block: # instantiate even when generic params are the same + type + ArrayLike[T] = concept + proc len(x: Self): int + proc `[]`(b: Self, i: int): T + proc p[T](x: ArrayLike[T])= + for k in x: + echo k + # For this test to work the second call's instantiation has to be incompatible with the first on the back end + p(['a','b','c']) + p("abc") + +block: # reject improper generic variables in candidates + type + ArrayLike[T] = concept + proc len(x: Self): int + proc g(b: Self, i: int): T + FreakString = concept + proc len(x: Self): int + proc characterSize(s: Self): int + A = object + + proc g[T, H](s: T, i: H): H = default(T) + proc len(s: A): int = discard + proc characterSize(s: A): int = discard + + proc p(symbol: ArrayLike[char]): int = assert false + proc p(symbol: FreakString): int=discard + + discard p(A()) + +block: # typerel disambiguation by concept subset + type + ArrayLike[T] = concept + proc len(x: Self): int + proc characterSize(s: Self): int + FreakString = concept + proc len(x: Self): int + proc characterSize(s: Self): int + proc tieBreaker(s: Self, j: int): float + A = object + + proc len(s: A): int = discard + proc characterSize(s: A): int = discard + proc tieBreaker(s: A, h: int):float = 0.0 + + proc p(symbol: ArrayLike[char]): int = assert false + proc p(symbol: FreakString): int=discard + + discard p(A()) + +block: # tie break via sumGeneric + type + C1 = concept + proc p1(x: Self, b: int) + proc p2(x: Self, b: float) + proc p3(x: Self, b: string) + C2 = concept + proc b1(x: Self, b: int) + proc b2(x: Self, b: float) + A = object + + proc p1(x: A, b: int)=discard + proc p2(x: A, b: float)=discard + proc p3(x: A, b: string)=discard + + proc b1(x: A, b: int)=discard + proc b2(x: A, b: float)=discard + + proc p(symbol: C1): int = discard + proc p(symbol: C2): int = assert false + + discard p(A()) + block: # not type type C1 = concept @@ -278,11 +379,11 @@ block: # exact nested concept binding proc spring(data: Serializable)=discard spring(ArrayImpl()) -block: # co-dependent implicit +block: # implicit via dependency #[ prove ArrayImpl is Serializable (spring) fail to find a binding for Serializable.w - Serializable and Buffer are co-dependent, assume Buffer.w exists + Serializable is dependent on Buffer, assume Buffer.w exists prove ArrayImpl is Sizeable (Buffer.w) prove ArrayImpl is ArrayImpl (len) ]# From ad0fe49084fb35a2fcffa9e9bdabd719df025e29 Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Fri, 6 Dec 2024 01:01:47 -0500 Subject: [PATCH 3/3] remove comment --- compiler/concepts.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/concepts.nim b/compiler/concepts.nim index d811da043d3c0..79a4a416d7d8d 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -233,7 +233,6 @@ proc matchImplicitDef(c: PContext; fn, an: PNode; aConpt: PNode, m: var MatchCon if aType.reduceToBase.isSelf: # Self in `an` is always legal here continue - echo aType if fType.reduceToBase.isSelf: if fType.kind in {tyVar, tySink, tyLent, tyOwned} and aType.kind == fType.kind: