mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-01 19:33:42 +00:00
fix 26147; new-style concepts: broken generic (Case B) (#26151)
ref #26147
(cherry picked from commit 0be9b4f3f6)
This commit is contained in:
committed by
narimiran
parent
cf683229e9
commit
c14d78eb56
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
75
tests/concepts/t26147.nim
Normal file
75
tests/concepts/t26147.nim
Normal file
@@ -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))
|
||||
Reference in New Issue
Block a user