Repository: xonsh/xonsh

Issue:
after upgrade to 0.8.9 has error when start xonsh
After upgrade to 0.8.9, every time launch xonsh got error:
```
TypeError: source_foreign() got an unexpected keyword argument 'spec'
TypeError: source_foreign() got an unexpected keyword argument 'spec'
TypeError: source_foreign() got an unexpected keyword argument 'spec'
```
## xonfig

```
$ xonfig

+------------------+----------------------+
| xonsh            | 0.8.9                |
| Git SHA          | 4135c690             |
| Commit Date      | Jan 31 10:14:50 2019 |
| Python           | 3.7.1                |
| PLY              | 3.11                 |
| have readline    | True                 |
| prompt toolkit   | 2.0.4                |
| shell type       | prompt_toolkit2      |
| pygments         | 2.2.0                |
| on posix         | True                 |
| on linux         | False                |
| on darwin        | True                 |
| on windows       | False                |
| on cygwin        | False                |
| on msys2         | False                |
| is superuser     | False                |
| default encoding | utf-8                |
| xonsh encoding   | utf-8                |
| encoding errors  | surrogateescape      |
+------------------+----------------------+
```


even delete all the content of .xonshrc still has same error, but if launch `xonsh --no-rc`  it's ok without any error. 


Discussion:
Thanks @eggcaker - this is an error with how eval aliases are being partially applied.  Fix incoming. 
@scopatz could you reference the issue? I'm curious about the cause of this :)
@Granitosaurus I believe it is the fix in #2992 where calling `value()` now passes in all arguments without first checking if it can.

Resolving patch:
diff --git a/xonsh/aliases.py b/xonsh/aliases.py
--- a/xonsh/aliases.py
+++ b/xonsh/aliases.py
@@ -21,16 +21,19 @@
     ON_NETBSD,
     ON_DRAGONFLY,
 )
-from xonsh.tools import unthreadable, print_color
-from xonsh.replay import replay_main
-from xonsh.timings import timeit_alias
 from xonsh.tools import (
+    XonshError,
     argvquote,
     escape_windows_cmd_string,
     to_bool,
     swap_values,
     strip_simple_quotes,
+    ALIAS_KWARG_NAMES,
+    unthreadable,
+    print_color,
 )
+from xonsh.replay import replay_main
+from xonsh.timings import timeit_alias
 from xonsh.xontribs import xontribs_main
 from xonsh.ast import isexpression
 
@@ -70,36 +73,19 @@ def get(self, key, default=None):
 
     def eval_alias(self, value, seen_tokens=frozenset(), acc_args=()):
         """
-        "Evaluates" the alias `value`, by recursively looking up the leftmost
+        "Evaluates" the alias ``value``, by recursively looking up the leftmost
         token and "expanding" if it's also an alias.
 
-        A value like ["cmd", "arg"] might transform like this:
-        > ["cmd", "arg"] -> ["ls", "-al", "arg"] -> callable()
-        where `cmd=ls -al` and `ls` is an alias with its value being a
+        A value like ``["cmd", "arg"]`` might transform like this:
+        ``> ["cmd", "arg"] -> ["ls", "-al", "arg"] -> callable()``
+        where ``cmd=ls -al`` and ``ls`` is an alias with its value being a
         callable.  The resulting callable will be "partially applied" with
-        ["-al", "arg"].
+        ``["-al", "arg"]``.
         """
         # Beware of mutability: default values for keyword args are evaluated
         # only once.
         if callable(value):
-            if acc_args:  # Partial application
-
-                def _alias(
-                    args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
-                ):
-                    args = list(acc_args) + args
-                    return value(
-                        args,
-                        stdin=stdin,
-                        stdout=stdout,
-                        stderr=stderr,
-                        spec=spec,
-                        stack=stack,
-                    )
-
-                return _alias
-            else:
-                return value
+            return partial_eval_alias(value, acc_args=acc_args)
         else:
             expand_path = builtins.__xonsh__.expand_path
             token, *rest = map(expand_path, value)
@@ -207,6 +193,129 @@ def __repr__(self):
         return "ExecAlias({0!r}, filename={1!r})".format(self.src, self.filename)
 
 
+class PartialEvalAliasBase:
+    """Partially evaluated alias."""
+
+    def __init__(self, f, acc_args=()):
+        """
+        Parameters
+        ----------
+        f : callable
+            A function to dispatch to.
+        acc_args : sequence of strings, optional
+            Additional arguments to prepent to the argument list passed in
+            when the alias is called.
+        """
+        self.f = f
+        self.acc_args = acc_args
+
+    def __call__(
+        self, args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
+    ):
+        args = list(self.acc_args) + args
+        return self.f(args, stdin, stdout, stderr, spec, stack)
+
+    def __repr__(self):
+        return "{name}({f!r}, acc_args={acc_args!r})".format(
+            name=self.__class__.__name__, f=self.f, acc_args=self.acc_args
+        )
+
+
+class PartialEvalAlias0(PartialEvalAliasBase):
+    def __call__(
+        self, args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
+    ):
+        args = list(self.acc_args) + args
+        if args:
+            msg = "callable alias {f!r} takes no arguments, but {args!f} provided. "
+            msg += "Of these {acc_args!r} were partially applied."
+            raise XonshError(msg.format(f=self.f, args=args, acc_args=self.acc_args))
+        return self.f()
+
+
+class PartialEvalAlias1(PartialEvalAliasBase):
+    def __call__(
+        self, args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
+    ):
+        args = list(self.acc_args) + args
+        return self.f(args)
+
+
+class PartialEvalAlias2(PartialEvalAliasBase):
+    def __call__(
+        self, args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
+    ):
+        args = list(self.acc_args) + args
+        return self.f(args, stdin)
+
+
+class PartialEvalAlias3(PartialEvalAliasBase):
+    def __call__(
+        self, args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
+    ):
+        args = list(self.acc_args) + args
+        return self.f(args, stdin, stdout)
+
+
+class PartialEvalAlias4(PartialEvalAliasBase):
+    def __call__(
+        self, args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
+    ):
+        args = list(self.acc_args) + args
+        return self.f(args, stdin, stdout, stderr)
+
+
+class PartialEvalAlias5(PartialEvalAliasBase):
+    def __call__(
+        self, args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
+    ):
+        args = list(self.acc_args) + args
+        return self.f(args, stdin, stdout, stderr, spec)
+
+
+class PartialEvalAlias6(PartialEvalAliasBase):
+    def __call__(
+        self, args, stdin=None, stdout=None, stderr=None, spec=None, stack=None
+    ):
+        args = list(self.acc_args) + args
+        return self.f(args, stdin, stdout, stderr, spec, stack)
+
+
+PARTIAL_EVAL_ALIASES = (
+    PartialEvalAlias0,
+    PartialEvalAlias1,
+    PartialEvalAlias2,
+    PartialEvalAlias3,
+    PartialEvalAlias4,
+    PartialEvalAlias5,
+    PartialEvalAlias6,
+)
+
+
+def partial_eval_alias(f, acc_args=()):
+    """Dispatches the appropriate eval alias based on the number of args to the original callable alias
+    and how many arguments to apply.
+    """
+    # no partial needed if no extra args
+    if not acc_args:
+        return f
+    # need to dispatch
+    numargs = 0
+    for name, param in inspect.signature(f).parameters.items():
+        if (
+            param.kind == param.POSITIONAL_ONLY
+            or param.kind == param.POSITIONAL_OR_KEYWORD
+        ):
+            numargs += 1
+        elif name in ALIAS_KWARG_NAMES and param.kind == param.KEYWORD_ONLY:
+            numargs += 1
+    if numargs < 7:
+        return PARTIAL_EVAL_ALIASES[numargs](f, acc_args=acc_args)
+    else:
+        e = "Expected proxy with 6 or fewer arguments for {}, not {}"
+        raise XonshError(e.format(", ".join(ALIAS_KWARG_NAMES), numargs))
+
+
 #
 # Actual aliases below
 #
diff --git a/xonsh/proc.py b/xonsh/proc.py
--- a/xonsh/proc.py
+++ b/xonsh/proc.py
@@ -41,6 +41,7 @@
     on_main_thread,
     XonshError,
     format_std_prepost,
+    ALIAS_KWARG_NAMES,
 )
 from xonsh.lazyasd import lazyobject, LazyObject
 from xonsh.jobs import wait_for_active_job, give_terminal_to, _continue
@@ -1224,7 +1225,6 @@ def proxy_five(f, args, stdin, stdout, stderr, spec, stack):
 
 
 PROXIES = (proxy_zero, proxy_one, proxy_two, proxy_three, proxy_four, proxy_five)
