Skip to content

Commit

Permalink
Merge branch 'master' into stubgen-patch
Browse files Browse the repository at this point in the history
  • Loading branch information
ilevkivskyi authored Apr 27, 2017
2 parents 3f6a988 + 665a810 commit e9cfae2
Show file tree
Hide file tree
Showing 73 changed files with 1,347 additions and 742 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
sudo: false
language: python
# cache package wheels (1 cache per python version)
cache: pip
python:
- "3.3"
- "3.4"
Expand Down
33 changes: 19 additions & 14 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,34 +1,28 @@
environment:
matrix:

- PYTHON: "C:\\Python35"
PYTHON_VERSION: "3.5.1"
PYTHON_ARCH: "32"

- PYTHON: "C:\\Python35-x64"
PYTHON_VERSION: "3.5.1"
PYTHON_ARCH: "64"

- PYTHON: "C:\\Python36"
PYTHON_VERSION: "3.6.x"
PYTHON_ARCH: "32"

- PYTHON: "C:\\Python36-x64"
PYTHON_VERSION: "3.6.x"
PYTHON_ARCH: "64"


install:
- "git config core.symlinks true"
- "git reset --hard"
- "%PYTHON%\\python.exe -m pip install -r test-requirements.txt"
- "git submodule update --init typeshed"
- "cd typeshed && git config core.symlinks true && git reset --hard && cd .."
- "%PYTHON%\\python.exe setup.py -q install"

build: off

test_script:
# Ignore lint (it's run separately below)
# Ignore lint (it's run in Travis)
- "%PYTHON%\\python.exe runtests.py -x lint"
- ps: if ($env:PYTHON_VERSION -Match "3.6.x" -And $env:PYTHON_ARCH -Match "64") { iex "$env:PYTHON\\python.exe -m flake8" }

after_test:
- "%PYTHON%\\python.exe -m pip install wheel"
Expand All @@ -37,3 +31,14 @@ after_test:

artifacts:
- path: dist\*

skip_commits:
files:
- docs/**/*
- '**/*.rst'
- '**/*.md'
- .gitignore
- .runtest_log.json
- .travis.yml
- CREDITS
- LICENSE
1 change: 0 additions & 1 deletion docs/source/cheat_sheet.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ When you're puzzled or when things are complicated
# type: (*str, **str) -> str
request = make_request(*args, **kwargs)
return self.do_api_query(request)
# Use `ignore` to suppress type-checking on a given line, when your
# code confuses mypy or runs into an outright bug in mypy.
Expand Down
6 changes: 6 additions & 0 deletions docs/source/cheat_sheet_py3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ When you're puzzled or when things are complicated
# dynamic to write a type for.
x = mystery_function() # type: Any
# This is how to deal with varargs.
# This makes each positional arg and each keyword arg a 'str'.
def call(self, *args: str, **kwargs: str) -> str:
request = make_request(*args, **kwargs)
return self.do_api_query(request)
# Use `ignore` to suppress type-checking on a given line, when your
# code confuses mypy or runs into an outright bug in mypy.
# Good practice is to comment every `ignore` with a bug link
Expand Down
7 changes: 1 addition & 6 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ flag (or its long form ``--help``)::
[--inferstats] [--custom-typing MODULE]
[--custom-typeshed-dir DIR] [--scripts-are-modules]
[--config-file CONFIG_FILE] [--show-column-numbers]
[--find-occurrences CLASS.MEMBER] [--strict-boolean]
[--find-occurrences CLASS.MEMBER]
[--cobertura-xml-report DIR] [--html-report DIR]
[--linecount-report DIR] [--linecoverage-report DIR]
[--memory-xml-report DIR] [--old-html-report DIR]
Expand Down Expand Up @@ -366,11 +366,6 @@ Here are some more useful flags:
- ``--warn-return-any`` causes mypy to generate a warning when returning a value
with type ``Any`` from a function declared with a non- ``Any`` return type.

