summary refs log tree commit diff stats
path: root/nimdoc/tester.nim
blob: 0c0be369998f10b1a8a4a73b81f64ded79e44c50 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# Small program that runs the test cases for 'nim doc'.
# To run this, cd to the git repo root, and run "nim r nimdoc/tester.nim".
# to change expected results (after carefully verifying everything), use -d:nimTestsNimdocFixup

import strutils, os
from std/private/gitutils import diffFiles

const fixup = defined(nimTestsNimdocFixup)

var
  failures = 0

const
  baseDir = "nimdoc"
let
  baseDirAbs = getCurrentDir() / baseDir

type
  NimSwitches = object
    doc: seq[string]
    docStage2: seq[string]
    buildIndex: seq[string]
    md2html: seq[string]
    md2htmlStage2: seq[string]

proc exec(cmd: string) =
  if execShellCmd(cmd) != 0:
    quit("FAILURE: " & cmd)

proc testNimDoc(prjDir, docsDir: string; switches: NimSwitches; fixup = false) =
  let
    nimDocSwitches = switches.doc.join(" ")
    nimDocStage2Switches = switches.docStage2.join(" ")
    nimMd2HtmlSwitches = switches.md2html.join(" ")
    nimMd2HtmlStage2Switches = switches.md2htmlStage2.join(" ")
    nimBuildIndexSwitches = switches.buildIndex.join(" ")

  putEnv("SOURCE_DATE_EPOCH", "100000")
  const nimExe = getCurrentCompilerExe() # so that `bin/nim_temp r nimdoc/tester.nim` works

  if nimDocSwitches != "":
    exec("$1 doc $2" % [nimExe, nimDocSwitches])
    echo("$1 doc $2" % [nimExe, nimDocSwitches])

  if nimMd2HtmlSwitches != "":
    exec("$1 md2html $2" % [nimExe, nimMd2HtmlSwitches])
    echo("$1 md2html $2" % [nimExe, nimMd2HtmlSwitches])

  if nimDocStage2Switches != "":
    exec("$1 doc $2" % [nimExe, nimDocStage2Switches])
    echo("$1 doc $2" % [nimExe, nimDocStage2Switches])

  if nimMd2HtmlStage2Switches != "":
    exec("$1 md2html $2" % [nimExe, nimMd2HtmlStage2Switches])
    echo("$1 md2html $2" % [nimExe, nimMd2HtmlStage2Switches])

  if nimBuildIndexSwitches != "":
    exec("$1 buildIndex $2" % [nimExe, nimBuildIndexSwitches])
    echo("$1 buildIndex $2" % [nimExe, nimBuildIndexSwitches])

  for expected in walkDirRec(prjDir / "expected/", checkDir=true):
    let versionCacheParam = "?v=" & $NimMajor & "." & $NimMinor & "." & $NimPatch
    let produced = expected.replace('\\', '/').replace("/expected/", "/$1/" % [docsDir])
    if not fileExists(produced):
      echo "FAILURE: files not found: ", produced
      inc failures
    let producedFile = readFile(produced).replace(versionCacheParam,"") #remove version cache param used for cache invalidation
    if readFile(expected) != producedFile:
      echo "FAILURE: files differ: ", produced
      echo diffFiles(expected, produced).output
      inc failures
      if fixup:
        writeFile(expected, producedFile)
    else:
      echo "SUCCESS: files identical: ", produced

  if failures == 0 and ((prjDir / docsDir) != prjDir):
    removeDir(prjDir / docsDir)

# Test "nim doc --project --out:.. --index:on .."
let
  test1PrjName = "testproject"
  test1Dir = baseDir / test1PrjName
  test1DocsDir = "htmldocs"
  test1Switches = NimSwitches(doc: @["--project",
                                     "--out:$1/$2" % [test1Dir, test1DocsDir],
                                     "--index:on",
                                     "$1/$2.nim" % [test1Dir, test1PrjName]],
                              buildIndex: @["--out:$1/$2/theindex.html" % [test1Dir, test1DocsDir],
                                            "$1/$2" % [test1Dir, test1DocsDir]])
testNimDoc(test1Dir, test1DocsDir, test1Switches, fixup)

# Test "nim doc --out:.. --index:on .."
let
  test2PrjDir = "test_out_index_dot_html"
  test2PrjName = "foo"
  test2Dir = baseDir / test2PrjDir
  test2DocsDir = "htmldocs"
  test2Switches = NimSwitches(doc: @["--out:$1/$2/index.html" % [test2Dir, test2DocsDir],
                                     "--index:on",
                                     "$1/$2.nim" % [test2Dir, test2PrjName]],
                              buildIndex: @["--out:$1/$2/theindex.html" % [test2Dir, test2DocsDir],
                                            "$1/$2" % [test2Dir, test2DocsDir]])
testNimDoc(test2Dir, test2DocsDir, test2Switches, fixup)

# Test `nim doc` on file with `{.doctype.}` pragma
let
  test3PrjDir = "test_doctype"
  test3PrjName = "test_doctype"
  test3Dir = baseDir / test3PrjDir
  test3DocsDir = "htmldocs"
  test3Switches = NimSwitches(doc: @["$1/$2.nim" % [test3Dir, test3PrjName]])
testNimDoc(test3Dir, test3DocsDir, test3Switches, fixup)


# Test concise external links (RFC#125) that work with `.idx` files.
# extlinks
# ├── project
# │   ├── main.nim
# │   ├── manual.md
# │   └── sub
# │       └── submodule.nim
# └── util.nim
#
# `main.nim` imports `submodule.nim` and `../utils.nim`.
# `main.nim`, `submodule.nim`, `manual.md` do importdoc and reference each other.
let
  test4PrjName = "extlinks/project"
  test4Dir = baseDir / test4PrjName
  test4DirAbs = baseDirAbs / test4PrjName
  test4MainModule = "main"
  test4MarkupDoc = "doc" / "manual.md"
  test4DocsDir = "htmldocs"
  # 1st stage is with --index:only, 2nd is final
  test4Switches = NimSwitches(
      doc: @["--project",
             "--outdir:$1/$2" % [test4Dir, test4DocsDir],
             "--index:only",
             "$1/$2.nim" % [test4Dir, test4MainModule]],
      md2html:
             @["--outdir:$1/$2" % [test4Dir, test4DocsDir],
             "--docroot:$1" % [test4DirAbs],
             "--index:only",
             "$1/$2" % [test4Dir, test4MarkupDoc]],
      docStage2:
           @["--project",
             "--outdir:$1/$2" % [test4Dir, test4DocsDir],
             "$1/$2.nim" % [test4Dir, test4MainModule]],
      md2htmlStage2:
             @["--outdir:$1/$2" % [test4Dir, test4DocsDir],
             "--docroot:$1" % [test4DirAbs],
             "$1/$2" % [test4Dir, test4MarkupDoc]],
  )
testNimDoc(test4Dir, test4DocsDir, test4Switches, fixup)

if failures > 0:
  quit "$# failures occurred; see note in nimdoc/tester.nim regarding -d:nimTestsNimdocFixup" %  $failures
p">(c, headSymbol, initialBinding, filter, best, alt, o, diagnosticsFlag) if len(syms) == 0: return # current overload being considered var sym = syms[0].s var scope = syms[0].scope # starts at 1 because 0 is already done with setup, only needs checking var nextSymIndex = 1 var z: TCandidate # current candidate while true: determineType(c, sym) initCandidate(c, z, sym, initialBinding, scope, diagnosticsFlag) # this is kinda backwards as without a check here the described # problems in recalc would not happen, but instead it 100% # does check forever in some cases if c.currentScope.symbols.counter == symCount: # may introduce new symbols with caveats described in recalc branch matches(c, n, orig, z) if z.state == csMatch: # little hack so that iterators are preferred over everything else: if sym.kind == skIterator: if not (efWantIterator notin flags and efWantIterable in flags): inc(z.exactMatches, 200) else: dec(z.exactMatches, 200) case best.state of csEmpty, csNoMatch: best = z of csMatch: var cmp = cmpCandidates(best, z) if cmp < 0: best = z # x is better than the best so far elif cmp == 0: alt = z # x is as good as the best so far elif errorsEnabled or z.diagnosticsEnabled: errors.add(CandidateError( sym: sym, firstMismatch: z.firstMismatch, diagnostics: z.diagnostics)) else: # this branch feels like a ticking timebomb # one of two bad things could happen # 1) new symbols are discovered but the loop ends before we recalc # 2) new symbols are discovered and resemmed forever # not 100% sure if these are possible though as they would rely # on somehow introducing a new overload during overload resolution # Symbol table has been modified. Restart and pre-calculate all syms # before any further candidate init and compare. SLOW, but rare case. syms = initCandidateSymbols(c, headSymbol, initialBinding, filter, best, alt, o, diagnosticsFlag) # reset counter because syms may be in a new order symCount = c.currentScope.symbols.counter nextSymIndex = 0 # just in case, should be impossible though if syms.len == 0: break if nextSymIndex > high(syms): # we have reached the end break # advance to next sym sym = syms[nextSymIndex].s scope = syms[nextSymIndex].scope inc(nextSymIndex) proc effectProblem(f, a: PType; result: var string; c: PContext) = if f.kind == tyProc and a.kind == tyProc: if tfThread in f.flags and tfThread notin a.flags: result.add "\n This expression is not GC-safe. Annotate the " & "proc with {.gcsafe.} to get extended error information." elif tfNoSideEffect in f.flags and tfNoSideEffect notin a.flags: result.add "\n This expression can have side effects. Annotate the " & "proc with {.noSideEffect.} to get extended error information." else: case compatibleEffects(f, a) of efCompat: discard of efRaisesDiffer: result.add "\n The `.raises` requirements differ." of efRaisesUnknown: result.add "\n The `.raises` requirements differ. Annotate the " & "proc with {.raises: [].} to get extended error information." of efTagsDiffer: result.add "\n The `.tags` requirements differ." of efTagsUnknown: result.add "\n The `.tags` requirements differ. Annotate the " & "proc with {.tags: [].} to get extended error information." of efEffectsDelayed: result.add "\n The `.effectsOf` annotations differ." of efTagsIllegal: result.add "\n The `.forbids` requirements caught an illegal tag." when defined(drnim): if not c.graph.compatibleProps(c.graph, f, a): result.add "\n The `.requires` or `.ensures` properties are incompatible." proc renderNotLValue(n: PNode): string = result = $n let n = if n.kind == nkHiddenDeref: n[0] else: n if n.kind == nkHiddenCallConv and n.len > 1: result = $n[0] & "(" & result & ")" elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2: result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")" proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): (TPreferedDesc, string) = var prefer = preferName # to avoid confusing errors like: # got (SslPtr, SocketHandle) # but expected one of: # openssl.SSL_set_fd(ssl: SslPtr, fd: SocketHandle): cint # we do a pre-analysis. If all types produce the same string, we will add # module information. let proto = describeArgs(c, n, 1, preferName) for err in errors: var errProto = "" let n = err.sym.typ.n for i in 1..<n.len: var p = n[i] if p.kind == nkSym: errProto.add(typeToString(p.sym.typ, preferName)) if i != n.len-1: errProto.add(", ") # else: ignore internal error as we're already in error handling mode if errProto == proto: prefer = preferModuleInfo break # we pretend procs are attached to the type of the first # argument in order to remove plenty of candidates. This is # comparable to what C# does and C# is doing fine. var filterOnlyFirst = false if optShowAllMismatches notin c.config.globalOptions and verboseTypeMismatch in c.config.legacyFeatures: for err in errors: if err.firstMismatch.arg > 1: filterOnlyFirst = true break var maybeWrongSpace = false var candidatesAll: seq[string] var candidates = "" var skipped = 0 for err in errors: candidates.setLen 0 if filterOnlyFirst and err.firstMismatch.arg == 1: inc skipped continue if verboseTypeMismatch notin c.config.legacyFeatures: candidates.add "[" & $err.firstMismatch.arg & "] " if err.sym.kind in routineKinds and err.sym.ast != nil: candidates.add(renderTree(err.sym.ast, {renderNoBody, renderNoComments, renderNoPragmas})) else: candidates.add(getProcHeader(c.config, err.sym, prefer)) candidates.addDeclaredLocMaybe(c.config, err.sym) candidates.add("\n") let nArg = if err.firstMismatch.arg < n.len: n[err.firstMismatch.arg] else: nil let nameParam = if err.firstMismatch.formal != nil: err.firstMismatch.formal.name.s else: "" if n.len > 1 and verboseTypeMismatch in c.config.legacyFeatures: candidates.add(" first type mismatch at position: " & $err.firstMismatch.arg) # candidates.add "\n reason: " & $err.firstMismatch.kind # for debugging case err.firstMismatch.kind of kUnknownNamedParam: if nArg == nil: candidates.add("\n unknown named parameter") else: candidates.add("\n unknown named parameter: " & $nArg[0]) of kAlreadyGiven: candidates.add("\n named param already provided: " & $nArg[0]) of kPositionalAlreadyGiven: candidates.add("\n positional param was already given as named param") of kExtraArg: candidates.add("\n extra argument given") of kMissingParam: candidates.add("\n missing parameter: " & nameParam) of kTypeMismatch, kVarNeeded: doAssert nArg != nil let wanted = err.firstMismatch.formal.typ doAssert err.firstMismatch.formal != nil candidates.add("\n required type for " & nameParam & ": ") candidates.addTypeDeclVerboseMaybe(c.config, wanted) candidates.add "\n but expression '" if err.firstMismatch.kind == kVarNeeded: candidates.add renderNotLValue(nArg) candidates.add "' is immutable, not 'var'" else: candidates.add renderTree(nArg) candidates.add "' is of type: " let got = nArg.typ candidates.addTypeDeclVerboseMaybe(c.config, got) doAssert wanted != nil if got != nil: if got.kind == tyProc and wanted.kind == tyProc: # These are proc mismatches so, # add the extra explict detail of the mismatch candidates.addPragmaAndCallConvMismatch(wanted, got, c.config) effectProblem(wanted, got, candidates, c) of kUnknown: discard "do not break 'nim check'" candidates.add "\n" if err.firstMismatch.arg == 1 and nArg.kind == nkTupleConstr and n.kind == nkCommand: maybeWrongSpace = true for diag in err.diagnostics: candidates.add(diag & "\n") candidatesAll.add candidates candidatesAll.sort # fix #13538 candidates = join(candidatesAll) if skipped > 0: candidates.add($skipped & " other mismatching symbols have been " & "suppressed; compile with --showAllMismatches:on to see them\n") if maybeWrongSpace: candidates.add("maybe misplaced space between " & renderTree(n[0]) & " and '(' \n") result = (prefer, candidates) const errTypeMismatch = "type mismatch: got <" errButExpected = "but expected one of:" errExpectedPosition = "Expected one of (first mismatch at [position]):" errUndeclaredField = "undeclared field: '$1'" errUndeclaredRoutine = "attempting to call undeclared routine: '$1'" errBadRoutine = "attempting to call routine: '$1'$2" errAmbiguousCallXYZ = "ambiguous call; both $1 and $2 match for: $3" proc describeParamList(c: PContext, n: PNode, startIdx = 1; prefer = preferName): string = result = "Expression: " & $n for i in startIdx..<n.len: result.add "\n [" & $i & "] " & renderTree(n[i]) & ": " result.add describeArg(c, n, i, startIdx, prefer) result.add "\n" template legacynotFoundError(c: PContext, n: PNode, errors: CandidateErrors) = let (prefer, candidates) = presentFailedCandidates(c, n, errors) var result = errTypeMismatch result.add(describeArgs(c, n, 1, prefer)) result.add('>') if candidates != "": result.add("\n" & errButExpected & "\n" & candidates) localError(c.config, n.info, result & "\nexpression: " & $n) proc notFoundError*(c: PContext, n: PNode, errors: CandidateErrors) = # Gives a detailed error message; this is separated from semOverloadedCall, # as semOverloadedCall is already pretty slow (and we need this information # only in case of an error). if c.config.m.errorOutputs == {}: # fail fast: globalError(c.config, n.info, "type mismatch") return # see getMsgDiagnostic: if nfExplicitCall notin n.flags and {nfDotField, nfDotSetter} * n.flags != {}: let ident = considerQuotedIdent(c, n[0], n).s let sym = n[1].typ.typSym var typeHint = "" if sym == nil: discard else: typeHint = " for type " & getProcHeader(c.config, sym) localError(c.config, n.info, errUndeclaredField % ident & typeHint) return if errors.len == 0: if n[0].kind in nkIdentKinds: let ident = considerQuotedIdent(c, n[0], n).s localError(c.config, n.info, errUndeclaredRoutine % ident) else: localError(c.config, n.info, "expression '$1' cannot be called" % n[0].renderTree) return if verboseTypeMismatch in c.config.legacyFeatures: legacynotFoundError(c, n, errors) else: let (prefer, candidates) = presentFailedCandidates(c, n, errors) var result = "type mismatch\n" result.add describeParamList(c, n, 1, prefer) if candidates != "": result.add("\n" & errExpectedPosition & "\n" & candidates) localError(c.config, n.info, result) proc bracketNotFoundError(c: PContext; n: PNode) = var errors: CandidateErrors = @[] var o: TOverloadIter let headSymbol = n[0] var symx = initOverloadIter(o, c, headSymbol) while symx != nil: if symx.kind in routineKinds: errors.add(CandidateError(sym: symx, firstMismatch: MismatchInfo(), diagnostics: @[], enabled: false)) symx = nextOverloadIter(o, c, headSymbol) if errors.len == 0: localError(c.config, n.info, "could not resolve: " & $n) else: notFoundError(c, n, errors) proc getMsgDiagnostic(c: PContext, flags: TExprFlags, n, f: PNode): string = if c.compilesContextId > 0: # we avoid running more diagnostic when inside a `compiles(expr)`, to # errors while running diagnostic (see test D20180828T234921), and # also avoid slowdowns in evaluating `compiles(expr)`. discard else: var o: TOverloadIter var sym = initOverloadIter(o, c, f) while sym != nil: result &= "\n found $1" % [getSymRepr(c.config, sym)] sym = nextOverloadIter(o, c, f) let ident = considerQuotedIdent(c, f, n).s if nfExplicitCall notin n.flags and {nfDotField, nfDotSetter} * n.flags != {}: let sym = n[1].typ.typSym var typeHint = "" if sym == nil: # Perhaps we're in a `compiles(foo.bar)` expression, or # in a concept, e.g.: # ExplainedConcept {.explain.} = concept x # x.foo is int # We could use: `(c.config $ n[1].info)` to get more context. discard else: typeHint = " for type " & getProcHeader(c.config, sym) let suffix = if result.len > 0: " " & result else: "" result = errUndeclaredField % ident & typeHint & suffix else: if result.len == 0: result = errUndeclaredRoutine % ident else: result = errBadRoutine % [ident, result] proc resolveOverloads(c: PContext, n, orig: PNode, filter: TSymKinds, flags: TExprFlags, errors: var CandidateErrors, errorsEnabled: bool): TCandidate = var initialBinding: PNode var alt: TCandidate var f = n[0] if f.kind == nkBracketExpr: # fill in the bindings: semOpAux(c, f) initialBinding = f f = f[0] else: initialBinding = nil pickBestCandidate(c, f, n, orig, initialBinding, filter, result, alt, errors, efExplain in flags, errorsEnabled, flags) var dummyErrors: CandidateErrors template pickSpecialOp(headSymbol) = pickBestCandidate(c, headSymbol, n, orig, initialBinding, filter, result, alt, dummyErrors, efExplain in flags, false, flags) let overloadsState = result.state if overloadsState != csMatch: if nfDotField in n.flags: internalAssert c.config, f.kind == nkIdent and n.len >= 2 # leave the op head symbol empty, # we are going to try multiple variants n.sons[0..1] = [nil, n[1], f] orig.sons[0..1] = [nil, orig[1], f] template tryOp(x) = let op = newIdentNode(getIdent(c.cache, x), n.info) n[0] = op orig[0] = op pickSpecialOp(op) if nfExplicitCall in n.flags: tryOp ".()" if result.state in {csEmpty, csNoMatch}: tryOp "." elif nfDotSetter in n.flags and f.kind == nkIdent and n.len == 3: # we need to strip away the trailing '=' here: let calleeName = newIdentNode(getIdent(c.cache, f.ident.s[0..^2]), n.info) let callOp = newIdentNode(getIdent(c.cache, ".="), n.info) n.sons[0..1] = [callOp, n[1], calleeName] orig.sons[0..1] = [callOp, orig[1], calleeName] pickSpecialOp(callOp) if overloadsState == csEmpty and result.state == csEmpty: if efNoUndeclared notin flags: # for tests/pragmas/tcustom_pragma.nim result.state = csNoMatch if efNoDiagnostics in flags: return # xxx adapt/use errorUndeclaredIdentifierHint(c, n, f.ident) localError(c.config, n.info, getMsgDiagnostic(c, flags, n, f)) return elif result.state != csMatch: if nfExprCall in n.flags: localError(c.config, n.info, "expression '$1' cannot be called" % renderTree(n, {renderNoComments})) else: if {nfDotField, nfDotSetter} * n.flags != {}: # clean up the inserted ops n.sons.delete(2) n[0] = f return if alt.state == csMatch and cmpCandidates(result, alt) == 0 and not sameMethodDispatcher(result.calleeSym, alt.calleeSym): internalAssert c.config, result.state == csMatch #writeMatches(result) #writeMatches(alt) if c.config.m.errorOutputs == {}: # quick error message for performance of 'compiles' built-in: globalError(c.config, n.info, errGenerated, "ambiguous call") elif c.config.errorCounter == 0: # don't cascade errors var args = "(" for i in 1..<n.len: if i > 1: args.add(", ") args.add(typeToString(n[i].typ)) args.add(")") localError(c.config, n.info, errAmbiguousCallXYZ % [ getProcHeader(c.config, result.calleeSym), getProcHeader(c.config, alt.calleeSym), args]) proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) = let a = if a.kind == nkHiddenDeref: a[0] else: a if a.kind == nkHiddenCallConv and a[0].kind == nkSym: let s = a[0].sym if s.isGenericRoutineStrict: let finalCallee = generateInstance(c, s, x.bindings, a.info) a[0].sym = finalCallee a[0].typ = finalCallee.typ #a.typ = finalCallee.typ[0] proc instGenericConvertersSons*(c: PContext, n: PNode, x: TCandidate) = assert n.kind in nkCallKinds if x.genericConverter: for i in 1..<n.len: instGenericConvertersArg(c, n[i], x) proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode = var m = newCandidate(c, f) result = paramTypesMatch(m, f, a, arg, nil) if m.genericConverter and result != nil: instGenericConvertersArg(c, result, m) proc inferWithMetatype(c: PContext, formal: PType, arg: PNode, coerceDistincts = false): PNode = var m = newCandidate(c, formal) m.coerceDistincts = coerceDistincts result = paramTypesMatch(m, formal, arg.typ, arg, nil) if m.genericConverter and result != nil: instGenericConvertersArg(c, result, m) if result != nil: # This almost exactly replicates the steps taken by the compiler during # param matching. It performs an embarrassing amount of back-and-forth # type jugling, but it's the price to pay for consistency and correctness result.typ = generateTypeInstance(c, m.bindings, arg.info, formal.skipTypes({tyCompositeTypeClass})) else: typeMismatch(c.config, arg.info, formal, arg.typ, arg) # error correction: result = copyTree(arg) result.typ = formal proc updateDefaultParams(call: PNode) = # In generic procs, the default parameter may be unique for each # instantiation (see tlateboundgenericparams). # After a call is resolved, we need to re-assign any default value # that was used during sigmatch. sigmatch is responsible for marking # the default params with `nfDefaultParam` and `instantiateProcType` # computes correctly the default values for each instantiation. let calleeParams = call[0].sym.typ.n for i in 1..<call.len: if nfDefaultParam in call[i].flags: let def = calleeParams[i].sym.ast if nfDefaultRefsParam in def.flags: call.flags.incl nfDefaultRefsParam call[i] = def proc getCallLineInfo(n: PNode): TLineInfo = case n.kind of nkAccQuoted, nkBracketExpr, nkCall, nkCallStrLit, nkCommand: if len(n) > 0: return getCallLineInfo(n[0]) of nkDotExpr: if len(n) > 1: return getCallLineInfo(n[1]) else: discard result = n.info proc inheritBindings(c: PContext, x: var TCandidate, expectedType: PType) = ## Helper proc to inherit bound generic parameters from expectedType into x. ## Does nothing if 'inferGenericTypes' isn't in c.features if inferGenericTypes notin c.features: return if expectedType == nil or x.callee[0] == nil: return # required for inference var flatUnbound: seq[PType] flatBound: seq[PType] # seq[(result type, expected type)] var typeStack = newSeq[(PType, PType)]() template stackPut(a, b) = ## skips types and puts the skipped version on stack # It might make sense to skip here one by one. It's not part of the main # type reduction because the right side normally won't be skipped const toSkip = { tyVar, tyLent, tyStatic, tyCompositeTypeClass } let x = a.skipTypes(toSkip) y = if a.kind notin toSkip: b else: b.skipTypes(toSkip) typeStack.add((x, y)) stackPut(x.callee[0], expectedType) while typeStack.len() > 0: let (t, u) = typeStack.pop() if t == u or t == nil or u == nil or t.kind == tyAnything or u.kind == tyAnything: continue case t.kind of ConcreteTypes, tyGenericInvocation, tyUncheckedArray: # nested, add all the types to stack let startIdx = if u.kind in ConcreteTypes: 0 else: 1 endIdx = min(u.sons.len() - startIdx, t.sons.len()) for i in startIdx ..< endIdx: # early exit with current impl if t[i] == nil or u[i] == nil: return stackPut(t[i], u[i]) of tyGenericParam: if x.bindings.idTableGet(t) != nil: return # fully reduced generic param, bind it if t notin flatUnbound: flatUnbound.add(t) flatBound.add(u) else: discard for i in 0 ..< flatUnbound.len(): x.bindings.idTablePut(flatUnbound[i], flatBound[i]) proc semResolvedCall(c: PContext, x: var TCandidate, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode = assert x.state == csMatch var finalCallee = x.calleeSym let info = getCallLineInfo(n) markUsed(c, info, finalCallee) onUse(info, finalCallee) assert finalCallee.ast != nil if x.hasFauxMatch: result = x.call result[0] = newSymNode(finalCallee, getCallLineInfo(result[0])) if containsGenericType(result.typ) or x.fauxMatch == tyUnknown: result.typ = newTypeS(x.fauxMatch, c) if result.typ.kind == tyError: incl result.typ.flags, tfCheckedForDestructor return let gp = finalCallee.ast[genericParamsPos] if gp.isGenericParams: if x.calleeSym.kind notin {skMacro, skTemplate}: if x.calleeSym.magic in {mArrGet, mArrPut}: finalCallee = x.calleeSym else: c.inheritBindings(x, expectedType) finalCallee = generateInstance(c, x.calleeSym, x.bindings, n.info) else: # For macros and templates, the resolved generic params # are added as normal params. c.inheritBindings(x, expectedType) for s in instantiateGenericParamList(c, gp, x.bindings): case s.kind of skConst: if not s.astdef.isNil: x.call.add s.astdef else: x.call.add c.graph.emptyNode of skType: x.call.add newSymNode(s, n.info) else: internalAssert c.config, false result = x.call instGenericConvertersSons(c, result, x) result[0] = newSymNode(finalCallee, getCallLineInfo(result[0])) result.typ = finalCallee.typ[0] updateDefaultParams(result) proc canDeref(n: PNode): bool {.inline.} = result = n.len >= 2 and (let t = n[1].typ; t != nil and t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyPtr, tyRef}) proc tryDeref(n: PNode): PNode = result = newNodeI(nkHiddenDeref, n.info) result.typ = n.typ.skipTypes(abstractInst)[0] result.add n proc semOverloadedCall(c: PContext, n, nOrig: PNode, filter: TSymKinds, flags: TExprFlags; expectedType: PType = nil): PNode = var errors: CandidateErrors = @[] # if efExplain in flags: @[] else: nil var r = resolveOverloads(c, n, nOrig, filter, flags, errors, efExplain in flags) if r.state == csMatch: # this may be triggered, when the explain pragma is used if errors.len > 0: let (_, candidates) = presentFailedCandidates(c, n, errors) message(c.config, n.info, hintUserRaw, "Non-matching candidates for " & renderTree(n) & "\n" & candidates) result = semResolvedCall(c, r, n, flags, expectedType) else: if efDetermineType in flags and c.inGenericContext > 0 and c.matchedConcept == nil: result = semGenericStmt(c, n) result.typ = makeTypeFromExpr(c, result.copyTree) elif efExplain notin flags: # repeat the overload resolution, # this time enabling all the diagnostic output (this should fail again) result = semOverloadedCall(c, n, nOrig, filter, flags + {efExplain}) elif efNoUndeclared notin flags: notFoundError(c, n, errors) proc explicitGenericInstError(c: PContext; n: PNode): PNode = localError(c.config, getCallLineInfo(n), errCannotInstantiateX % renderTree(n)) result = n proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = # binding has to stay 'nil' for this to work! var m = newCandidate(c, s, nil) for i in 1..<n.len: let formal = s.ast[genericParamsPos][i-1].typ var arg = n[i].typ # try transforming the argument into a static one before feeding it into # typeRel if formal.kind == tyStatic and arg.kind != tyStatic: let evaluated = c.semTryConstExpr(c, n[i]) if evaluated != nil: arg = newTypeS(tyStatic, c) arg.sons = @[evaluated.typ] arg.n = evaluated let tm = typeRel(m, formal, arg) if tm in {isNone, isConvertible}: return nil var newInst = generateInstance(c, s, m.bindings, n.info) newInst.typ.flags.excl tfUnresolved let info = getCallLineInfo(n) markUsed(c, info, s) onUse(info, s) result = newSymNode(newInst, info) proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = assert n.kind == nkBracketExpr for i in 1..<n.len: let e = semExprWithType(c, n[i]) if e.typ == nil: n[i].typ = errorType(c) else: n[i].typ = e.typ.skipTypes({tyTypeDesc}) var s = s var a = n[0] if a.kind == nkSym: # common case; check the only candidate has the right # number of generic type parameters: if s.ast[genericParamsPos].safeLen != n.len-1: let expected = s.ast[genericParamsPos].safeLen localError(c.config, getCallLineInfo(n), errGenerated, "cannot instantiate: '" & renderTree(n) & "'; got " & $(n.len-1) & " typeof(s) but expected " & $expected) return n result = explicitGenericSym(c, n, s) if result == nil: result = explicitGenericInstError(c, n) elif a.kind in {nkClosedSymChoice, nkOpenSymChoice}: # choose the generic proc with the proper number of type parameters. # XXX I think this could be improved by reusing sigmatch.paramTypesMatch. # It's good enough for now. result = newNodeI(a.kind, getCallLineInfo(n)) for i in 0..<a.len: var candidate = a[i].sym if candidate.kind in {skProc, skMethod, skConverter, skFunc, skIterator}: # it suffices that the candidate has the proper number of generic # type parameters: if candidate.ast[genericParamsPos].safeLen == n.len-1: let x = explicitGenericSym(c, n, candidate) if x != nil: result.add(x) # get rid of nkClosedSymChoice if not ambiguous: if result.len == 1 and a.kind == nkClosedSymChoice: result = result[0] elif result.len == 0: result = explicitGenericInstError(c, n) # candidateCount != 1: return explicitGenericInstError(c, n) else: result = explicitGenericInstError(c, n) proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym = # Searches for the fn in the symbol table. If the parameter lists are suitable # for borrowing the sym in the symbol table is returned, else nil. # New approach: generate fn(x, y, z) where x, y, z have the proper types # and use the overloading resolution mechanism: var call = newNodeI(nkCall, fn.info) var hasDistinct = false call.add(newIdentNode(fn.name, fn.info)) for i in 1..<fn.typ.n.len: let param = fn.typ.n[i] const desiredTypes = abstractVar + {tyCompositeTypeClass} - {tyTypeDesc, tyDistinct} #[. # We only want the type not any modifiers such as `ptr`, `var`, `ref` ... # tyCompositeTypeClass is here for # when using something like: type Foo[T] = distinct int proc `$`(f: Foo): string {.borrow.} # We want to skip the `Foo` to get `int` ]# let t = skipTypes(param.typ, desiredTypes) if t.kind == tyDistinct or param.typ.kind == tyDistinct: hasDistinct = true var x: PType if param.typ.kind == tyVar: x = newTypeS(param.typ.kind, c) x.addSonSkipIntLit(t.baseOfDistinct(c.graph, c.idgen), c.idgen) else: x = t.baseOfDistinct(c.graph, c.idgen) call.add(newNodeIT(nkEmpty, fn.info, x)) if hasDistinct: let filter = if fn.kind in {skProc, skFunc}: {skProc, skFunc} else: {fn.kind} var resolved = semOverloadedCall(c, call, call, filter, {}) if resolved != nil: result = resolved[0].sym if not compareTypes(result.typ[0], fn.typ[0], dcEqIgnoreDistinct): result = nil elif result.magic in {mArrPut, mArrGet}: # cannot borrow these magics for now result = nil