-PROXY_KWARG_NAMES = frozenset(["args", "stdin", "stdout", "stderr", "spec", "stack"])
 
 
 def partial_proxy(f):
@@ -1236,7 +1236,7 @@ def partial_proxy(f):
             or param.kind == param.POSITIONAL_OR_KEYWORD
         ):
             numargs += 1
-        elif name in PROXY_KWARG_NAMES and param.kind == param.KEYWORD_ONLY:
+        elif name in ALIAS_KWARG_NAMES and param.kind == param.KEYWORD_ONLY:
             numargs += 1
     if numargs < 6:
         return functools.partial(PROXIES[numargs], f)
@@ -1245,7 +1245,7 @@ def partial_proxy(f):
         return f
     else:
         e = "Expected proxy with 6 or fewer arguments for {}, not {}"
-        raise XonshError(e.format(", ".join(PROXY_KWARG_NAMES), numargs))
+        raise XonshError(e.format(", ".join(ALIAS_KWARG_NAMES), numargs))
 
 
 class ProcProxyThread(threading.Thread):
diff --git a/xonsh/tools.py b/xonsh/tools.py
--- a/xonsh/tools.py
+++ b/xonsh/tools.py
@@ -2330,6 +2330,9 @@ def columnize(elems, width=80, newline="\n"):
     return lines
 
 
+ALIAS_KWARG_NAMES = frozenset(["args", "stdin", "stdout", "stderr", "spec", "stack"])
+
+
 def unthreadable(f):
     """Decorator that specifies that a callable alias should be run only
     on the main thread process. This is often needed for debuggers and


Test patch:
diff --git a/tests/test_aliases.py b/tests/test_aliases.py
--- a/tests/test_aliases.py
+++ b/tests/test_aliases.py
@@ -63,12 +63,21 @@ def test_eval_recursive_callable_partial(xonsh_execer, xonsh_builtins):
     assert ales.get("indirect_cd")(["arg2", "arg3"]) == ["..", "arg2", "arg3"]
 
 
-def _return_to_sender(args, **kwargs):
-    return args, kwargs
+def _return_to_sender_all(args, stdin, stdout, stderr, spec, stack):
+    return (
+        args,
+        {
+            "stdin": stdin,
+            "stdout": stdout,
+            "stderr": stderr,
+            "spec": spec,
+            "stack": stack,
+        },
+    )
 
 
-def test_recursive_callable_partial_(xonsh_execer, xonsh_builtins):
-    ales = Aliases({"rtn": _return_to_sender, "rtn-recurse": ["rtn", "arg1"]})
+def test_recursive_callable_partial_all(xonsh_execer, xonsh_builtins):
+    ales = Aliases({"rtn": _return_to_sender_all, "rtn-recurse": ["rtn", "arg1"]})
     alias = ales.get("rtn-recurse")
     assert callable(alias)
     args, obs = alias(["arg2"], stdin="a", stdout="b", stderr="c", spec="d", stack="e")
@@ -76,3 +85,31 @@ def test_recursive_callable_partial_(xonsh_execer, xonsh_builtins):
     assert len(obs) == 5
     exp = {"stdin": "a", "stdout": "b", "stderr": "c", "spec": "d", "stack": "e"}
     assert obs == exp
+
+
+def _return_to_sender_handles(args, stdin, stdout, stderr):
+    return args, {"stdin": stdin, "stdout": stdout, "stderr": stderr}
+
+
+def test_recursive_callable_partial_handles(xonsh_execer, xonsh_builtins):
+    ales = Aliases({"rtn": _return_to_sender_handles, "rtn-recurse": ["rtn", "arg1"]})
+    alias = ales.get("rtn-recurse")
+    assert callable(alias)
+    args, obs = alias(["arg2"], stdin="a", stdout="b", stderr="c")
+    assert args == ["arg1", "arg2"]
+    assert len(obs) == 3
+    exp = {"stdin": "a", "stdout": "b", "stderr": "c"}
+    assert obs == exp
+
+
+def _return_to_sender_none():
+    return "wakka", {}
+
+
+def test_recursive_callable_partial_none(xonsh_execer, xonsh_builtins):
+    ales = Aliases({"rtn": _return_to_sender_none, "rtn-recurse": ["rtn"]})
+    alias = ales.get("rtn-recurse")
+    assert callable(alias)
+    args, obs = alias()
+    assert args == "wakka"
+    assert len(obs) == 0