- ``--strict-boolean`` will make using non-boolean expressions in conditions
an error. This means ``if x`` and ``while x`` are disallowed when ``x`` has any
type other than ``bool``. Instead use explicit checks like ``if x > 0`` or
``while x is not None``.

- ``--strict`` mode enables all optional error checking flags. You can see the
list of flags enabled by strict mode in the full ``mypy -h`` output.

Expand Down
106 changes: 68 additions & 38 deletions docs/source/function_overloading.rst
Original file line number Diff line number Diff line change
@@ -1,60 +1,90 @@
Function overloading in stubs
=============================
Function Overloading
====================

Sometimes you have a library function that seems to call for two or
more signatures. That's okay -- you can define multiple *overloaded*
instances of a function with the same name but different signatures in
a stub file (this feature is not supported for user code, at least not
yet) using the ``@overload`` decorator. For example, we can define an
``abs`` function that works for both ``int`` and ``float`` arguments:
Sometimes the types in a function depend on each other in ways that
can't be captured with a ``Union``. For example, the ``__getitem__``
(``[]`` bracket indexing) method can take an integer and return a
single item, or take a ``slice`` and return a ``Sequence`` of items.
You might be tempted to annotate it like so:

.. code-block:: python
# This is a stub file!
from typing import overload
@overload
def abs(n: int) -> int: pass
@overload
def abs(n: float) -> float: pass
Note that we can't use ``Union[int, float]`` as the argument type,
since this wouldn't allow us to express that the return
type depends on the argument type.

Now if we import ``abs`` as defined in the above library stub, we can
write code like this, and the types are inferred correctly:
from typing import Sequence, TypeVar, Union
T = TypeVar('T')
class MyList(Sequence[T]):
def __getitem__(self, index: Union[int, slice]) -> Union[T, Sequence[T]]:
if isinstance(index, int):
... # Return a T here
elif isinstance(index, slice):
... # Return a sequence of Ts here
else:
raise TypeError(...)
But this is too loose, as it implies that when you pass in an ``int``
you might sometimes get out a single item and sometimes a sequence.
The return type depends on the parameter type in a way that can't be
expressed using a type variable. Instead, we can use `overloading
<https://www.python.org/dev/peps/pep-0484/#function-method-overloading>`_
to give the same function multiple type annotations (signatures) and
accurately describe the function's behavior.

.. code-block:: python
n = abs(-2) # 2 (int)
f = abs(-1.5) # 1.5 (float)
from typing import overload, Sequence, TypeVar, Union
T = TypeVar('T')
class MyList(Sequence[T]):
# The @overload definitions are just for the type checker,
# and overwritten by the real implementation below.
@overload
def __getitem__(self, index: int) -> T:
pass # Don't put code here
# All overloads and the implementation must be adjacent
# in the source file, and overload order may matter:
# when two overloads may overlap, the more specific one
# should come first.
@overload
def __getitem__(self, index: slice) -> Sequence[T]:
pass # Don't put code here
# The implementation goes last, without @overload.
# It may or may not have type hints; if it does,
# these are checked against the overload definitions
# as well as against the implementation body.
def __getitem__(self, index):
# This is exactly the same as before.
if isinstance(index, int):
... # Return a T here
elif isinstance(index, slice):
... # Return a sequence of Ts here
else:
raise TypeError(...)
Overloaded function variants are still ordinary Python functions and
they still define a single runtime object. The following code is
thus valid:

.. code-block:: python
my_abs = abs
my_abs(-2) # 2 (int)
my_abs(-1.5) # 1.5 (float)
they still define a single runtime object. There is no automatic
dispatch happening, and you must manually handle the different types
in the implementation (usually with :func:`isinstance` checks, as
shown in the example).

The overload variants must be adjacent in the code. This makes code
clearer, as you don't have to hunt for overload variants across the
file.

Overloads in stub files are exactly the same, except there is no
implementation.

.. note::

As generic type variables are erased at runtime when constructing
instances of generic types, an overloaded function cannot have
variants that only differ in a generic type argument,
e.g. ``List[int]`` versus ``List[str]``.
e.g. ``List[int]`` and ``List[str]``.

