From 11053afff83e00bb04be05590a169ed79ce5f0d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Sun, 26 Jan 2014 22:30:20 +0100 Subject: [PATCH 01/23] osproc: fix naming inconsistiences --- lib/pure/osproc.nim | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index c7678b2148..f764971c4a 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -13,7 +13,7 @@ include "system/inclrtl" import - strutils, os, strtabs, streams, sequtils + strutils, os, strtabs, streams when defined(windows): import winlean @@ -44,7 +44,7 @@ type poStdErrToStdOut, ## merge stdout and stderr to the stdout stream poParentStreams ## use the parent's streams -template poUseShell*: TProcessOption {.deprecated.} = poUsePath +const poUseShell* {.deprecated.} = poUsePath ## Deprecated alias for poUsePath. proc quoteShellWindows*(s: string): string {.noSideEffect, rtl, extern: "nosp$1".} = @@ -604,20 +604,20 @@ elif not defined(useNimRtl): pipe(pStderr) != 0'i32: osError(osLastError()) - var sys_command: string - var sys_args_raw: seq[string] + var sysCommand: string + var sysArgsRaw: seq[string] if poEvalCommand in options: - sys_command = "/bin/sh" - sys_args_raw = @[sys_command, "-c", command] + sysCommand = "/bin/sh" + sysArgsRaw = @[sysCommand, "-c", command] assert args.len == 0 else: - sys_command = command - sys_args_raw = @[command] + sysCommand = command + sysArgsRaw = @[command] for arg in args.items: - sys_args_raw.add arg + sysArgsRaw.add arg - var sys_args = allocCStringArray(sys_args_raw) - finally: deallocCStringArray(sys_args) + var sysArgs = allocCStringArray(sysArgsRaw) + finally: deallocCStringArray(sysArgs) var pid: TPid when defined(posix_spawn) and not defined(useFork): @@ -650,15 +650,15 @@ elif not defined(useNimRtl): else: chck posix_spawn_file_actions_adddup2(fops, p_stderr[writeIdx], 2) - var sys_env = if env == nil: envToCStringArray() else: envToCStringArray(env) + var sysEnv = if env == nil: envToCStringArray() else: envToCStringArray(env) var res: cint - # This is incorrect! + # FIXME: chdir is global to process if workingDir.len > 0: os.setCurrentDir(workingDir) if poUsePath in options: - res = posix_spawnp(pid, sys_command, fops, attr, sys_args, sys_env) + res = posix_spawnp(pid, sysCommand, fops, attr, sysArgs, sysEnv) else: - res = posix_spawn(pid, sys_command, fops, attr, sys_args, sys_env) - deallocCStringArray(sys_env) + res = posix_spawn(pid, sysCommand, fops, attr, sysArgs, sysEnv) + deallocCStringArray(sysEnv) discard posix_spawn_file_actions_destroy(fops) discard posix_spawnattr_destroy(attr) chck res @@ -687,15 +687,15 @@ elif not defined(useNimRtl): if env == nil: if poUsePath in options: - discard execvp(sys_command, sys_args) + discard execvp(sysCommand, sysArgs) else: - discard execv(sys_command, sys_args) + discard execv(sysCommand, sysArgs) else: - var c_env = envToCStringArray(env) + var cEnv = envToCStringArray(env) if poUsePath in options: - discard execvpe(sys_command, sys_args, c_env) + discard execvpe(sysCommand, sysArgs, cEnv) else: - discard execve(sys_command, sys_args, c_env) + discard execve(sysCommand, sysArgs, cEnv) # too risky to raise an exception here: quit("execve call failed: " & $strerror(errno)) # Parent process. Copy process information. From cd2bd7fa7b1048956c72ad7665b70b2eabecd549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Fri, 14 Feb 2014 15:08:02 +0100 Subject: [PATCH 02/23] osproc: use clone with CLONE_VM on Linux for faster process spawning --- lib/posix/linux.nim | 25 ++++++ lib/pure/osproc.nim | 193 +++++++++++++++++++++++++++----------------- 2 files changed, 146 insertions(+), 72 deletions(-) create mode 100644 lib/posix/linux.nim diff --git a/lib/posix/linux.nim b/lib/posix/linux.nim new file mode 100644 index 0000000000..1ed1af3b6d --- /dev/null +++ b/lib/posix/linux.nim @@ -0,0 +1,25 @@ +import posix + +const + CSIGNAL* = 0x000000FF + CLONE_VM* = 0x00000100 + CLONE_FS* = 0x00000200 + CLONE_FILES* = 0x00000400 + CLONE_SIGHAND* = 0x00000800 + CLONE_PTRACE* = 0x00002000 + CLONE_VFORK* = 0x00004000 + CLONE_PARENT* = 0x00008000 + CLONE_THREAD* = 0x00010000 + CLONE_NEWNS* = 0x00020000 + CLONE_SYSVSEM* = 0x00040000 + CLONE_SETTLS* = 0x00080000 + CLONE_PARENT_SETTID* = 0x00100000 + CLONE_CHILD_CLEARTID* = 0x00200000 + CLONE_DETACHED* = 0x00400000 + CLONE_UNTRACED* = 0x00800000 + CLONE_CHILD_SETTID* = 0x01000000 + CLONE_STOPPED* = 0x02000000 + +# fn should be of type proc (a2: pointer): void {.cdecl.} +proc clone*(fn: pointer; child_stack: pointer; flags: cint; + arg: pointer; ptid: ptr TPid; tls: pointer; ctid: ptr TPid): cint {.importc, header: "".} diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index c0c7f46d74..40877f6382 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -20,6 +20,11 @@ when defined(windows): else: import posix +when defined(linux): + import linux + when not defined(useFork): + const useClone = true + type TProcess = object of TObject when defined(windows): @@ -590,6 +595,19 @@ elif not defined(useNimRtl): copyMem(result[i], addr(x[0]), x.len+1) inc(i) + type TStartProcessData = object + sysCommand: cstring + sysArgs: cstringArray + sysEnv: cstringArray + workingDir: cstring + pStdin, pStdout, pStderr: array[0..1, cint] + optionPoUsePath: bool + optionPoParentStreams: bool + optionPoStdErrToStdOut: bool + + proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} + proc startProcessAfterFork(data: ptr TStartProcessData) {.tags: [FExecIO, FReadEnv], cdecl.} + proc startProcess(command: string, workingDir: string = "", args: openArray[string] = [], @@ -616,88 +634,48 @@ elif not defined(useNimRtl): for arg in args.items: sysArgsRaw.add arg + var pid: TPid + var sysArgs = allocCStringArray(sysArgsRaw) finally: deallocCStringArray(sysArgs) - var pid: TPid - when defined(posix_spawn) and not defined(useFork): - var attr: Tposix_spawnattr - var fops: Tposix_spawn_file_actions - - template chck(e: expr) = - if e != 0'i32: osError(osLastError()) - - chck posix_spawn_file_actions_init(fops) - chck posix_spawnattr_init(attr) - - var mask: Tsigset - chck sigemptyset(mask) - chck posix_spawnattr_setsigmask(attr, mask) - chck posix_spawnattr_setpgroup(attr, 0'i32) - - chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or - POSIX_SPAWN_SETSIGMASK or - POSIX_SPAWN_SETPGROUP) - - if poParentStreams notin options: - chck posix_spawn_file_actions_addclose(fops, pStdin[writeIdx]) - chck posix_spawn_file_actions_adddup2(fops, pStdin[readIdx], readIdx) - chck posix_spawn_file_actions_addclose(fops, pStdout[readIdx]) - chck posix_spawn_file_actions_adddup2(fops, pStdout[writeIdx], writeIdx) - chck posix_spawn_file_actions_addclose(fops, pStderr[readIdx]) - if poStdErrToStdOut in options: - chck posix_spawn_file_actions_adddup2(fops, pStdout[writeIdx], 2) - else: - chck posix_spawn_file_actions_adddup2(fops, p_stderr[writeIdx], 2) - - var sysEnv = if env == nil: envToCStringArray() else: envToCStringArray(env) - var res: cint - # FIXME: chdir is global to process - if workingDir.len > 0: os.setCurrentDir(workingDir) - if poUsePath in options: - res = posix_spawnp(pid, sysCommand, fops, attr, sysArgs, sysEnv) + var sysEnv = if env == nil: + envToCStringArray() else: - res = posix_spawn(pid, sysCommand, fops, attr, sysArgs, sysEnv) - deallocCStringArray(sysEnv) - discard posix_spawn_file_actions_destroy(fops) - discard posix_spawnattr_destroy(attr) - chck res + envToCStringArray(env) + finally: deallocCStringArray(sysEnv) + + var data: TStartProcessData + data.sysCommand = sysCommand + data.sysArgs = sysArgs + data.sysEnv = sysEnv + data.pStdin = pStdin + data.pStdout = pStdout + data.pStderr = pStderr + data.optionPoParentStreams = poParentStreams in options + data.optionPoUsePath = poUsePath in options + data.optionPoStdErrToStdOut = poStdErrToStdOut in options + data.workingDir = workingDir + + when defined(useClone): + const stackSize = 8096 + let stackEnd = cast[clong](alloc(stackSize)) + let stack = cast[pointer](stackEnd + stackSize) + let fn: pointer = startProcessAfterFork + pid = clone(fn, stack, + cint(CLONE_VM or CLONE_VFORK or SIGCHLD), + pointer(addr data), nil, nil, nil) + + if pid < 0: osError(osLastError()) + elif defined(posix_spawn) and not defined(useFork): + pid = startProcessAuxSpawn(data) else: pid = fork() if pid < 0: osError(osLastError()) if pid == 0: - ## child process: + startProcessAfterFork(addr(data)) - if poParentStreams notin options: - discard close(p_stdin[writeIdx]) - if dup2(p_stdin[readIdx], readIdx) < 0: osError(osLastError()) - discard close(p_stdout[readIdx]) - if dup2(p_stdout[writeIdx], writeIdx) < 0: osError(osLastError()) - discard close(p_stderr[readIdx]) - if poStdErrToStdOut in options: - if dup2(p_stdout[writeIdx], 2) < 0: osError(osLastError()) - else: - if dup2(p_stderr[writeIdx], 2) < 0: osError(osLastError()) - - # Create a new process group - if setpgid(0, 0) == -1: quit("setpgid call failed: " & $strerror(errno)) - - if workingDir.len > 0: os.setCurrentDir(workingDir) - - if env == nil: - if poUsePath in options: - discard execvp(sysCommand, sysArgs) - else: - discard execv(sysCommand, sysArgs) - else: - var cEnv = envToCStringArray(env) - if poUsePath in options: - discard execvpe(sysCommand, sysArgs, cEnv) - else: - discard execve(sysCommand, sysArgs, cEnv) - # too risky to raise an exception here: - quit("execve call failed: " & $strerror(errno)) # Parent process. Copy process information. if poEchoCmd in options: echo(command, " ", join(args, " ")) @@ -723,6 +701,77 @@ elif not defined(useNimRtl): discard close(pStdin[readIdx]) discard close(pStdout[writeIdx]) + proc startProcessAuxSpawn(data: TStartProcessData): TPid = + var attr: Tposix_spawnattr + var fops: Tposix_spawn_file_actions + + template chck(e: expr) = + if e != 0'i32: osError(osLastError()) + + chck posix_spawn_file_actions_init(fops) + chck posix_spawnattr_init(attr) + + var mask: Tsigset + chck sigemptyset(mask) + chck posix_spawnattr_setsigmask(attr, mask) + chck posix_spawnattr_setpgroup(attr, 0'i32) + + chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or + POSIX_SPAWN_SETSIGMASK or + POSIX_SPAWN_SETPGROUP) + + if not data.optionPoParentStreams: + chck posix_spawn_file_actions_addclose(fops, data.pStdin[writeIdx]) + chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], readIdx) + chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx]) + chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx) + chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx]) + if data.optionPoStdErrToStdOut: + chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2) + else: + chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2) + + var res: cint + # FIXME: chdir is global to process + if data.workingDir.len > 0: + setCurrentDir($data.workingDir) + var pid: TPid + + if data.optionPoUsePath: + res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) + else: + res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv) + + discard posix_spawn_file_actions_destroy(fops) + discard posix_spawnattr_destroy(attr) + chck res + return pid + + proc startProcessAfterFork(data: ptr TStartProcessData) = + # Warning: no GC here! + if not data.optionPoParentStreams: + discard close(data.pStdin[writeIdx]) + if dup2(data.pStdin[readIdx], readIdx) < 0: osError(osLastError()) + discard close(data.pStdout[readIdx]) + if dup2(data.pStdout[writeIdx], writeIdx) < 0: osError(osLastError()) + discard close(data.pStderr[readIdx]) + if data.optionPoStdErrToStdOut: + if dup2(data.pStdout[writeIdx], 2) < 0: osError(osLastError()) + else: + if dup2(data.pStderr[writeIdx], 2) < 0: osError(osLastError()) + + if data.workingDir.len > 0: + if chdir(data.workingDir) < 0: + quit("chdir failed") + + if data.optionPoUsePath: + discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv) + else: + discard execve(data.sysCommand, data.sysArgs, data.sysEnv) + + # too risky to raise an exception here: + quit("execve call failed: " & $strerror(errno)) + proc close(p: PProcess) = if p.inStream != nil: close(p.inStream) if p.outStream != nil: close(p.outStream) From 4c09fc110f3d269c34ccbfabb665bc34c768b63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Fri, 14 Feb 2014 15:51:06 +0100 Subject: [PATCH 03/23] osproc: make failed execv an exception (when using fork or clone) startProcessAuxFork creates a pipe, which is used by a child to pass an error code if execv fails. --- lib/posix/posix.nim | 1 + lib/pure/osproc.nim | 86 +++++++++++++++++++++++++++++++++------------ 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 41260b36fc..138df1aecf 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -2066,6 +2066,7 @@ proc pthread_spin_unlock*(a1: ptr Tpthread_spinlock): cint {. proc pthread_testcancel*() {.importc, header: "".} +proc exitnow*(code: int): void {.importc: "_exit", header: "".} proc access*(a1: cstring, a2: cint): cint {.importc, header: "".} proc alarm*(a1: cint): cint {.importc, header: "".} proc chdir*(a1: cstring): cint {.importc, header: "".} diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 40877f6382..aa2f6f937d 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -600,13 +600,16 @@ elif not defined(useNimRtl): sysArgs: cstringArray sysEnv: cstringArray workingDir: cstring - pStdin, pStdout, pStderr: array[0..1, cint] + pStdin, pStdout, pStderr, pErrorPipe: array[0..1, cint] optionPoUsePath: bool optionPoParentStreams: bool optionPoStdErrToStdOut: bool proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} - proc startProcessAfterFork(data: ptr TStartProcessData) {.tags: [FExecIO, FReadEnv], cdecl.} + + proc startProcessAuxFork(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} + proc startProcessAfterFork(data: ptr TStartProcessData) {. + tags: [FExecIO, FReadEnv], noStackFrame, cdecl.} proc startProcess(command: string, workingDir: string = "", @@ -658,23 +661,11 @@ elif not defined(useNimRtl): data.optionPoStdErrToStdOut = poStdErrToStdOut in options data.workingDir = workingDir - when defined(useClone): - const stackSize = 8096 - let stackEnd = cast[clong](alloc(stackSize)) - let stack = cast[pointer](stackEnd + stackSize) - let fn: pointer = startProcessAfterFork - pid = clone(fn, stack, - cint(CLONE_VM or CLONE_VFORK or SIGCHLD), - pointer(addr data), nil, nil, nil) - if pid < 0: osError(osLastError()) - elif defined(posix_spawn) and not defined(useFork): + when defined(posix_spawn) and not defined(useFork) and not defined(useClone): pid = startProcessAuxSpawn(data) else: - pid = fork() - if pid < 0: osError(osLastError()) - if pid == 0: - startProcessAfterFork(addr(data)) + pid = startProcessAuxFork(data) # Parent process. Copy process information. if poEchoCmd in options: @@ -747,30 +738,79 @@ elif not defined(useNimRtl): chck res return pid + proc startProcessAuxFork(data: TStartProcessData): TPid = + if pipe(data.pErrorPipe) != 0: + osError(osLastError()) + + finally: + discard close(data.pErrorPipe[readIdx]) + + var pid: TPid + var dataCopy = data + + if defined(useClone): + const stackSize = 8096 + let stackEnd = cast[clong](alloc(stackSize)) + let stack = cast[pointer](stackEnd + stackSize) + let fn: pointer = startProcessAfterFork + pid = clone(fn, stack, + cint(CLONE_VM or CLONE_VFORK or SIGCHLD), + pointer(addr dataCopy), nil, nil, nil) + discard close(data.pErrorPipe[writeIdx]) + dealloc(stack) + else: + pid = fork() + if pid == 0: + startProcessAfterFork(addr(dataCopy)) + exitnow(1) + + discard close(data.pErrorPipe[writeIdx]) + if pid < 0: osError(osLastError()) + + var error: cint + let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error)) + if sizeRead == sizeof(error): + osError($strerror(error)) + + return pid + + proc startProcessFail(data: ptr TStartProcessData) {.noStackFrame.} = + var error: cint = errno + discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error)) + exitnow(1) + proc startProcessAfterFork(data: ptr TStartProcessData) = # Warning: no GC here! + # Or anythink that touches global structures - all called nimrod procs + # must be marked with noStackFrame. Inspect C code after making changes. if not data.optionPoParentStreams: discard close(data.pStdin[writeIdx]) - if dup2(data.pStdin[readIdx], readIdx) < 0: osError(osLastError()) + if dup2(data.pStdin[readIdx], readIdx) < 0: + startProcessFail(data) discard close(data.pStdout[readIdx]) - if dup2(data.pStdout[writeIdx], writeIdx) < 0: osError(osLastError()) + if dup2(data.pStdout[writeIdx], writeIdx) < 0: + startProcessFail(data) discard close(data.pStderr[readIdx]) if data.optionPoStdErrToStdOut: - if dup2(data.pStdout[writeIdx], 2) < 0: osError(osLastError()) + if dup2(data.pStdout[writeIdx], 2) < 0: + startProcessFail(data) else: - if dup2(data.pStderr[writeIdx], 2) < 0: osError(osLastError()) + if dup2(data.pStderr[writeIdx], 2) < 0: + startProcessFail(data) if data.workingDir.len > 0: if chdir(data.workingDir) < 0: - quit("chdir failed") + startProcessFail(data) + + discard close(data.pErrorPipe[readIdx]) + discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC) if data.optionPoUsePath: discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv) else: discard execve(data.sysCommand, data.sysArgs, data.sysEnv) - # too risky to raise an exception here: - quit("execve call failed: " & $strerror(errno)) + startProcessFail(data) proc close(p: PProcess) = if p.inStream != nil: close(p.inStream) From 765c682c92f27e13d079b146941033d7b68e261b Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 18 Feb 2014 01:18:59 +0200 Subject: [PATCH 04/23] fix #204; --- compiler/msgs.nim | 2 ++ compiler/semstmts.nim | 7 ++++++- lib/pure/sockets2.nim | 8 ++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 2682053611..0140d1ac4d 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -104,6 +104,7 @@ type errXhasSideEffects, errIteratorExpected, errLetNeedsInit, errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX, errXCannotBeClosure, errXMustBeCompileTime, + errCannotInferTypeOfTheLiteral, errUser, warnCannotOpenFile, warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, @@ -348,6 +349,7 @@ const errIllegalCaptureX: "illegal capture '$1'", errXCannotBeClosure: "'$1' cannot have 'closure' calling convention", errXMustBeCompileTime: "'$1' can only be used in compile-time context", + errCannotInferTypeOfTheLiteral: "cannot infer the type of the $1", errUser: "$1", warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]", warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]", diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 503ea4bc1a..a9a9079530 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -349,7 +349,12 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = # BUGFIX: ``fitNode`` is needed here! # check type compability between def.typ and typ: if typ != nil: def = fitNode(c, typ, def) - else: typ = skipIntLit(def.typ) + else: + typ = skipIntLit(def.typ) + if typ.kind in {tySequence, tyArray, tySet} and + typ.lastSon.kind == tyEmpty: + localError(def.info, errCannotInferTypeOfTheLiteral, + ($typ.kind).substr(2).toLower) else: def = ast.emptyNode if symkind == skLet: localError(a.info, errLetNeedsInit) diff --git a/lib/pure/sockets2.nim b/lib/pure/sockets2.nim index 22624bbad5..f8284b3392 100644 --- a/lib/pure/sockets2.nim +++ b/lib/pure/sockets2.nim @@ -89,7 +89,7 @@ when defined(posix): of AF_UNIX: result = posix.AF_UNIX of AF_INET: result = posix.AF_INET of AF_INET6: result = posix.AF_INET6 - else: nil + else: discard proc toInt(typ: TType): cint = case typ @@ -97,7 +97,7 @@ when defined(posix): of SOCK_DGRAM: result = posix.SOCK_DGRAM of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET of SOCK_RAW: result = posix.SOCK_RAW - else: nil + else: discard proc toInt(p: TProtocol): cint = case p @@ -107,7 +107,7 @@ when defined(posix): of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 of IPPROTO_RAW: result = posix.IPPROTO_RAW of IPPROTO_ICMP: result = posix.IPPROTO_ICMP - else: nil + else: discard else: proc toInt(domain: TDomain): cint = @@ -199,4 +199,4 @@ proc htons*(x: int16): int16 = when defined(Windows): var wsa: TWSADATA - if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError()) \ No newline at end of file + if WSAStartup(0x0101'i16, addr wsa) != 0: OSError(OSLastError()) From 4d3846e26b38ddf7fade05489f0f3335db057950 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 18 Feb 2014 01:37:00 +0200 Subject: [PATCH 05/23] fix tbindtypedesc and tactiontable2 --- compiler/sigmatch.nim | 17 ++++++++++++----- tests/actiontable/tactiontable2.nim | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 5fe474ef36..240145118e 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -900,20 +900,27 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = result = isNone of tyTypeDesc: - if a.kind != tyTypeDesc: return isNone - var prev = PType(idTableGet(c.bindings, f)) if prev == nil: + # proc foo(T: typedesc, x: T) + # when `f` is an unresolved typedesc, `a` could be any + # type, so we should not perform this check earlier + if a.kind != tyTypeDesc: return isNone + if f.base.kind == tyNone: result = isGeneric else: result = typeRel(c, f.base, a.base) + if result != isNone: put(c.bindings, f, a) else: - let toMatch = if tfUnresolved in f.flags: a - else: a.base - result = typeRel(c, prev.base, toMatch) + if tfUnresolved in f.flags: + result = typeRel(c, prev.base, a) + elif a.kind == tyTypeDesc: + result = typeRel(c, prev.base, a.base) + else: + result = isNone of tyStmt: result = isGeneric diff --git a/tests/actiontable/tactiontable2.nim b/tests/actiontable/tactiontable2.nim index 00b4276032..878356321c 100644 --- a/tests/actiontable/tactiontable2.nim +++ b/tests/actiontable/tactiontable2.nim @@ -1,6 +1,6 @@ discard """ line: 21 - errormsg: "invalid type: 'TTable'" + errormsg: "invalid type: 'TTable[string, proc (string)]'" """ import tables From cda92048ba9408d356e0023543fcfb45ea357da8 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 18 Feb 2014 02:46:14 +0200 Subject: [PATCH 06/23] fix some trivial errors in the test suite and some more regressions caused by tyTypeDesc[tyNone] --- compiler/semtypes.nim | 5 +-- lib/system/jssys.nim | 59 +++++++++++++++---------------- tests/generics/tgenericlambda.nim | 10 +++++- tests/global/globalaux.nim | 15 ++++++++ tests/global/globalaux2.nim | 4 +++ tests/module/trecinca.nim | 2 +- tests/module/trecincb.nim | 2 +- tests/stdlib/tircbot.nim | 2 +- tests/{ => template}/sunset.tmpl | 0 tests/typerel/tvoid.nim | 6 +++- 10 files changed, 68 insertions(+), 37 deletions(-) create mode 100644 tests/global/globalaux.nim create mode 100644 tests/global/globalaux2.nim rename tests/{ => template}/sunset.tmpl (100%) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 184aca4f80..98abaf0059 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -669,7 +669,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, of tyTypeDesc: if tfUnresolved notin paramType.flags: # naked typedescs are not bindOnce types - if paramType.sonsLen == 0 and paramTypId != nil and + if paramType.base.kind == tyNone and paramTypId != nil and paramTypId.id == typedescId.id: paramTypId = nil result = addImplicitGeneric(c.newTypeWithSons(tyTypeDesc, paramType.sons)) @@ -1011,7 +1011,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of mOrdinal: result = semOrdinal(c, n, prev) of mSeq: result = semContainer(c, n, tySequence, "seq", prev) of mVarargs: result = semVarargs(c, n, prev) - of mExpr, mTypeDesc: + of mTypeDesc: result = makeTypeDesc(c, semTypeNode(c, n[1], nil)) + of mExpr: result = semTypeNode(c, n.sons[0], nil) if result != nil: result = copyType(result, getCurrOwner(), false) diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 4fc5f479b7..0714f45892 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -418,76 +418,75 @@ proc modInt64(a, b: int): int {.noStackFrame, compilerproc.} = return Math.floor(`a` % `b`); """ -proc NegInt(a: int): int {.compilerproc.} = +proc negInt(a: int): int {.compilerproc.} = result = a*(-1) -proc NegInt64(a: int64): int64 {.compilerproc.} = +proc negInt64(a: int64): int64 {.compilerproc.} = result = a*(-1) -proc AbsInt(a: int): int {.compilerproc.} = +proc absInt(a: int): int {.compilerproc.} = result = if a < 0: a*(-1) else: a -proc AbsInt64(a: int64): int64 {.compilerproc.} = +proc absInt64(a: int64): int64 {.compilerproc.} = result = if a < 0: a*(-1) else: a -proc LeU(a, b: int): bool {.compilerproc.} = +proc leU(a, b: int): bool {.compilerproc.} = result = abs(a) <= abs(b) -proc LtU(a, b: int): bool {.compilerproc.} = +proc ltU(a, b: int): bool {.compilerproc.} = result = abs(a) < abs(b) -proc LeU64(a, b: int64): bool {.compilerproc.} = +proc leU64(a, b: int64): bool {.compilerproc.} = result = abs(a) <= abs(b) - -proc LtU64(a, b: int64): bool {.compilerproc.} = +proc ltU64(a, b: int64): bool {.compilerproc.} = result = abs(a) < abs(b) -proc AddU(a, b: int): int {.compilerproc.} = +proc addU(a, b: int): int {.compilerproc.} = result = abs(a) + abs(b) -proc AddU64(a, b: int64): int64 {.compilerproc.} = +proc addU64(a, b: int64): int64 {.compilerproc.} = result = abs(a) + abs(b) -proc SubU(a, b: int): int {.compilerproc.} = +proc subU(a, b: int): int {.compilerproc.} = result = abs(a) - abs(b) -proc SubU64(a, b: int64): int64 {.compilerproc.} = +proc subU64(a, b: int64): int64 {.compilerproc.} = result = abs(a) - abs(b) -proc MulU(a, b: int): int {.compilerproc.} = +proc mulU(a, b: int): int {.compilerproc.} = result = abs(a) * abs(b) -proc MulU64(a, b: int64): int64 {.compilerproc.} = +proc mulU64(a, b: int64): int64 {.compilerproc.} = result = abs(a) * abs(b) -proc DivU(a, b: int): int {.compilerproc.} = +proc divU(a, b: int): int {.compilerproc.} = result = abs(a) div abs(b) -proc DivU64(a, b: int64): int64 {.compilerproc.} = +proc divU64(a, b: int64): int64 {.compilerproc.} = result = abs(a) div abs(b) -proc ModU(a, b: int): int {.compilerproc.} = +proc modU(a, b: int): int {.compilerproc.} = result = abs(a) mod abs(b) -proc ModU64(a, b: int64): int64 {.compilerproc.} = +proc modU64(a, b: int64): int64 {.compilerproc.} = result = abs(a) mod abs(b) -proc Ze(a: int): int {.compilerproc.} = - result = a -proc Ze64(a: int64): int64 {.compilerproc.} = +proc ze*(a: int): int {.compilerproc.} = result = a -proc ToU8(a: int): int8 {.noStackFrame, compilerproc.} = +proc ze64*(a: int64): int64 {.compilerproc.} = + result = a + +proc toU8*(a: int): int8 {.noStackFrame, compilerproc.} = asm """ return `a`; """ -proc ToU16(a: int): int16 {.noStackFrame, compilerproc.} = +proc toU16*(a: int): int16 {.noStackFrame, compilerproc.} = asm """ return `a`; """ -proc ToU32(a: int): int32 {.noStackFrame, compilerproc.} = +proc toU32*(a: int64): int32 {.noStackFrame, compilerproc.} = asm """ return `a`; """ - proc nimMin(a, b: int): int {.compilerproc.} = return if a <= b: a else: b proc nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b @@ -500,9 +499,9 @@ proc isFatPointer(ti: PNimType): bool = tyArray, tyArrayConstr, tyTuple, tyOpenArray, tySet, tyVar, tyRef, tyPtr} -proc NimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.} +proc nimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.} -proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} = +proc nimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} = case n.kind of nkNone: sysAssert(false, "NimCopyAux") of nkSlot: @@ -518,7 +517,7 @@ proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} = } """ -proc NimCopy(x: pointer, ti: PNimType): pointer = +proc nimCopy(x: pointer, ti: PNimType): pointer = case ti.kind of tyPtr, tyRef, tyVar, tyNil: if not isFatPointer(ti): @@ -585,7 +584,7 @@ proc genericReset(x: Pointer, ti: PNimType): pointer {.compilerproc.} = else: result = nil -proc ArrayConstr(len: int, value: pointer, typ: PNimType): pointer {. +proc arrayConstr(len: int, value: pointer, typ: PNimType): pointer {. noStackFrame, compilerproc.} = # types are fake asm """ diff --git a/tests/generics/tgenericlambda.nim b/tests/generics/tgenericlambda.nim index a71c592c59..f7aafe1d90 100644 --- a/tests/generics/tgenericlambda.nim +++ b/tests/generics/tgenericlambda.nim @@ -1,5 +1,5 @@ discard """ - output: "10\n10" + output: "10\n10\n1\n2\n3" """ proc test(x: proc (a, b: int): int) = @@ -8,3 +8,11 @@ proc test(x: proc (a, b: int): int) = test(proc (a, b): auto = a + b) test do (a, b) -> auto: a + b + +proc foreach[T](s: seq[T], body: proc(x: T)) = + for e in s: + body(e) + +foreach(@[1,2,3]) do (x): + echo x + diff --git a/tests/global/globalaux.nim b/tests/global/globalaux.nim new file mode 100644 index 0000000000..5f6f727210 --- /dev/null +++ b/tests/global/globalaux.nim @@ -0,0 +1,15 @@ +type + TObj*[T] = object + val*: T + +var + totalGlobals* = 0 + +proc makeObj[T](x: T): TObj[T] = + totalGlobals += 1 + result.val = x + +proc globalInstance*[T]: var TObj[T] = + var g {.global.} = when T is int: makeObj(10) else: makeObj("hello") + result = g + diff --git a/tests/global/globalaux2.nim b/tests/global/globalaux2.nim new file mode 100644 index 0000000000..6c77f1f485 --- /dev/null +++ b/tests/global/globalaux2.nim @@ -0,0 +1,4 @@ +import globalaux + +echo "in globalaux2: ", globalInstance[int]().val + diff --git a/tests/module/trecinca.nim b/tests/module/trecinca.nim index 73a0ec9376..62d37783ce 100644 --- a/tests/module/trecinca.nim +++ b/tests/module/trecinca.nim @@ -1,7 +1,7 @@ discard """ file: "tests/reject/trecincb.nim" line: 9 - errormsg: "recursive dependency: 'tests/reject/trecincb.nim'" + errormsg: "recursive dependency: 'tests/module/trecincb.nim'" """ # Test recursive includes diff --git a/tests/module/trecincb.nim b/tests/module/trecincb.nim index 9dd7d51de4..a2934052f9 100644 --- a/tests/module/trecincb.nim +++ b/tests/module/trecincb.nim @@ -1,7 +1,7 @@ discard """ file: "trecincb.nim" line: 9 - errormsg: "recursive dependency: 'tests/reject/trecincb.nim'" + errormsg: "recursive dependency: 'tests/module/trecincb.nim'" """ # Test recursive includes diff --git a/tests/stdlib/tircbot.nim b/tests/stdlib/tircbot.nim index 71ecb0b48e..f0417c7ac6 100644 --- a/tests/stdlib/tircbot.nim +++ b/tests/stdlib/tircbot.nim @@ -183,7 +183,7 @@ type channel: string timestamp: TTime case kind*: TSeenType - of PSeenJoin: discard + of PSeenJoin: nil of PSeenPart, PSeenQuit, PSeenMsg: msg: string of PSeenNick: diff --git a/tests/sunset.tmpl b/tests/template/sunset.tmpl similarity index 100% rename from tests/sunset.tmpl rename to tests/template/sunset.tmpl diff --git a/tests/typerel/tvoid.nim b/tests/typerel/tvoid.nim index bb569e7f87..d319362176 100644 --- a/tests/typerel/tvoid.nim +++ b/tests/typerel/tvoid.nim @@ -1,5 +1,9 @@ discard """ - output: "he, no return type;abc a string" + output: '''12 +empty +he, no return type; +abc a string +ha''' """ proc ReturnT[T](x: T): T = From 0bbf6081d00e83329021a0051d92c3433fc8a00f Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 18 Feb 2014 20:04:58 +0200 Subject: [PATCH 07/23] fix #931 and few more tests --- compiler/semtypes.nim | 1 - compiler/semtypinst.nim | 9 ++++++--- tests/metatype/tbindtypedesc.nim | 23 ++++++++++------------- tests/threads/nimrod.cfg | 1 + 4 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 tests/threads/nimrod.cfg diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 98abaf0059..0eba602cc9 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -705,7 +705,6 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, # result.rawAddSon(copyType(paramType.sons[i], getCurrOwner(), true)) result = instGenericContainer(c, paramType.sym.info, result, allowMetaTypes = true) - result.lastSon.shouldHaveMeta result = newTypeWithSons(c, tyCompositeTypeClass, @[paramType, result]) result = addImplicitGeneric(result) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index f08214f1ed..22edc6e329 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -220,7 +220,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType = # is difficult to handle: var body = t.sons[0] if body.kind != tyGenericBody: internalError(cl.info, "no generic body") - var header: PType = nil + var header: PType = t # search for some instantiation here: if cl.allowMetaTypes: result = PType(idTableGet(cl.localCache, t)) @@ -232,11 +232,13 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType = if x.kind == tyGenericParam: x = lookupTypeVar(cl, x) if x != nil: - if header == nil: header = instCopyType(cl, t) + if header == t: header = instCopyType(cl, t) header.sons[i] = x propagateToOwner(header, x) + else: + propagateToOwner(header, x) - if header != nil: + if header != t: # search again after first pass: result = searchInstTypes(header) if result != nil: return @@ -244,6 +246,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType = header = instCopyType(cl, t) result = newType(tyGenericInst, t.sons[0].owner) + result.flags = header.flags # be careful not to propagate unnecessary flags here (don't use rawAddSon) result.sons = @[header.sons[0]] # ugh need another pass for deeply recursive generic types (e.g. PActor) diff --git a/tests/metatype/tbindtypedesc.nim b/tests/metatype/tbindtypedesc.nim index 5ea8cf063f..84527362f0 100644 --- a/tests/metatype/tbindtypedesc.nim +++ b/tests/metatype/tbindtypedesc.nim @@ -1,10 +1,10 @@ discard """ - msg: ''' -int -float -TFoo -TFoo -''' + msg: '''int int +float float +int int +TFoo TFoo +int float +TFoo TFoo''' """ import typetraits @@ -24,9 +24,8 @@ template reject(e: expr) = proc genericParamRepeated[T: typedesc](a: T, b: T) = static: - echo a.name - echo b.name - + echo a.name, " ", b.name + accept genericParamRepeated(int, int) accept genericParamRepeated(float, float) @@ -35,8 +34,7 @@ reject genericParamRepeated(int, float) proc genericParamOnce[T: typedesc](a, b: T) = static: - echo a.name - echo b.name + echo a.name, " ", b.name accept genericParamOnce(int, int) accept genericParamOnce(TFoo, TFoo) @@ -68,8 +66,7 @@ reject typePairs2(string, int, TBAR, TBAR) proc dontBind(a: typedesc, b: typedesc) = static: - echo a.name - echo b.name + echo a.name, " ", b.name accept dontBind(int, float) accept dontBind(TFoo, TFoo) diff --git a/tests/threads/nimrod.cfg b/tests/threads/nimrod.cfg new file mode 100644 index 0000000000..b81c897213 --- /dev/null +++ b/tests/threads/nimrod.cfg @@ -0,0 +1 @@ +threads:on From 1f376d8594d9e20aa20b900853a166cbb49af5bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Tue, 18 Feb 2014 20:22:40 +0100 Subject: [PATCH 08/23] osproc: use push stacktrace:off instead of nostackframe --- lib/pure/osproc.nim | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index aa2f6f937d..93d737aa69 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -606,10 +606,11 @@ elif not defined(useNimRtl): optionPoStdErrToStdOut: bool proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} - proc startProcessAuxFork(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} + {.push stacktrace: off, profiler: off.} proc startProcessAfterFork(data: ptr TStartProcessData) {. - tags: [FExecIO, FReadEnv], noStackFrame, cdecl.} + tags: [FExecIO, FReadEnv], cdecl.} + {.pop.} proc startProcess(command: string, workingDir: string = "", @@ -774,7 +775,8 @@ elif not defined(useNimRtl): return pid - proc startProcessFail(data: ptr TStartProcessData) {.noStackFrame.} = + {.push stacktrace: off, profiler: off.} + proc startProcessFail(data: ptr TStartProcessData) = var error: cint = errno discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error)) exitnow(1) @@ -811,6 +813,7 @@ elif not defined(useNimRtl): discard execve(data.sysCommand, data.sysArgs, data.sysEnv) startProcessFail(data) + {.pop} proc close(p: PProcess) = if p.inStream != nil: close(p.inStream) From 1a6d05515ff96e8bba294b352493cb7da2794a96 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Thu, 20 Feb 2014 23:33:58 +0200 Subject: [PATCH 09/23] fix #945 --- compiler/semstmts.nim | 9 +++++---- tests/metatype/tusertypeclasses.nim | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index a9a9079530..6c4e29f297 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -143,10 +143,11 @@ proc discardCheck(c: PContext, result: PNode) = while n.kind in skipForDiscardable: n = n.lastSon n.typ = nil - elif c.inTypeClass > 0 and result.typ.kind == tyBool: - let verdict = semConstExpr(c, result) - if verdict.intVal == 0: - localError(result.info, "type class predicate failed") + elif c.inTypeClass > 0: + if result.typ.kind == tyBool: + let verdict = semConstExpr(c, result) + if verdict.intVal == 0: + localError(result.info, "type class predicate failed") elif result.typ.kind != tyError and gCmd != cmdInteractive: if result.typ.kind == tyNil: fixNilType(result) diff --git a/tests/metatype/tusertypeclasses.nim b/tests/metatype/tusertypeclasses.nim index 4c8c0fc56a..5b04c490f2 100644 --- a/tests/metatype/tusertypeclasses.nim +++ b/tests/metatype/tusertypeclasses.nim @@ -31,9 +31,10 @@ proc intval(x: int) = discard # check real and virtual fields type TFoo = generic T - intval T.x + T.x + y(T) intval T.y - + proc y(x: TObj): int = 10 proc testFoo(x: TFoo) = discard From 12c95f31dcd6587d09f9eeedfd58d1ec96189abd Mon Sep 17 00:00:00 2001 From: Grzegorz Adam Hankiewicz Date: Thu, 20 Feb 2014 23:32:03 +0100 Subject: [PATCH 10/23] Documents system.readAll() limitations. Refs #298. --- lib/system.nim | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index 2acb989c5f..b683a49551 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2014,8 +2014,10 @@ when not defined(JS): #and not defined(NimrodVM): ## Flushes `f`'s buffer. proc readAll*(file: TFile): TaintedString {.tags: [FReadIO].} - ## Reads all data from the stream `file`. Raises an IO exception - ## in case of an error + ## Reads all data from the stream `file`. + ## + ## Raises an IO exception in case of an error. It is an error if the + ## current file position is not at the beginning of the file. proc readFile*(filename: string): TaintedString {.tags: [FReadIO].} ## Opens a file named `filename` for reading. Then calls `readAll` From adb390af4bfbb3549672280007e9c46bf7a223c2 Mon Sep 17 00:00:00 2001 From: Grzegorz Adam Hankiewicz Date: Fri, 21 Feb 2014 00:32:28 +0100 Subject: [PATCH 11/23] Checks that exported symbols are valid C identifiers. Refs #800. --- compiler/msgs.nim | 3 ++- compiler/pragmas.nim | 25 ++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 0140d1ac4d..8c41a8191a 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -105,7 +105,7 @@ type errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX, errXCannotBeClosure, errXMustBeCompileTime, errCannotInferTypeOfTheLiteral, - errUser, + errBadExport, errUser warnCannotOpenFile, warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, warnDeprecated, warnConfigDeprecated, @@ -350,6 +350,7 @@ const errXCannotBeClosure: "'$1' cannot have 'closure' calling convention", errXMustBeCompileTime: "'$1' can only be used in compile-time context", errCannotInferTypeOfTheLiteral: "cannot infer the type of the $1", + errBadExport: "invalid exported symbol, $1", errUser: "$1", warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]", warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]", diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index d9ed50cfeb..6e21a85b9b 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -97,8 +97,27 @@ proc makeExternImport(s: PSym, extname: string) = incl(s.flags, sfImportc) excl(s.flags, sfForward) -proc makeExternExport(s: PSym, extname: string) = +const invalidIdentChars = AllChars - IdentChars + +proc validateExternName(s: PSym, info: TLineInfo) = + ## Validates that the symbol name in s.loc.r is a valid C identifier. + ## + ## Valid identifiers are those alphanumeric including the underscore not + ## starting with a number. If the check fails, a friendly error will be + ## displayed to the user. + let target = ropeToStr(s.loc.r) + if target.len < 1: + localError(info, errBadExport, "can't be zero length") + if not (target[0] in IdentStartChars): + localError(info, errBadExport, "'" & target & "' can't start with a number") + if not target.allCharsInSet(IdentChars): + let pos = target.find(invalidIdentChars, 1) + localError(info, errBadExport, "'" & target & + "' contains bad character at byte " & $pos) + +proc makeExternExport(s: PSym, extname: string, info: TLineInfo) = setExternName(s, extname) + validateExternName(s, info) incl(s.flags, sfExportc) proc processImportCompilerProc(s: PSym, extname: string) = @@ -515,7 +534,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, if k in validPragmas: case k of wExportc: - makeExternExport(sym, getOptionalStr(c, it, "$1")) + makeExternExport(sym, getOptionalStr(c, it, "$1"), it.info) incl(sym.flags, sfUsed) # avoid wrong hints of wImportc: makeExternImport(sym, getOptionalStr(c, it, "$1")) of wImportCompilerProc: @@ -601,7 +620,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, processDynLib(c, it, sym) of wCompilerproc: noVal(it) # compilerproc may not get a string! - makeExternExport(sym, "$1") + makeExternExport(sym, "$1", it.info) incl(sym.flags, sfCompilerProc) incl(sym.flags, sfUsed) # suppress all those stupid warnings registerCompilerProc(sym) From 6621454dec0492044f76d1c78e9df7682048b83b Mon Sep 17 00:00:00 2001 From: Fabio Cevasco Date: Fri, 21 Feb 2014 14:14:43 +0100 Subject: [PATCH 12/23] pegs.findAll iterator fix Modified the findAll iterator so that it continues looking for a match within the input string (bug?). --- lib/pure/pegs.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index 70b617393a..3b1516e17f 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -836,7 +836,9 @@ iterator findAll*(s: string, pattern: TPeg, start = 0): string = while i < s.len: c.ml = 0 var L = rawMatch(s, pattern, i, c) - if L < 0: break + if L < 0: + inc(i, 1) + continue yield substr(s, i, i+L-1) inc(i, L) From 067c3816ba317d2d2bcafdd3c2806df886b855b2 Mon Sep 17 00:00:00 2001 From: Fabio Cevasco Date: Sat, 22 Feb 2014 10:07:37 +0100 Subject: [PATCH 13/23] Rewrote the changes to findAll using if/else --- lib/pure/pegs.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pure/pegs.nim b/lib/pure/pegs.nim index 3b1516e17f..68b1ab2237 100644 --- a/lib/pure/pegs.nim +++ b/lib/pure/pegs.nim @@ -838,9 +838,9 @@ iterator findAll*(s: string, pattern: TPeg, start = 0): string = var L = rawMatch(s, pattern, i, c) if L < 0: inc(i, 1) - continue - yield substr(s, i, i+L-1) - inc(i, L) + else: + yield substr(s, i, i+L-1) + inc(i, L) proc findAll*(s: string, pattern: TPeg, start = 0): seq[string] {. nosideEffect, rtl, extern: "npegs$1".} = From 3aa7f65240005a7d5ce26519248fa435f3ed3e5b Mon Sep 17 00:00:00 2001 From: Grzegorz Adam Hankiewicz Date: Sat, 22 Feb 2014 10:18:33 +0100 Subject: [PATCH 14/23] Addresses issues raised on #947. Refs #800. * Uses errGenerated instead of deprecated extending of enums. * Reduces bloat and usefulness of end user error messages. * Limits checks to C, Cpp and Objc targets. --- compiler/msgs.nim | 3 +-- compiler/pragmas.nim | 20 +++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 8c41a8191a..0140d1ac4d 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -105,7 +105,7 @@ type errThreadvarCannotInit, errWrongSymbolX, errIllegalCaptureX, errXCannotBeClosure, errXMustBeCompileTime, errCannotInferTypeOfTheLiteral, - errBadExport, errUser + errUser, warnCannotOpenFile, warnOctalEscape, warnXIsNeverRead, warnXmightNotBeenInit, warnDeprecated, warnConfigDeprecated, @@ -350,7 +350,6 @@ const errXCannotBeClosure: "'$1' cannot have 'closure' calling convention", errXMustBeCompileTime: "'$1' can only be used in compile-time context", errCannotInferTypeOfTheLiteral: "cannot infer the type of the $1", - errBadExport: "invalid exported symbol, $1", errUser: "$1", warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]", warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]", diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 6e21a85b9b..75c16f6cde 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -99,25 +99,23 @@ proc makeExternImport(s: PSym, extname: string) = const invalidIdentChars = AllChars - IdentChars -proc validateExternName(s: PSym, info: TLineInfo) = +proc validateExternCName(s: PSym, info: TLineInfo) = ## Validates that the symbol name in s.loc.r is a valid C identifier. ## ## Valid identifiers are those alphanumeric including the underscore not - ## starting with a number. If the check fails, a friendly error will be + ## starting with a number. If the check fails, a generic error will be ## displayed to the user. let target = ropeToStr(s.loc.r) - if target.len < 1: - localError(info, errBadExport, "can't be zero length") - if not (target[0] in IdentStartChars): - localError(info, errBadExport, "'" & target & "' can't start with a number") - if not target.allCharsInSet(IdentChars): - let pos = target.find(invalidIdentChars, 1) - localError(info, errBadExport, "'" & target & - "' contains bad character at byte " & $pos) + if target.len < 1 or (not (target[0] in IdentStartChars)) or + (not target.allCharsInSet(IdentChars)): + localError(info, errGenerated, "invalid exported symbol") proc makeExternExport(s: PSym, extname: string, info: TLineInfo) = setExternName(s, extname) - validateExternName(s, info) + case gCmd + of cmdCompileToC, cmdCompileToCpp, cmdCompileToOC: + validateExternCName(s, info) + else: discard incl(s.flags, sfExportc) proc processImportCompilerProc(s: PSym, extname: string) = From 3b5825e9bcf09bb6da98601d07af10c2640f4cd1 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 22 Feb 2014 17:22:19 +0000 Subject: [PATCH 15/23] Implemented selector support for asyncio2. --- lib/posix/epoll.nim | 8 +- lib/posix/posix.nim | 2 +- lib/pure/asyncio2.nim | 231 ++++++++++++++++++++--- lib/pure/net.nim | 17 +- lib/pure/selectors.nim | 357 ++++++++++++++++++------------------ lib/pure/sockets2.nim | 8 +- tests/async/tasyncawait.nim | 4 +- 7 files changed, 416 insertions(+), 211 deletions(-) diff --git a/lib/posix/epoll.nim b/lib/posix/epoll.nim index d50394f604..3665215510 100644 --- a/lib/posix/epoll.nim +++ b/lib/posix/epoll.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +from posix import TSocketHandle + const EPOLLIN* = 0x00000001 EPOLLPRI* = 0x00000002 @@ -33,8 +35,8 @@ const type epoll_data* {.importc: "union epoll_data", header: "", pure, final.} = object # TODO: This is actually a union. - thePtr* {.importc: "ptr".}: pointer # \ - #fd*: cint + #thePtr* {.importc: "ptr".}: pointer + fd*: cint # \ #u32*: uint32 #u64*: uint64 @@ -54,7 +56,7 @@ proc epoll_create1*(flags: cint): cint {.importc: "epoll_create1", ## Same as epoll_create but with an FLAGS parameter. The unused SIZE ## parameter has been dropped. -proc epoll_ctl*(epfd: cint; op: cint; fd: cint; event: ptr epoll_event): cint {. +proc epoll_ctl*(epfd: cint; op: cint; fd: cint | TSocketHandle; event: ptr epoll_event): cint {. importc: "epoll_ctl", header: "".} ## Manipulate an epoll instance "epfd". Returns 0 in case of success, ## -1 in case of error ( the "errno" variable will contain the diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 41260b36fc..bb4039c1bf 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -2356,7 +2356,7 @@ proc FD_ZERO*(a1: var TFdSet) {.importc, header: "".} proc pselect*(a1: cint, a2, a3, a4: ptr TFdSet, a5: ptr Ttimespec, a6: var Tsigset): cint {.importc, header: "".} -proc select*(a1: cint, a2, a3, a4: ptr TFdSet, a5: ptr Ttimeval): cint {. +proc select*(a1: cint | TSocketHandle, a2, a3, a4: ptr TFdSet, a5: ptr Ttimeval): cint {. importc, header: "".} when hasSpawnH: diff --git a/lib/pure/asyncio2.nim b/lib/pure/asyncio2.nim index 8541b2ba74..12d4cb5a31 100644 --- a/lib/pure/asyncio2.nim +++ b/lib/pure/asyncio2.nim @@ -9,8 +9,6 @@ import os, oids, tables, strutils, macros -import winlean - import sockets2, net ## Asyncio2 @@ -93,7 +91,10 @@ proc failed*[T](future: PFuture[T]): bool = ## Determines whether ``future`` completed with an error. future.error != nil -when defined(windows): +# TODO: Get rid of register. Do it implicitly. + +when defined(windows) or defined(nimdoc): + import winlean type TCompletionKey = dword @@ -293,7 +294,10 @@ when defined(windows): proc recv*(p: PDispatcher, socket: TSocketHandle, size: int, flags: int = 0): PFuture[string] = ## Reads ``size`` bytes from ``socket``. Returned future will complete once - ## all of the requested data is read. + ## all of the requested data is read. If socket is disconnected during the + ## recv operation then the future may complete with only a part of the + ## requested data read. If socket is disconnected and no data is available + ## to be read then the future will complete with a value of ``""``. var retFuture = newFuture[string]() @@ -448,24 +452,206 @@ when defined(windows): return retFuture - proc accept*(p: PDispatcher, socket: TSocketHandle): PFuture[TSocketHandle] = - ## Accepts a new connection. Returns a future containing the client socket - ## corresponding to that connection. - ## The future will complete when the connection is successfully accepted. - var retFut = newFuture[TSocketHandle]() - var fut = p.acceptAddr(socket) - fut.callback = - proc (future: PFuture[tuple[address: string, client: TSocketHandle]]) = - assert future.finished - if future.failed: - retFut.fail(future.error) - else: - retFut.complete(future.read.client) - return retFut - initAll() else: - # TODO: Selectors. + import selectors + from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK + type + TCallback = proc (sock: TSocketHandle): bool {.closure.} + + PData* = ref object of PObject + sock: TSocketHandle + readCBs: seq[TCallback] + writeCBs: seq[TCallback] + + PDispatcher* = ref object + selector: PSelector + + proc newDispatcher*(): PDispatcher = + new result + result.selector = newSelector() + + proc update(p: PDispatcher, sock: TSocketHandle, events: set[TEvent]) = + assert sock in p.selector + echo("Update: ", events) + if events == {}: + discard p.selector.unregister(sock) + else: + discard p.selector.update(sock, events) + + proc addRead(p: PDispatcher, sock: TSocketHandle, cb: TCallback) = + if sock notin p.selector: + var data = PData(sock: sock, readCBs: @[cb], writeCBs: @[]) + p.selector.register(sock, {EvRead}, data.PObject) + else: + p.selector[sock].data.PData.readCBs.add(cb) + p.update(sock, p.selector[sock].events + {EvRead}) + + proc addWrite(p: PDispatcher, sock: TSocketHandle, cb: TCallback) = + if sock notin p.selector: + var data = PData(sock: sock, readCBs: @[], writeCBs: @[cb]) + p.selector.register(sock, {EvWrite}, data.PObject) + else: + p.selector[sock].data.PData.writeCBs.add(cb) + p.update(sock, p.selector[sock].events + {EvWrite}) + + proc poll*(p: PDispatcher, timeout = 500) = + for info in p.selector.select(timeout): + let data = PData(info.key.data) + assert data.sock == info.key.fd + echo("R: ", data.readCBs.len, " W: ", data.writeCBs.len, ". ", info.events) + + if EvRead in info.events: + var newReadCBs: seq[TCallback] = @[] + for cb in data.readCBs: + if not cb(data.sock): + # Callback wants to be called again. + newReadCBs.add(cb) + data.readCBs = newReadCBs + + if EvWrite in info.events: + var newWriteCBs: seq[TCallback] = @[] + for cb in data.writeCBs: + if not cb(data.sock): + # Callback wants to be called again. + newWriteCBs.add(cb) + data.writeCBs = newWriteCBs + + var newEvents: set[TEvent] + if data.readCBs.len != 0: newEvents = {EvRead} + if data.writeCBs.len != 0: newEvents = newEvents + {EvWrite} + p.update(data.sock, newEvents) + + proc connect*(p: PDispatcher, socket: TSocketHandle, address: string, port: TPort, + af = AF_INET): PFuture[int] = + var retFuture = newFuture[int]() + + proc cb(sock: TSocketHandle): bool = + # We have connected. + retFuture.complete(0) + return true + + var aiList = getAddrInfo(address, port, af) + var success = false + var lastError: TOSErrorCode + var it = aiList + while it != nil: + var ret = connect(socket, it.ai_addr, it.ai_addrlen.TSocklen) + if ret == 0: + # Request to connect completed immediately. + success = true + retFuture.complete(0) + break + else: + lastError = osLastError() + if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS: + success = true + addWrite(p, socket, cb) + break + else: + success = false + it = it.ai_next + + dealloc(aiList) + if not success: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + return retFuture + + proc recv*(p: PDispatcher, socket: TSocketHandle, size: int, + flags: int = 0): PFuture[string] = + var retFuture = newFuture[string]() + + var readBuffer = newString(size) + var sizeRead = 0 + + proc cb(sock: TSocketHandle): bool = + result = true + let netSize = size - sizeRead + let res = recv(sock, addr readBuffer[sizeRead], netSize, flags.cint) + if res < 0: + let lastError = osLastError() + if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + else: + result = false # We still want this callback to be called. + elif res == 0: + # Disconnected + if sizeRead == 0: + retFuture.complete("") + else: + readBuffer.setLen(sizeRead) + retFuture.complete(readBuffer) + else: + sizeRead.inc(res) + if res != netSize: + result = false # We want to read all the data requested. + else: + retFuture.complete(readBuffer) + + addRead(p, socket, cb) + return retFuture + + proc send*(p: PDispatcher, socket: TSocketHandle, data: string): PFuture[int] = + var retFuture = newFuture[int]() + + var written = 0 + + proc cb(sock: TSocketHandle): bool = + result = true + let netSize = data.len-written + var d = data.cstring + let res = send(sock, addr d[written], netSize, 0.cint) + if res < 0: + let lastError = osLastError() + if lastError.int32 notin {EINTR, EWOULDBLOCK, EAGAIN}: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + else: + result = false # We still want this callback to be called. + else: + written.inc(res) + if res != netSize: + result = false # We still have data to send. + else: + retFuture.complete(0) + addWrite(p, socket, cb) + return retFuture + + + proc acceptAddr*(p: PDispatcher, socket: TSocketHandle): + PFuture[tuple[address: string, client: TSocketHandle]] = + var retFuture = newFuture[tuple[address: string, client: TSocketHandle]]() + proc cb(sock: TSocketHandle): bool = + result = true + var sockAddress: Tsockaddr_in + var addrLen = sizeof(sockAddress).TSocklen + var client = accept(sock, cast[ptr TSockAddr](addr(sockAddress)), + addr(addrLen)) + if client == osInvalidSocket: + let lastError = osLastError() + assert lastError.int32 notin {EWOULDBLOCK, EAGAIN} + if lastError.int32 == EINTR: + return false + else: + retFuture.fail(newException(EOS, osErrorMsg(lastError))) + else: + retFuture.complete(($inet_ntoa(sockAddress.sin_addr), client)) + addRead(p, socket, cb) + return retFuture + +proc accept*(p: PDispatcher, socket: TSocketHandle): PFuture[TSocketHandle] = + ## Accepts a new connection. Returns a future containing the client socket + ## corresponding to that connection. + ## The future will complete when the connection is successfully accepted. + var retFut = newFuture[TSocketHandle]() + var fut = p.acceptAddr(socket) + fut.callback = + proc (future: PFuture[tuple[address: string, client: TSocketHandle]]) = + assert future.finished + if future.failed: + retFut.fail(future.error) + else: + retFut.complete(future.read.client) + return retFut # -- Await Macro @@ -665,8 +851,7 @@ when isMainModule: var p = newDispatcher() var sock = socket() - #sock.setBlocking false - p.register(sock) + sock.setBlocking false when false: @@ -706,7 +891,7 @@ when isMainModule: var recvF = p.recv(sock, 10) recvF.callback = proc (future: PFuture[string]) = - echo("Read: ", future.read) + echo("Read ", future.read.len, ": ", future.read.repr) else: diff --git a/lib/pure/net.nim b/lib/pure/net.nim index bdcae677e8..0ec007009c 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -37,4 +37,19 @@ proc bindAddr*(socket: TSocket, port = TPort(0), address = "") {. if bindAddr(socket, aiList.ai_addr, aiList.ai_addrlen.TSocklen) < 0'i32: dealloc(aiList) osError(osLastError()) - dealloc(aiList) \ No newline at end of file + dealloc(aiList) + +proc setBlocking*(s: TSocket, blocking: bool) {.tags: [].} = + ## Sets blocking mode on socket + when defined(Windows): + var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking + if ioctlsocket(s, FIONBIO, addr(mode)) == -1: + osError(osLastError()) + else: # BSD sockets + var x: int = fcntl(s, F_GETFL, 0) + if x == -1: + osError(osLastError()) + else: + var mode = if blocking: x and not O_NONBLOCK else: x or O_NONBLOCK + if fcntl(s, F_SETFL, mode) == -1: + osError(osLastError()) \ No newline at end of file diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index 83c158da14..6482a01a6b 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2013 Dominik Picheta +# (c) Copyright 2014 Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -9,212 +9,211 @@ # TODO: Docs. -import tables, os, unsigned -when defined(windows): - import winlean -else: - import posix +import tables, os, unsigned, hashes + +when defined(linux): import posix, epoll +elif defined(windows): import winlean + +proc hash*(x: TSocketHandle): THash {.borrow.} type TEvent* = enum EvRead, EvWrite - TSelectorKey* = object - fd: cint - events: set[TEvent] - data: PObject + PSelectorKey* = ref object + fd*: TSocketHandle + events*: set[TEvent] ## The events which ``fd`` listens for. + data*: PObject ## User object. - TReadyInfo* = tuple[key: TSelectorKey, events: set[TEvent]] + TReadyInfo* = tuple[key: PSelectorKey, events: set[TEvent]] - PSelector* = ref object of PObject ## Selector interface. - fds*: TTable[cint, TSelectorKey] - registerImpl*: proc (s: PSelector, fd: cint, events: set[TEvent], - data: PObject): TSelectorKey {.nimcall, tags: [FWriteIO].} - unregisterImpl*: proc (s: PSelector, fd: cint): TSelectorKey {.nimcall, tags: [FWriteIO].} - selectImpl*: proc (s: PSelector, timeout: int): seq[TReadyInfo] {.nimcall, tags: [FReadIO].} - closeImpl*: proc (s: PSelector) {.nimcall.} - -template initSelector(r: expr) = - new r - r.fds = initTable[cint, TSelectorKey]() - -proc register*(s: PSelector, fd: cint, events: set[TEvent], data: PObject): - TSelectorKey = - if not s.registerImpl.isNil: result = s.registerImpl(s, fd, events, data) - -proc unregister*(s: PSelector, fd: cint): TSelectorKey = - ## - ## **Note:** For the ``epoll`` implementation the resulting ``TSelectorKey`` - ## will only have the ``fd`` field set. This is an optimisation and may - ## change in the future if a viable use case is presented. - if not s.unregisterImpl.isNil: result = s.unregisterImpl(s, fd) - -proc select*(s: PSelector, timeout = 500): seq[TReadyInfo] = - ## - ## The ``events`` field of the returned ``key`` contains the original events - ## for which the ``fd`` was bound. This is contrary to the ``events`` field - ## of the ``TReadyInfo`` tuple which determines which events are ready - ## on the ``fd``. - - if not s.selectImpl.isNil: result = s.selectImpl(s, timeout) - -proc close*(s: PSelector) = - if not s.closeImpl.isNil: s.closeImpl(s) - -# ---- Select() ---------------------------------------------------------------- - -type - PSelectSelector* = ref object of PSelector ## Implementation of select() - -proc ssRegister(s: PSelector, fd: cint, events: set[TEvent], - data: PObject): TSelectorKey = - if s.fds.hasKey(fd): - raise newException(EInvalidValue, "FD already exists in selector.") - var sk = TSelectorKey(fd: fd, events: events, data: data) - s.fds[fd] = sk - result = sk - -proc ssUnregister(s: PSelector, fd: cint): TSelectorKey = - result = s.fds[fd] - s.fds.del(fd) - -proc ssClose(s: PSelector) = nil - -proc timeValFromMilliseconds(timeout: int): TTimeVal = - if timeout != -1: - var seconds = timeout div 1000 - result.tv_sec = seconds.int32 - result.tv_usec = ((timeout - seconds * 1000) * 1000).int32 - -proc createFdSet(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey], - m: var int) = - FD_ZERO(rd); FD_ZERO(wr) - for k, v in pairs(fds): - if EvRead in v.events: - m = max(m, int(k)) - FD_SET(k, rd) - if EvWrite in v.events: - m = max(m, int(k)) - FD_SET(k, wr) - -proc getReadyFDs(rd, wr: var TFdSet, fds: TTable[cint, TSelectorKey]): - seq[TReadyInfo] = - result = @[] - for k, v in pairs(fds): - var events: set[TEvent] = {} - if FD_ISSET(k, rd) != 0'i32: - events = events + {EvRead} - if FD_ISSET(k, wr) != 0'i32: - events = events + {EvWrite} - result.add((v, events)) - -proc select(fds: TTable[cint, TSelectorKey], timeout = 500): - seq[TReadyInfo] = - var tv {.noInit.}: TTimeVal = timeValFromMilliseconds(timeout) - - var rd, wr: TFdSet - var m = 0 - createFdSet(rd, wr, fds, m) - - var retCode = 0 - if timeout != -1: - retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, addr(tv))) - else: - retCode = int(select(cint(m+1), addr(rd), addr(wr), nil, nil)) - - if retCode < 0: - OSError(OSLastError()) - elif retCode == 0: - return @[] - else: - return getReadyFDs(rd, wr, fds) - -proc ssSelect(s: PSelector, timeout: int): seq[TReadyInfo] = - result = select(s.fds, timeout) - -proc newSelectSelector*(): PSelectSelector = - initSelector(result) - result.registerImpl = ssRegister - result.unregisterImpl = ssUnregister - result.selectImpl = ssSelect - result.closeImpl = ssClose - -# ---- Epoll ------------------------------------------------------------------- - -when defined(linux): - import epoll +when defined(linux) or defined(nimdoc): type - PEpollSelector* = ref object of PSelector + PSelector* = ref object epollFD: cint events: array[64, ptr epoll_event] + fds: TTable[TSocketHandle, PSelectorKey] - TDataWrapper = object - fd: cint - boundEvents: set[TEvent] ## The events which ``fd`` listens for. - data: PObject ## User object. - - proc esRegister(s: PSelector, fd: cint, events: set[TEvent], - data: PObject): TSelectorKey = - var es = PEpollSelector(s) - var event: epoll_event + proc createEventStruct(events: set[TEvent], fd: TSocketHandle): epoll_event = if EvRead in events: - event.events = EPOLLIN + result.events = EPOLLIN if EvWrite in events: - event.events = event.events or EPOLLOUT - - var dw = cast[ptr TDataWrapper](alloc0(sizeof(TDataWrapper))) # TODO: This needs to be dealloc'd - dw.fd = fd - dw.boundEvents = events - dw.data = data - event.data.thePtr = dw - - if epoll_ctl(es.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: - OSError(OSLastError()) - - result = TSelectorKey(fd: fd, events: events, data: data) + result.events = result.events or EPOLLOUT + result.data.fd = fd.cint - proc esUnregister(s: PSelector, fd: cint): TSelectorKey = - # We cannot find out the information about this ``fd`` from the epoll - # context. As such I will simply return an almost empty TSelectorKey. - var es = PEpollSelector(s) - if epoll_ctl(es.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: + proc register*(s: PSelector, fd: TSocketHandle, events: set[TEvent], + data: PObject): PSelectorKey {.discardable.} = + ## Registers file descriptor ``fd`` to selector ``s`` with a set of TEvent + ## ``events``. + if s.fds.hasKey(fd): + raise newException(EInvalidValue, "File descriptor already exists.") + + var event = createEventStruct(events, fd) + + if epoll_ctl(s.epollFD, EPOLL_CTL_ADD, fd, addr(event)) != 0: OSError(OSLastError()) - # We could fill in the ``fds`` TTable to get the info, but that wouldn't - # be nice for our memory. - result = TSelectorKey(fd: fd, events: {}, data: nil) + + var key = PSelectorKey(fd: fd, events: events, data: data) + + s.fds[fd] = key + result = key + + proc update*(s: PSelector, fd: TSocketHandle, + events: set[TEvent]): PSelectorKey {.discardable.} = + ## Updates the events which ``fd`` wants notifications for. + if not s.fds.hasKey(fd): + raise newException(EInvalidValue, "File descriptor not found.") + var event = createEventStruct(events, fd) + + s.fds[fd].events = events + echo("About to update") + if epoll_ctl(s.epollFD, EPOLL_CTL_MOD, fd, addr(event)) != 0: + OSError(OSLastError()) + echo("finished updating") + result = s.fds[fd] + + proc unregister*(s: PSelector, fd: TSocketHandle): PSelectorKey {.discardable.} = + if not s.fds.hasKey(fd): + raise newException(EInvalidValue, "File descriptor not found.") + if epoll_ctl(s.epollFD, EPOLL_CTL_DEL, fd, nil) != 0: + OSError(OSLastError()) + result = s.fds[fd] + s.fds.del(fd) - proc esClose(s: PSelector) = - var es = PEpollSelector(s) - if es.epollFD.close() != 0: OSError(OSLastError()) - dealloc(addr es.events) # TODO: Test this + proc close*(s: PSelector) = + if s.epollFD.close() != 0: OSError(OSLastError()) + dealloc(addr s.events) # TODO: Test this - proc esSelect(s: PSelector, timeout: int): seq[TReadyInfo] = + proc select*(s: PSelector, timeout: int): seq[TReadyInfo] = + ## + ## The ``events`` field of the returned ``key`` contains the original events + ## for which the ``fd`` was bound. This is contrary to the ``events`` field + ## of the ``TReadyInfo`` tuple which determines which events are ready + ## on the ``fd``. result = @[] - var es = PEpollSelector(s) - let evNum = epoll_wait(es.epollFD, es.events[0], 64.cint, timeout.cint) + let evNum = epoll_wait(s.epollFD, s.events[0], 64.cint, timeout.cint) if evNum < 0: OSError(OSLastError()) if evNum == 0: return @[] for i in 0 .. 0: echo ready[0].events i.inc if i == 6: + assert selector.unregister(sock.getFD).fd == sock.getFD selector.close() break diff --git a/lib/pure/sockets2.nim b/lib/pure/sockets2.nim index f8284b3392..031217b904 100644 --- a/lib/pure/sockets2.nim +++ b/lib/pure/sockets2.nim @@ -17,11 +17,13 @@ when hostos == "solaris": when defined(Windows): import winlean + export ioctlsocket else: import posix + export fcntl, F_GETFL, O_NONBLOCK, F_SETFL export TSocketHandle, TSockaddr_in, TAddrinfo, INADDR_ANY, TSockAddr, TSockLen, - inet_ntoa + inet_ntoa, recv, `==`, connect, send, accept type @@ -63,10 +65,10 @@ type when defined(windows): let - OSInvalidSocket* = winlean.INVALID_SOCKET + osInvalidSocket* = winlean.INVALID_SOCKET else: let - OSInvalidSocket* = posix.INVALID_SOCKET + osInvalidSocket* = posix.INVALID_SOCKET proc `==`*(a, b: TPort): bool {.borrow.} ## ``==`` for ports. diff --git a/tests/async/tasyncawait.nim b/tests/async/tasyncawait.nim index bcaffc2875..bde5bf8c8d 100644 --- a/tests/async/tasyncawait.nim +++ b/tests/async/tasyncawait.nim @@ -21,7 +21,7 @@ proc sendMessages(disp: PDispatcher, client: TSocketHandle): PFuture[int] {.asyn proc launchSwarm(disp: PDispatcher, port: TPort): PFuture[int] {.async.} = for i in 0 .. Date: Sun, 23 Feb 2014 00:19:18 +0100 Subject: [PATCH 16/23] Added tougher test case for return within finally statement. --- tests/exception/tfinally4.nim | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/exception/tfinally4.nim diff --git a/tests/exception/tfinally4.nim b/tests/exception/tfinally4.nim new file mode 100644 index 0000000000..05c57c4f5c --- /dev/null +++ b/tests/exception/tfinally4.nim @@ -0,0 +1,40 @@ +discard """ + file: "tfinally4.nim" + output: "B1\nA1\n1\nB1\nB2\ncatch\nA1\n1\nB1\nA1\nA2\n2\nB1\nB2\ncatch\nA1\nA2\n0\nB1\nA1\n1\nB1\nB2\nA1\n1\nB1\nA1\nA2\n2\nB1\nB2\nA1\nA2\n3" +""" + +# More thorough test of return-in-finaly + +var raiseEx = true +var returnA = true +var returnB = false + +proc main: int = + try: #A + try: #B + if raiseEx: + raise newException(EOS, "") + return 3 + finally: #B + echo "B1" + if returnB: + return 2 + echo "B2" + except EOS: #A + echo "catch" + finally: #A + echo "A1" + if returnA: + return 1 + echo "A2" + +for x in [true, false]: + for y in [true, false]: + for z in [true, false]: + # echo "raiseEx: " & $x + # echo "returnA: " & $y + # echo "returnB: " & $z + raiseEx = x + returnA = y + returnB = z + echo main() From ef379d0a10e800982d4a10ad623d2426d68e830d Mon Sep 17 00:00:00 2001 From: Audun Wilhelmsen Date: Sun, 23 Feb 2014 00:20:16 +0100 Subject: [PATCH 17/23] Added test cases for return in except statements. --- tests/exception/tnestedreturn.nim | 40 ++++++++++++++++++++++++++++++ tests/exception/tnestedreturn2.nim | 20 +++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/exception/tnestedreturn.nim create mode 100644 tests/exception/tnestedreturn2.nim diff --git a/tests/exception/tnestedreturn.nim b/tests/exception/tnestedreturn.nim new file mode 100644 index 0000000000..b9f7843f6e --- /dev/null +++ b/tests/exception/tnestedreturn.nim @@ -0,0 +1,40 @@ +discard """ + file: "tnestedreturn.nim" + output: "A\nB\nC\n" +""" + +# Various tests of return nested in double try/except statements + +proc test1() = + + finally: echo "A" + + try: + raise newException(EOS, "Problem") + except EOS: + return + +test1() + + +proc test2() = + + finally: echo "B" + + try: + return + except EOS: + discard + +test2() + +proc test3() = + try: + try: + raise newException(EOS, "Problem") + except EOS: + return + finally: + echo "C" + +test3() diff --git a/tests/exception/tnestedreturn2.nim b/tests/exception/tnestedreturn2.nim new file mode 100644 index 0000000000..14a2dab927 --- /dev/null +++ b/tests/exception/tnestedreturn2.nim @@ -0,0 +1,20 @@ +discard """ + file: "tnestedreturn.nim" + outputsub: "Error: unhandled exception: Problem [EOS]" + exitcode: "1" +""" + +proc test4() = + try: + try: + raise newException(EOS, "Problem") + except EOS: + return + finally: + discard + +# Should cause unhandled exception error, +# but could cause segmentation fault if +# exceptions are not handled properly. +test4() +raise newException(EOS, "Problem") From 739b4f214b62f55f5fcfc8c71a246c385146fca8 Mon Sep 17 00:00:00 2001 From: Audun Wilhelmsen Date: Sun, 23 Feb 2014 00:23:06 +0100 Subject: [PATCH 18/23] Fixed #688 : return in except statments. Also fixed return in finally statements. --- compiler/ccgstmts.nim | 41 +++++++++++++++++++++++++---------------- compiler/cgendata.nim | 7 +++++-- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 443d845f63..4576a54b5a 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -263,33 +263,33 @@ proc genIf(p: BProc, n: PNode, d: var TLoc) = proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int) = - # This is called by return and break stmts. - # When jumping out of try/except/finally stmts, - # we need to pop safe points from try statements, - # execute finally-stmts, and pop exceptions - # from except stmts + # Called by return and break stmts. + # Deals with issues faced when jumping out of try/except/finally stmts, - let L = p.nestedTryStmts.len - - # danger of endless recursion! we workaround this here by a temp stack var stack: seq[PNode] - newSeq(stack, howManyTrys) - for i in countup(1, howManyTrys): - stack[i-1] = p.nestedTryStmts[L-i] - setLen(p.nestedTryStmts, L-howManyTrys) + newSeq(stack, 0) var alreadyPoppedCnt = p.inExceptBlock - for tryStmt in items(stack): + for i in countup(1, howManyTrys): + if gCmd != cmdCompileToCpp: + # Pop safe points generated by try if alreadyPoppedCnt > 0: dec alreadyPoppedCnt else: linefmt(p, cpsStmts, "#popSafePoint();$n") - # Find finally-stmts for this try-stmt - # and generate a copy of the finally stmts here + + # Pop this try-stmt of the list of nested trys + # so we don't infinite recurse on it in the next step. + var tryStmt = p.nestedTryStmts.pop + stack.add(tryStmt) + + # Find finally-stmt for this try-stmt + # and generate a copy of its sons var finallyStmt = lastSon(tryStmt) if finallyStmt.kind == nkFinally: genStmts(p, finallyStmt.sons[0]) + # push old elements again: for i in countdown(howManyTrys-1, 0): p.nestedTryStmts.add(stack[i]) @@ -304,7 +304,14 @@ proc genReturnStmt(p: BProc, t: PNode) = p.beforeRetNeeded = true genLineDir(p, t) if (t.sons[0].kind != nkEmpty): genStmts(p, t.sons[0]) - blockLeaveActions(p, min(1, p.nestedTryStmts.len), p.inExceptBlock) + blockLeaveActions(p, + howManyTrys = p.nestedTryStmts.len, + howManyExcepts = p.inExceptBlock) + if (p.finallySafePoints.len > 0): + # If we're in a finally block, and we came here by exception + # consume it before we return. + var safePoint = p.finallySafePoints[p.finallySafePoints.len-1] + linefmt(p, cpsStmts, "if ($1.status != 0) #popCurrentException();$n", safePoint) lineFF(p, cpsStmts, "goto BeforeRet;$n", "br label %BeforeRet$n", []) proc genComputedGoto(p: BProc; n: PNode) = @@ -843,7 +850,9 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = discard pop(p.nestedTryStmts) endBlock(p) # end of else block if i < length and t.sons[i].kind == nkFinally: + p.finallySafePoints.add(safePoint) exprBlock(p, t.sons[i].sons[0], d) + discard pop(p.finallySafePoints) linefmt(p, cpsStmts, "if ($1.status != 0) #reraiseException();$n", safePoint) proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false): PRope = diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index 71479abddd..0df7bb6dc8 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -65,11 +65,13 @@ type prc*: PSym # the Nimrod proc that this C proc belongs to beforeRetNeeded*: bool # true iff 'BeforeRet' label for proc is needed threadVarAccessed*: bool # true if the proc already accessed some threadvar - nestedTryStmts*: seq[PNode] # in how many nested try statements we are - # (the vars must be volatile then) + nestedTryStmts*: seq[PNode] # in how many nested try statements we are + # (the vars must be volatile then) inExceptBlock*: int # are we currently inside an except block? # leaving such scopes by raise or by return must # execute any applicable finally blocks + finallySafePoints*: seq[PRope] # For correctly cleaning up exceptions when + # using return in finally statements labels*: Natural # for generating unique labels in the C proc blocks*: seq[TBlock] # nested blocks breakIdx*: int # the block that will be exited @@ -142,6 +144,7 @@ proc newProc*(prc: PSym, module: BModule): BProc = else: result.options = gOptions newSeq(result.blocks, 1) result.nestedTryStmts = @[] + result.finallySafePoints = @[] iterator cgenModules*: var BModule = for i in 0..high(gModules): From 95eb2b88e6820e23d74e4a54e9aa4fba5415ec5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Sun, 23 Feb 2014 21:01:03 +0100 Subject: [PATCH 19/23] osproc: increase stack size from 8k to 64k --- lib/pure/osproc.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 93d737aa69..5c34c5a115 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -750,7 +750,7 @@ elif not defined(useNimRtl): var dataCopy = data if defined(useClone): - const stackSize = 8096 + const stackSize = 65536 let stackEnd = cast[clong](alloc(stackSize)) let stack = cast[pointer](stackEnd + stackSize) let fn: pointer = startProcessAfterFork From 6712aa6e38fdab6e250532c622d3191c16c2dc25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Sun, 23 Feb 2014 21:02:13 +0100 Subject: [PATCH 20/23] osproc: use fork by default on Linux, clone if requested by useClone flag --- lib/pure/osproc.nim | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 5c34c5a115..0ac01091c1 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -22,8 +22,6 @@ else: when defined(linux): import linux - when not defined(useFork): - const useClone = true type TProcess = object of TObject @@ -663,7 +661,7 @@ elif not defined(useNimRtl): data.workingDir = workingDir - when defined(posix_spawn) and not defined(useFork) and not defined(useClone): + when defined(posix_spawn) and not defined(useFork) and not defined(useClone) and not defined(linux): pid = startProcessAuxSpawn(data) else: pid = startProcessAuxFork(data) From 30a3098095e2d04939cc8f1074507343f65a58d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Sun, 23 Feb 2014 23:08:48 +0100 Subject: [PATCH 21/23] osproc: MacOSX workaround for lack of execvpe --- lib/pure/osproc.nim | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 79a4de04b1..4e17d3e02b 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -779,6 +779,9 @@ elif not defined(useNimRtl): discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error)) exitnow(1) + when defined(macosx): + var environ {.importc.}: cstringArray + proc startProcessAfterFork(data: ptr TStartProcessData) = # Warning: no GC here! # Or anythink that touches global structures - all called nimrod procs @@ -806,7 +809,13 @@ elif not defined(useNimRtl): discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC) if data.optionPoUsePath: - discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv) + when defined(macosx): + # MacOSX doesn't have execvpe, so we need workaround. + # On MacOSX we can arrive here only from fork, so this is safe: + environ = data.sysEnv + discard execvp(data.sysCommand, data.sysArgs) + else: + discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv) else: discard execve(data.sysCommand, data.sysArgs, data.sysEnv) From 7314f11adc742169770ac718ca4a5417ef16d69e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Mon, 24 Feb 2014 17:45:45 +0100 Subject: [PATCH 22/23] osproc: MacOSX fix - if -> when --- lib/pure/osproc.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 4e17d3e02b..2a685f3fb4 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -747,7 +747,7 @@ elif not defined(useNimRtl): var pid: TPid var dataCopy = data - if defined(useClone): + when defined(useClone): const stackSize = 65536 let stackEnd = cast[clong](alloc(stackSize)) let stack = cast[pointer](stackEnd + stackSize) From e6b0b7ecc9bb81d94eec19fbc4fc62e104f59253 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 25 Feb 2014 00:49:24 +0200 Subject: [PATCH 23/23] some fixes for static params usage in macros --- compiler/cgen.nim | 16 +++++++-------- compiler/msgs.nim | 3 +++ compiler/semtypes.nim | 12 +++++++++-- compiler/sigmatch.nim | 46 ++++++++++++++++++++++++++++--------------- 4 files changed, 51 insertions(+), 26 deletions(-) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 5057ae558e..87ed23f368 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -962,8 +962,8 @@ proc genMainProc(m: BModule) = NimMainBody = "N_CDECL(void, NimMain)(void) {$N" & "\tPreMain();$N" & - "$1$N" & - "}$N" + "$1" & + "}$N$N" PosixNimMain = "int cmdCount;$N" & @@ -977,20 +977,20 @@ proc genMainProc(m: BModule) = "\tcmdCount = argc;$N" & "\tgEnv = env;$N" & MainProcsWithResult & - "}$N" + "}$N$N" StandaloneCMain = "int main(void) {$N" & MainProcs & "\treturn 0;$N" & - "}$N" + "}$N$N" WinNimMain = NimMainBody WinCMain = "N_STDCALL(int, WinMain)(HINSTANCE hCurInstance, $N" & " HINSTANCE hPrevInstance, $N" & " LPSTR lpCmdLine, int nCmdShow) {$N" & - MainProcsWithResult & "}$N" + MainProcsWithResult & "}$N$N" WinNimDllMain = "N_LIB_EXPORT " & NimMainBody @@ -998,14 +998,14 @@ proc genMainProc(m: BModule) = "BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdreason, $N" & " LPVOID lpvReserved) {$N" & "\tif(fwdreason == DLL_PROCESS_ATTACH) {$N" & MainProcs & "}$N" & - "\treturn 1;$N}$N" + "\treturn 1;$N}$N$N" PosixNimDllMain = WinNimDllMain PosixCDllMain = "void NIM_POSIX_INIT NimMainInit(void) {$N" & MainProcs & - "}$N" + "}$N$N" var nimMain, otherMain: TFormatStr if platform.targetOS == osWindows and @@ -1034,7 +1034,7 @@ proc genMainProc(m: BModule) = platform.targetOS == osStandalone: "".toRope else: ropecg(m, "\t#initStackBottom();$N") inc(m.labels) - appcg(m, m.s[cfsProcs], "void PreMain() {$N" & PreMainBody & "}$N", [ + appcg(m, m.s[cfsProcs], "void PreMain() {$N" & PreMainBody & "}$N$N", [ mainDatInit, initStackBottomCall, gBreakpoints, otherModsInit]) appcg(m, m.s[cfsProcs], nimMain, [mainModInit, toRope(m.labels)]) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 0140d1ac4d..b44ca2ff01 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -94,6 +94,7 @@ type errNewSectionExpected, errWhitespaceExpected, errXisNoValidIndexFile, errCannotRenderX, errVarVarTypeNotAllowed, errInstantiateXExplicitely, errOnlyACallOpCanBeDelegator, errUsingNoSymbol, + errMacroBodyDependsOnGenericTypes, errDestructorNotGenericEnough, errXExpectsTwoArguments, @@ -326,6 +327,8 @@ const errInstantiateXExplicitely: "instantiate '$1' explicitely", errOnlyACallOpCanBeDelegator: "only a call operator can be a delegator", errUsingNoSymbol: "'$1' is not a variable, constant or a proc name", + errMacroBodyDependsOnGenericTypes: "the macro body cannot be compiled, " & + "because the parameter '$1' has a generic type", errDestructorNotGenericEnough: "Destructor signarue is too specific. " & "A destructor must be associated will all instantiations of a generic type", errXExpectsTwoArguments: "\'$1\' expects two arguments", diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 0eba602cc9..809b804282 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -653,6 +653,10 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, var paramTypId = if not anon and paramType.sym != nil: paramType.sym.name else: nil + template maybeLift(typ: PType): expr = + let lifted = liftingWalk(typ) + (if lifted != nil: lifted else: typ) + template addImplicitGeneric(e: expr): expr = addImplicitGenericImpl(e, paramTypId) @@ -663,7 +667,10 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, of tyStatic: # proc(a: expr{string}, b: expr{nkLambda}) # overload on compile time values and AST trees - result = addImplicitGeneric(c.newTypeWithSons(tyStatic, paramType.sons)) + let base = paramType.base.maybeLift + if base.isMetaType and procKind == skMacro: + localError(info, errMacroBodyDependsOnGenericTypes, paramName) + result = addImplicitGeneric(c.newTypeWithSons(tyStatic, @[base])) result.flags.incl tfHasStatic of tyTypeDesc: @@ -671,7 +678,8 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, # naked typedescs are not bindOnce types if paramType.base.kind == tyNone and paramTypId != nil and paramTypId.id == typedescId.id: paramTypId = nil - result = addImplicitGeneric(c.newTypeWithSons(tyTypeDesc, paramType.sons)) + result = addImplicitGeneric( + c.newTypeWithSons(tyTypeDesc, @[paramType.base])) of tyDistinct: if paramType.sonsLen == 1: diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 240145118e..f8e3459df9 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1029,6 +1029,28 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, r = typeRel(m, f, a) + if r != isNone and m.calleeSym != nil and + m.calleeSym.kind in {skMacro, skTemplate}: + # XXX: duplicating this is ugly, maybe we should move this + # directly into typeRel using return-like templates + case r + of isConvertible, isIntConv: inc(m.convMatches) + of isSubtype, isSubrange: inc(m.subtypeMatches) + of isGeneric, isInferred: inc(m.genericMatches) + of isInferredConvertible: inc(m.genericMatches); inc(m.convMatches) + of isFromIntLit: inc(m.intConvMatches, 256) + of isEqual: inc(m.exactMatches) + of isNone: discard + + if f.kind == tyStmt and argOrig.kind == nkDo: + return argOrig[bodyPos] + elif f.kind == tyTypeDesc: + return arg + elif f.kind == tyStatic: + return arg.typ.n + else: + return argOrig + case r of isConvertible: inc(m.convMatches) @@ -1046,31 +1068,23 @@ proc paramTypesMatchAux(m: var TCandidate, f, argType: PType, #result = copyTree(arg) result = implicitConv(nkHiddenStdConv, f, copyTree(arg), m, c) of isInferred, isInferredConvertible: + inc(m.genericMatches) if arg.kind in {nkProcDef, nkIteratorDef} + nkLambdaKinds: result = c.semInferredLambda(c, m.bindings, arg) else: let inferred = c.semGenerateInstance(c, arg.sym, m.bindings, arg.info) result = newSymNode(inferred, arg.info) if r == isInferredConvertible: + inc(m.convMatches) result = implicitConv(nkHiddenStdConv, f, result, m, c) of isGeneric: inc(m.genericMatches) - if m.calleeSym != nil and m.calleeSym.kind in {skMacro, skTemplate}: - if f.kind == tyStmt and argOrig.kind == nkDo: - result = argOrig[bodyPos] - elif f.kind == tyTypeDesc: - result = arg - elif f.kind == tyStatic: - result = arg.typ.n - else: - result = argOrig - else: - result = copyTree(arg) - result.typ = getInstantiatedType(c, arg, m, f) - # BUG: f may not be the right key! - if skipTypes(result.typ, abstractVar-{tyTypeDesc}).kind in {tyTuple}: - result = implicitConv(nkHiddenStdConv, f, copyTree(arg), m, c) - # BUGFIX: use ``result.typ`` and not `f` here + result = copyTree(arg) + result.typ = getInstantiatedType(c, arg, m, f) + # BUG: f may not be the right key! + if skipTypes(result.typ, abstractVar-{tyTypeDesc}).kind in {tyTuple}: + result = implicitConv(nkHiddenStdConv, f, copyTree(arg), m, c) + # BUGFIX: use ``result.typ`` and not `f` here of isFromIntLit: # too lazy to introduce another ``*matches`` field, so we conflate # ``isIntConv`` and ``isIntLit`` here: