Skip to content

Commit

Permalink
Param match relax (#23033)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Nikolay Nikolov <nickysn@gmail.com>
Co-authored-by: Pylgos <43234674+Pylgos@users.noreply.github.com>
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>
Co-authored-by: Jason Beetham <beefers331@gmail.com>
(cherry picked from commit 94f7e96)
  • Loading branch information
Graveflo authored and narimiran committed Aug 31, 2024
1 parent 352c82a commit d1aa568
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 68 deletions.
6 changes: 4 additions & 2 deletions compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1920,13 +1920,15 @@ proc skipGenericOwner*(s: PSym): PSym =
## Generic instantiations are owned by their originating generic
## symbol. This proc skips such owners and goes straight to the owner
## of the generic itself (the module or the enclosing proc).
result = if s.kind in skProcKinds and sfFromGeneric in s.flags:
result = if s.kind == skModule:
s
elif s.kind in skProcKinds and sfFromGeneric in s.flags and s.owner.kind != skModule:
s.owner.owner
else:
s.owner

proc originatingModule*(s: PSym): PSym =
result = s.owner
result = s
while result.kind != skModule: result = result.owner

proc isRoutine*(s: PSym): bool {.inline.} =
Expand Down
12 changes: 12 additions & 0 deletions compiler/lookups.nim
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,18 @@ proc searchInScopesFilterBy*(c: PContext, s: PIdent, filter: TSymKinds): seq[PSy
if s.kind in filter:
result.add s

proc cmpScopes*(ctx: PContext, s: PSym): int =
# Do not return a negative number
if s.originatingModule == ctx.module:
result = 2
var owner = s
while true:
owner = owner.skipGenericOwner
if owner.kind == skModule: break
inc result
else:
result = 1

proc isAmbiguous*(c: PContext, s: PIdent, filter: TSymKinds, sym: var PSym): bool =
result = false
block outer:
Expand Down
137 changes: 75 additions & 62 deletions compiler/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,7 @@ proc initCandidate*(ctx: PContext, c: var TCandidate, callee: PSym,
initCandidateAux(ctx, c, callee.typ)
c.calleeSym = callee
if callee.kind in skProcKinds and calleeScope == -1:
if callee.originatingModule == ctx.module:
c.calleeScope = 2
var owner = callee
while true:
owner = owner.skipGenericOwner
if owner.kind == skModule: break
inc c.calleeScope
else:
c.calleeScope = 1
c.calleeScope = cmpScopes(ctx, callee)
else:
c.calleeScope = calleeScope
c.diagnostics = @[] # if diagnosticsEnabled: @[] else: nil
Expand Down Expand Up @@ -290,7 +282,7 @@ proc writeMatches*(c: TCandidate) =
echo " conv matches: ", c.convMatches
echo " inheritance: ", c.inheritancePenalty

proc cmpCandidates*(a, b: TCandidate): int =
proc cmpCandidates*(a, b: TCandidate, isFormal=true): int =
result = a.exactMatches - b.exactMatches
if result != 0: return
result = a.genericMatches - b.genericMatches
Expand All @@ -304,13 +296,14 @@ proc cmpCandidates*(a, b: TCandidate): int =
# the other way round because of other semantics:
result = b.inheritancePenalty - a.inheritancePenalty
if result != 0: return
# check for generic subclass relation
result = checkGeneric(a, b)
if isFormal:
# check for generic subclass relation
result = checkGeneric(a, b)
if result != 0: return
# prefer more specialized generic over more general generic:
result = complexDisambiguation(a.callee, b.callee)
if result != 0: return
# prefer more specialized generic over more general generic:
result = complexDisambiguation(a.callee, b.callee)
# only as a last resort, consider scoping:
if result != 0: return
result = a.calleeScope - b.calleeScope

proc argTypeToString(arg: PNode; prefer: TPreferedDesc): string =
Expand Down Expand Up @@ -2346,56 +2339,76 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType,
if arg == nil or arg.kind notin nkSymChoices:
result = paramTypesMatchAux(m, f, a, arg, argOrig)
else:
# CAUTION: The order depends on the used hashing scheme. Thus it is
# incorrect to simply use the first fitting match. However, to implement
# this correctly is inefficient. We have to copy `m` here to be able to
# roll back the side effects of the unification algorithm.
let c = m.c
var
x = newCandidate(c, m.callee)
y = newCandidate(c, m.callee)
z = newCandidate(c, m.callee)
x.calleeSym = m.calleeSym
y.calleeSym = m.calleeSym
z.calleeSym = m.calleeSym
let matchSet = {skProc, skFunc, skMethod, skConverter,skIterator, skMacro,
skTemplate, skEnumField}

var best = -1
for i in 0..<arg.len:
if arg[i].sym.kind in {skProc, skFunc, skMethod, skConverter,
skIterator, skMacro, skTemplate, skEnumField}:
copyCandidate(z, m)
z.callee = arg[i].typ
if tfUnresolved in z.callee.flags: continue
z.calleeSym = arg[i].sym
# XXX this is still all wrong: (T, T) should be 2 generic matches
# and (int, int) 2 exact matches, etc. Essentially you cannot call
# typeRel here and expect things to work!
let r = typeRel(z, f, arg[i].typ)
incMatches(z, r, 2)
if r != isNone:
z.state = csMatch
case x.state
of csEmpty, csNoMatch:
x = z
best = i
of csMatch:
let cmp = cmpCandidates(x, z)
if cmp < 0:
best = i
x = z
elif cmp == 0:
y = z # z is as good as x
result = arg

if x.state == csEmpty:
result = nil
elif y.state == csMatch and cmpCandidates(x, y) == 0:
if x.state != csMatch:
internalError(m.c.graph.config, arg.info, "x.state is not csMatch")
# ambiguous: more than one symbol fits!
# See tsymchoice_for_expr as an example. 'f.kind == tyUntyped' should match
# anyway:
if f.kind in {tyUntyped, tyTyped}: result = arg
else: result = nil
if f.kind in {tyTyped, tyUntyped}:
var
bestScope = -1
counts = 0
for i in 0..<arg.len:
if arg[i].sym.kind in matchSet:
let thisScope = cmpScopes(m.c, arg[i].sym)
if thisScope > bestScope:
best = i
bestScope = thisScope
counts = 0
elif thisScope == bestScope:
inc counts
if best == -1:
result = nil
elif counts > 0:
best = -1
else:
# CAUTION: The order depends on the used hashing scheme. Thus it is
# incorrect to simply use the first fitting match. However, to implement
# this correctly is inefficient. We have to copy `m` here to be able to
# roll back the side effects of the unification algorithm.
let c = m.c
var
x = newCandidate(c, m.callee)
y = newCandidate(c, m.callee)
z = newCandidate(c, m.callee)
x.calleeSym = m.calleeSym
y.calleeSym = m.calleeSym
z.calleeSym = m.calleeSym

for i in 0..<arg.len:
if arg[i].sym.kind in matchSet:
copyCandidate(z, m)
z.callee = arg[i].typ
if tfUnresolved in z.callee.flags: continue
z.calleeSym = arg[i].sym
z.calleeScope = cmpScopes(m.c, arg[i].sym)
# XXX this is still all wrong: (T, T) should be 2 generic matches
# and (int, int) 2 exact matches, etc. Essentially you cannot call
# typeRel here and expect things to work!
let r = typeRel(z, f, arg[i].typ)
incMatches(z, r, 2)
if r != isNone:
z.state = csMatch
case x.state
of csEmpty, csNoMatch:
x = z
best = i
of csMatch:
let cmp = cmpCandidates(x, z, isFormal=false)
if cmp < 0:
best = i
x = z
elif cmp == 0:
y = z # z is as good as x

if x.state == csEmpty:
result = nil
elif y.state == csMatch and cmpCandidates(x, y, isFormal=false) == 0:
if x.state != csMatch:
internalError(m.c.graph.config, arg.info, "x.state is not csMatch")
result = nil
if best > -1 and result != nil:
# only one valid interpretation found:
markUsed(m.c, arg.info, arg[best].sym)
onUse(arg.info, arg[best].sym)
Expand Down
5 changes: 3 additions & 2 deletions lib/pure/sugar.nim
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,10 @@ proc collectImpl(init, body: NimNode): NimNode {.since: (1, 1).} =
let res = genSym(nskVar, "collectResult")
var bracketExpr: NimNode
if init != nil:
expectKind init, {nnkCall, nnkIdent, nnkSym, nnkOpenSym}
expectKind init, {nnkCall, nnkIdent, nnkSym, nnkClosedSymChoice, nnkOpenSymChoice}
bracketExpr = newTree(nnkBracketExpr,
if init.kind == nnkCall: freshIdentNodes(init[0]) else: freshIdentNodes(init))
if init.kind in {nnkCall, nnkClosedSymChoice, nnkOpenSymChoice}:
freshIdentNodes(init[0]) else: freshIdentNodes(init))
else:
bracketExpr = newTree(nnkBracketExpr)
let (resBody, keyType, valueType) = trans(body, res, bracketExpr)
Expand Down
2 changes: 2 additions & 0 deletions tests/lookups/issue_23032/deep_scope.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
type A*[T] = object
proc foo*(a: A[int]): bool = false
13 changes: 13 additions & 0 deletions tests/lookups/t23032.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
discard """
action: "run"
outputsub: "proc (a: A[system.float]): bool{.noSideEffect, gcsafe.}"
"""

import issue_23032/deep_scope

proc foo(a: A[float]):bool = true

let p: proc = foo
echo p.typeof
doAssert p(A[float]()) == true
doAssert compiles(doAssert p(A[int]()) == true) == false
19 changes: 19 additions & 0 deletions tests/macros/t23032_1.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import std/macros

type A[T, H] = object

proc `%*`(a: A): bool = true
proc `%*`[T](a: A[int, T]): bool = false

macro collapse(s: untyped) =
result = newStmtList()
result.add quote do:
doAssert(`s`(A[float, int]()) == true)

macro startHere(n: untyped): untyped =
result = newStmtList()
let s = n[0]
result.add quote do:
`s`.collapse()

startHere(`a` %* `b`)
20 changes: 20 additions & 0 deletions tests/macros/t23032_2.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
discard """
action: "reject"
errormsg: "ambiguous identifier '%*'"
"""
import std/macros

type A[T, H] = object

proc `%*`[T](a: A) = discard
proc `%*`[T](a: A[int, T]) = discard

macro collapse(s: typed) = discard

macro startHere(n: untyped): untyped =
result = newStmtList()
let s = n[0]
result.add quote do:
collapse(`s`.typeof())

startHere(`a` %* `b`)
7 changes: 5 additions & 2 deletions tests/macros/tgetimpl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ static:
doAssert checkOwner(poo, 2) == "nskProc"
doAssert checkOwner(poo, 3) == "nskModule"
doAssert isSameOwner(foo, poo)
doAssert isSameOwner(foo, echo) == false
doAssert isSameOwner(poo, len) == false
proc wrappedScope() =
proc dummyproc() = discard
doAssert isSameOwner(foo, dummyproc) == false
doAssert isSameOwner(poo, dummyproc) == false
wrappedScope()

#---------------------------------------------------------------

Expand Down

0 comments on commit d1aa568

Please sign in to comment.