.. note::

If you are writing a regular module rather than a stub, you can
often use a type variable with a value restriction to represent
functions as ``abs`` above (see :ref:`type-variable-value-restriction`).
If you just need to constrain a type variable to certain types or
subtypes, you can use a :ref:`value restriction
<type-variable-value-restriction>`.
3 changes: 3 additions & 0 deletions docs/source/revision_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ Revision history

List of major changes to this document:

- April 2017
* Remove option ``strict_boolean``.

- March 2017
* Publish ``mypy`` version 0.500 on PyPI.

Expand Down
59 changes: 35 additions & 24 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -860,8 +860,7 @@ def write_cache(id: str, path: str, tree: MypyFile,

# Make sure directory for cache files exists
parent = os.path.dirname(data_json)
if not os.path.isdir(parent):
os.makedirs(parent)
os.makedirs(parent, exist_ok=True)
assert os.path.dirname(meta_json) == parent

# Construct temp file names
Expand All @@ -877,6 +876,20 @@ def write_cache(id: str, path: str, tree: MypyFile,
data_str = json.dumps(data, sort_keys=True)
interface_hash = compute_hash(data_str)

# Obtain and set up metadata
try:
st = manager.get_stat(path)
except OSError as err:
manager.log("Cannot get stat for {}: {}".format(path, err))
# Remove apparently-invalid cache files.
for filename in [data_json, meta_json]:
try:
os.remove(filename)
except OSError:
pass
# Still return the interface hash we computed.
return interface_hash

# Write data cache file, if applicable
if old_interface_hash == interface_hash:
# If the interface is unchanged, the cached data is guaranteed
Expand All @@ -891,8 +904,6 @@ def write_cache(id: str, path: str, tree: MypyFile,
os.replace(data_json_tmp, data_json)
manager.trace("Interface for {} has changed".format(id))

# Obtain and set up metadata
st = manager.get_stat(path) # TODO: Handle errors
mtime = st.st_mtime
size = st.st_size
options = manager.options.clone_for_module(id)
Expand Down Expand Up @@ -1524,26 +1535,26 @@ def valid_references(self) -> Set[str]:
return valid_refs

def write_cache(self) -> None:
ok = self.path and self.options.incremental
if ok:
if self.manager.options.quick_and_dirty:
is_errors = self.manager.errors.is_errors_for_file(self.path)
else:
is_errors = self.manager.errors.is_errors()
ok = not is_errors
if ok:
dep_prios = [self.priorities.get(dep, PRI_HIGH) for dep in self.dependencies]
new_interface_hash = write_cache(
self.id, self.path, self.tree,
list(self.dependencies), list(self.suppressed), list(self.child_modules),
dep_prios, self.interface_hash,
self.manager)
if new_interface_hash == self.interface_hash:
self.manager.log("Cached module {} has same interface".format(self.id))
else:
self.manager.log("Cached module {} has changed interface".format(self.id))
self.mark_interface_stale()
self.interface_hash = new_interface_hash
if not self.path or self.options.cache_dir == os.devnull:
return
if self.manager.options.quick_and_dirty:
is_errors = self.manager.errors.is_errors_for_file(self.path)
else:
is_errors = self.manager.errors.is_errors()
if is_errors:
return
dep_prios = [self.priorities.get(dep, PRI_HIGH) for dep in self.dependencies]
new_interface_hash = write_cache(
self.id, self.path, self.tree,
list(self.dependencies), list(self.suppressed), list(self.child_modules),
dep_prios, self.interface_hash,
self.manager)
if new_interface_hash == self.interface_hash:
self.manager.log("Cached module {} has same interface".format(self.id))
else:
self.manager.log("Cached module {} has changed interface".format(self.id))
self.mark_interface_stale()
self.interface_hash = new_interface_hash


def dispatch(sources: List[BuildSource], manager: BuildManager) -> Graph:
Expand Down
Loading

0 comments on commit e9cfae2

Please sign in to comment.