diff --git a/compiler/concepts.nim b/compiler/concepts.nim index 7ae138f86f..57dca3abfc 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -11,7 +11,8 @@ ## for details. Note this is a first implementation and only the "Concept matching" ## section has been implemented. -import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable +import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types, + layeredtable, semtypinst import std/sets @@ -71,7 +72,8 @@ proc semConceptDeclaration*(c: PContext; n: PNode): PNode = type MatchFlags* = enum - mfDontBind # Do not bind generic parameters + mfDontBind # Do not export bindings from the concept match + mfBindGenericParam # Export inferred invocation parameters despite mfDontBind mfCheckGeneric # formal <- formal comparison as opposed to formal <- operand ConceptTypePair = tuple[conceptId, typeId: ItemId] @@ -578,7 +580,17 @@ proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool = # error was reported earlier. result = false -proc fixBindings(bindings: var LayeredIdTable; concpt: PType; invocation: PType; m: var MatchCon) = +proc resolvedBinding(c: PContext; t: PType; m: MatchCon): PType = + ## An inferred concept parameter can refer to an implementation-local + ## generic parameter, for example `Elem[Impl.T]`. Resolve it while the + ## matcher's private bindings (`Impl.T -> int`) are still available. + if t.containsUnresolvedType: + prepareMetatypeForSigmatch(c, m.bindings, m.concpt.sym.info, t) + else: + t + +proc fixBindings(c: PContext; bindings: var LayeredIdTable; concpt: PType; + invocation: PType; m: var MatchCon) = # invocation != nil means we have a non-atomic concept: if invocation != nil and invocation.kind == tyGenericInvocation: assert concpt.sym.typ.kind == tyGenericBody @@ -590,8 +602,9 @@ proc fixBindings(bindings: var LayeredIdTable; concpt: PType; invocation: PType; continue let found = m.bindings.lookup(thisSym) if found != nil: - when logBindings: echo "Invocation bind: ", thisSym, " ", found - bindings.put(thisSym, found) + let resolved = resolvedBinding(c, found, m) + when logBindings: echo "Invocation bind: ", thisSym, " ", resolved + bindings.put(thisSym, resolved) # bind even more generic parameters let genBody = invocation.base @@ -607,6 +620,20 @@ proc fixBindings(bindings: var LayeredIdTable; concpt: PType; invocation: PType; bindings.put(invocation[i], boundV) bindings.put(concpt, m.potentialImplementation) +proc fixConstraintBindings(c: PContext; bindings: var LayeredIdTable; + invocation: PType; m: MatchCon) = + ## Propagates only the dependent parameters of a concept constraint. The + ## concept itself and its private matcher bindings must remain unbound so + ## that independent constraints using the same concept don't get coupled. + if invocation != nil and invocation.kind == tyGenericInvocation: + let genBody = invocation.base + assert genBody.kind == tyGenericBody + for i in FirstGenericParamAt ..< invocation.kidsLen: + if lookup(bindings, invocation[i]) == nil: + let boundValue = m.bindings.lookup(genBody[i - 1]) + if boundValue != nil: + bindings.put(invocation[i], resolvedBinding(c, boundValue, m)) + proc processConcept(c: PContext; concpt, invocation: PType, bindings: var LayeredIdTable; m: var MatchCon): bool = m.bindings = m.bindings.newTypeMapLayer() if invocation != nil and invocation.kind == tyGenericInst: @@ -616,8 +643,11 @@ proc processConcept(c: PContext; concpt, invocation: PType, bindings: var Layere if invocation[i].kind != tyVoid: bindParam(c, m, genericBody[i-1], invocation[i]) result = conceptMatchNode(c, concpt.conceptBody, m) - if result and mfDontBind notin m.flags: - fixBindings(bindings, concpt, invocation, m) + if result: + if mfDontBind notin m.flags: + fixBindings(c, bindings, concpt, invocation, m) + elif mfBindGenericParam in m.flags: + fixConstraintBindings(c, bindings, invocation, m) proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable; invocation: PType, flags: set[MatchFlags] = {}): bool = ## Entry point from sigmatch. 'concpt' is the concept we try to match (here still a PType but diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index dec1be7608..ecaf3aa474 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1175,6 +1175,8 @@ proc enterConceptMatch(c: var TCandidate; f,a: PType, flags: TTypeRelFlags): TTy return typeRel(c, prev, a, flags) if trDontBind in flags: conceptFlags.incl mfDontBind + if trBindGenericParam in flags: + conceptFlags.incl mfBindGenericParam if trCheckGeneric in flags: conceptFlags.incl mfCheckGeneric let mres = concepts.conceptMatch(c.c, concpt, a, c.bindings, container, flags = conceptFlags) diff --git a/tests/concepts/t26147.nim b/tests/concepts/t26147.nim new file mode 100644 index 0000000000..77eef87691 --- /dev/null +++ b/tests/concepts/t26147.nim @@ -0,0 +1,75 @@ +discard """ + action: run +""" + +type Indexable[T] = concept + proc `[]`(a: Self; index: int): T + proc len(a: Self): int + +iterator items[T; I: Indexable[T]](indexable: I): T = + for index in 0 ..< indexable.len: + yield indexable[index] + +type Dummy[T] = distinct seq[T] + +proc `[]`[T](d: Dummy[T], i: int): T = seq[T](d)[i] +proc len[T](d: Dummy[T]): int = seq[T](d).len + +var acc = 0 +for x in Dummy(@[1, 2, 3]): + acc += x +doAssert acc == 6 + +# Inferred concept parameters are resolved through the implementation's own +# generic bindings before being exported to the surrounding routine. +type + Elem[T] = object + value: T + NestedDummy[T] = ref object + data: seq[T] + +proc `[]`[T](d: NestedDummy[T], i: int): Elem[T] = + Elem[T](value: d.data[i]) +proc len[T](d: NestedDummy[T]): int = d.data.len + +iterator directItems[T](indexable: Indexable[T]): T = + for index in 0 ..< indexable.len: + yield indexable[index] + +var nestedAcc = 0 +for x in NestedDummy[int](data: @[4, 5, 6]): + nestedAcc += x.value +doAssert nestedAcc == 15 + +var directNestedAcc = 0 +for x in directItems(NestedDummy[int](data: @[7, 8, 9])): + directNestedAcc += x.value +doAssert directNestedAcc == 24 + +# All dependent parameters inferred while checking a concept constraint must +# be propagated to the constrained routine. +type + KeyValue[K, V] = concept + proc key(x: Self): K + proc value(x: Self): V + Pair[K, V] = object + k: K + v: V + +proc key[K, V](x: Pair[K, V]): K = x.k +proc value[K, V](x: Pair[K, V]): V = x.v + +proc unpack[K, V; P: KeyValue[K, V]](x: P): (K, V) = + (x.key, x.value) + +let pair = Pair[int, string](k: 7, v: "seven") +doAssert unpack(pair) == (7, "seven") +doAssert not compiles(unpack[string, int](pair)) + +proc unpackBoth[K1, V1, K2, V2; + P1: KeyValue[K1, V1]; P2: KeyValue[K2, V2]]( + x: P1; y: P2): ((K1, V1), (K2, V2)) = + (unpack(x), unpack(y)) + +let otherPair = Pair[string, float](k: "eight", v: 8.0) +doAssert unpackBoth(pair, otherPair) == ((7, "seven"), ("eight", 8.0))