From a0c43bac2f85ee8abfab37bba8d92856a494cbf2 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Mon, 15 Sep 2014 14:15:20 -0500 Subject: [PATCH 001/144] Fix parsing empty base class lists --- mypy/parse.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/parse.py b/mypy/parse.py index 0f69c9988e39..73170fcaf792 100755 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -278,6 +278,8 @@ def parse_class_def(self) -> ClassDef: if self.current_str() == '(': lparen = self.skip() while True: + if self.current_str() == ')': + break if self.current_str() == 'metaclass': metaclass = self.parse_metaclass() break @@ -1489,7 +1491,7 @@ def parse_comparison_expr(self, left: Node, prec: int) -> ComparisonExpr: operators_str.append(op_str) operators.append( (op, op2) ) - operand = self.parse_expression(prec) + operand = self.parse_expression(prec) operands.append(operand) # Continue if next token is a comparison operator From 8f54ec7ee44e8d231ed99b11983ecf6d8ccee594 Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Mon, 15 Sep 2014 14:49:00 -0500 Subject: [PATCH 002/144] Fix failing tests --- mypy/test/data/parse-errors.test | 7 ------- 1 file changed, 7 deletions(-) diff --git a/mypy/test/data/parse-errors.test b/mypy/test/data/parse-errors.test index e29d3638bac9..6ce444272a8f 100644 --- a/mypy/test/data/parse-errors.test +++ b/mypy/test/data/parse-errors.test @@ -60,13 +60,6 @@ file: In class "A": file, line 1: Parse error before ) file, line 2: Parse error before end of file -[case testEmptySuperClass] -class A(): - pass -[out] -file: In class "A": -file, line 1: Parse error before ) - [case testMissingSuperClass] class A(: pass From 7cdf3b3904037c2ab5158246cd0c5befa00155eb Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Mon, 15 Sep 2014 20:19:32 -0500 Subject: [PATCH 003/144] Add Sphinx docs --- .gitignore | 1 + docs/Makefile | 177 ++++++++ docs/make.bat | 242 +++++++++++ docs/source/conf.py | 264 ++++++++++++ docs/source/faq.rst | 140 +++++++ docs/source/index.rst | 20 + docs/source/tutorial.rst | 867 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 1711 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/conf.py create mode 100644 docs/source/faq.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/tutorial.rst diff --git a/.gitignore b/.gitignore index 552d791d590d..619fe676938d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ __pycache__ @* /build /env +docs/build/ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000000..be69e9d88281 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Mypy.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Mypy.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Mypy" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Mypy" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000000..1e3d84320174 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,242 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +set I18NSPHINXOPTS=%SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Mypy.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Mypy.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 000000000000..6d160cce044b --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,264 @@ +# -*- coding: utf-8 -*- +# +# Mypy documentation build configuration file, created by +# sphinx-quickstart on Sun Sep 14 19:50:35 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Mypy' +copyright = u'2014, Jukka Lehtosalo' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.0.1.dev1' +# The full version, including alpha/beta/rc tags. +release = '0.0.1.dev1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +try: + import sphinx_rtd_theme +except: + html_theme = 'default' +else: + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Mypydoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'Mypy.tex', u'Mypy Documentation', + u'Jukka', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'mypy', u'Mypy Documentation', + [u'Jukka Lehtosalo'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Mypy', u'Mypy Documentation', + u'Jukka', 'Mypy', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/source/faq.rst b/docs/source/faq.rst new file mode 100644 index 000000000000..1953cf04fb41 --- /dev/null +++ b/docs/source/faq.rst @@ -0,0 +1,140 @@ +Frequently Asked Questions +========================== + +Why have both dynamic and static typing? +**************************************** + +Dynamic typing can be flexible, powerful, convenient and easy. But it's not always the best approach; there are good reasons why many developers choose to use staticaly typed languages. + +Here are some potential benefits of mypy-style static typing: + +- Static typing can make programs easier to understand and maintain. Type declarations can serve as machine-checked documentation. This is important as code is typically read much more often than modified, and this is especially important for large and complex programs. + +- Static typing can help you find bugs earlier and with less testing and debugging. Especially in large and complex projects this can be a major time-saver. + +- Static typing can help you find difficult-to-find bugs before your code goes into production. This can improve reliability and reduce the number of security issues. + +- Static typing makes it practical to build very useful development tools that can improve programming productivity or software quality, including IDEs with precise and reliable code completion, static analysis tools, etc. + +- You can get the benefits of both dynamic and static typing in a single language. Dynamic typing can be perfect for a small project or for writing the UI of your program, for example. As your program grows, you can adapt tricky application logic to static typing to help maintenance. + +See also the `front page `_. + +Would my project benefit from static typing? +******************************************** + +For many projects dynamic typing is perfectly fine (we think that Python is a great language). But sometimes your projects demand bigger guns, and that's when mypy may come in handy. + +If some of these ring true for your projects, mypy (and static typing) may be useful: + +- Your project is large or complex. + +- Your codebase must be maintained for a long time. + +- Multiple developers are working on the same code. + +- Running tests takes a lot of time or work (type checking may help you find errors early in development, reducing the number of testing iterations). + +- Some project members (devs or management) don't like dynamic typing, but others prefer dynamic typing and Python syntax. Mypy could be a solution that everybody finds easy to accept. + +- You want to future-proof your project even if currently none of the above really apply. + +Can I use mypy to type check my existing Python code? +***************************************************** + +It depends. Compatibility is pretty good, but several Python features are not yet implemented. The ultimate goal is to make using mypy practical for most Python code. Code that uses complex introspection or metaprogramming may be impractical to type check, but it should still be possible to use static typing in other parts of a program. + +Will static typing make my programs run faster? +*********************************************** + +Mypy only does static type checking and it does not improve performance. It has a minimal performance impact. In the future, there could be other tools that can compile statically typed mypy code to C modules or to efficient JVM bytecode, for example, but this outside the scope of the mypy project. It may also be possible to modify existing Python VMs to take advantage of static type information, but whether this is feasible is still unknown. This is nontrivial since the runtime types do not necessarily correspond to the static types. + +All of my code is still in Python 2. What are my options? +********************************************************* + +Mypy currently supports Python 3 syntax. Python 2 support is still in early stages of development. However, Python 2 support will be improving. Mypy includes a custom codec that lets you use Python 3 function annotations in Python 2 code. The codec just removes the annotations before bytecode compilation. + +Is mypy free? +************* + +Yes. Mypy is free software, and it can also be used for commercial and proprietary projects. Mypy is available under the MIT license. + +Why not use structural subtyping? +********************************* + +Mypy primarily uses `nominal subtyping `_ instead of `structural subtyping `_. Some argue that structural subtyping is better suited for languages with duck typing such as Python. + +Here are some reasons why mypy uses nominal subtyping: + +1. It is easy to generate short and informative error messages when using a nominal type system. This is especially important when using type inference. + +2. Python supports basically nominal isinstance tests and they are widely used in programs. It is not clear how to support isinstance in a purely structural type system while remaining compatible with Python idioms. + +3. Many programmers are already familiar with nominal subtyping and it has been successfully used in languages such as Java, C++ and C#. Only few languages use structural subtyping. + +However, structural subtyping can also be useful. Structural subtyping is a likely feature to be added to mypy in the future, even though we expect that most mypy programs will still primarily use nominal subtyping. + +I like Python as it is. I don't need static typing. +*************************************************** + +That wasn't really a question, was it? Mypy is not aimed at replacing Python. The goal is to give more options for Python programmers, to make Python a more competitive alternative to other statically typed languages in large projects, to improve programmer productivity and to improve software quality. + +How are mypy programs different from normal Python? +*************************************************** + +Since you use a vanilla Python implementation to run mypy programs, mypy programs are also Python programs. The type checker may give warnings for some valid Python code, but the code is still always runnable. Also, some Python features and syntax are still not supported by mypy, but this is gradually improving. + +The obvious difference is the availability of static type checking. The :doc:`mypy tutorial ` mentions some modifications to Python code that may be required to make code type check without errors, such as the need to make attributes explicit and more explicit protocol representation. + +Mypy will support modular, efficient type checking, and this seems to rule out type checking some language features, such as arbitrary runtime addition of methods. However, it is likely that many of these features will be supported in a restricted form (for example, runtime modification is only supported for classes or methods registered as dynamic or 'patchable'). + +How is mypy different from PyPy? +******************************** + +*This answer relates to PyPy as a Python implementation. See also the answer related to RPython below.* + +Mypy and PyPy are orthogonal. Mypy does static type checking, i.e. it is basically a linter, but static typing has no runtime effect, whereas the PyPy is an Python implementation. You can use PyPy to run mypy programs. + +How is mypy different from Cython? +********************************** + +`Cython `_ is a variant of Python that supports compilation to CPython C modules. It can give major speedups to certain classes of programs compared to CPython, and it provides static typing (though this is different from mypy). Mypy differs in the following aspects, among others: + +- Cython is much more focused on performance than mypy. Mypy is only about static type checking, and increasing performance is not a direct goal. + +- The mypy syntax is arguably simpler and more "Pythonic" (no cdef/cpdef, etc.) for statically typed code. + +- The mypy syntax is compatible with Python. Mypy programs are normal Python programs that can be run using any Python implementation. Cython has many incompatible extensions to Python syntax, and Cython programs generally cannot be run without first compiling them to CPython extension modules via C. Cython also has a pure Python mode, but it seems to support only a subset of Cython functionality, and the syntax is quite verbose. + +- Mypy has a different set of type system features. For example, mypy has genericity (parametric polymorphism), function types and bidirectional type inference, which are not supported by Cython. (Cython has fused types that are different but related to mypy generics. Mypy also has a similar feature as an extension of generics.) + +- The mypy type checker knows about the static types of many Python stdlib modules and can effectively type check code that uses them. + +- Cython supports accessing C functions directly and many features are defined in terms of translating them to C or C++. Mypy just uses Python semantics, and mypy does not deal with accessing C library functionality. + +How is mypy different from Nuitka? +********************************** + +`Nuitka `_ is a static compiler that can translate Python programs to C++. Nuitka integrates with the CPython runtime. Nuitka has additional future goals, such as using type inference and whole-program analysis to further speed up code. Here are some differences: + +- Nuitka is primarily focused on speeding up Python code. Mypy focuses on static type checking and facilitating better tools. + +- Whole-program analysis tends to be slow and scale poorly to large or complex programs. It is still unclear if Nuitka can solve these issues. Mypy does not use whole-program analysis and will support modular type checking (though this has not been implemented yet). + +How is mypy different from RPython or Shed Skin? +************************************************ + +`RPython `_ and `Shed Skin `_ are basically statically typed subsets of Python. Mypy does the following important things differently: + +- Mypy supports both static and dynamic typing. Dynamically typed and statically typed code can be freely mixed and can interact seamlessly. + +- Mypy aims to support (in the future) fast and modular type checking. Both RPython and Shed Skin use whole-program type inference which is very slow, does not scale well to large programs and often produces confusing error messages. Mypy can support modularity since it only uses local type inference; static type checking depends on having type annotatations for functions signatures. + +- Mypy will support introspection, dynamic loading of code and many other dynamic language features (though using these may make static typing less effective). RPython and Shed Skin only support a restricted Python subset without several of these features. + +- Mypy supports user-defined generic types. + +Mypy is a cool project. Can I help? +*********************************** + +Any help is much appreciated! `Contact `_ the developers if you would like to contribute. Any help related to development, design, publicity, documentation, testing, web site maintenance, financing, etc. can be helpful. You can learn a lot by contributing, and anybody can help, even beginners! However, some knowledge of compilers and/or type systems is essential if you want to work on mypy internals. diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 000000000000..50ee205a4af1 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,20 @@ +.. Mypy documentation master file, created by + sphinx-quickstart on Sun Sep 14 19:50:35 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Mypy's documentation! +================================ + +.. toctree:: + :maxdepth: 2 + + tutorial + faq + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` + diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst new file mode 100644 index 000000000000..2017f5a1398c --- /dev/null +++ b/docs/source/tutorial.rst @@ -0,0 +1,867 @@ +Tutorial +======== + +Function Signatures +******************* + +A function without a type signature is dynamically typed. You can declare the signature of a function using the Python 3 annotation syntax This makes the function statically typed (the type checker reports type errors within the function): + +.. code-block:: python + + # Dynamically typed (identical to Python) + + def greeting(name): + return 'Hello, {}'.format(name) + +.. code-block:: python + + # Statically typed (still valid Python) + + def greeting(name: str) -> str: + return 'Hello, {}'.format(name) + +A None return type indicates a function that does not explicitly return a value. Using a None result in a statically typed context results in a type check error: + +.. code-block:: python + + def p() -> None: + print('hello') + + a = p() # Type check error: p has None return value + +The typing module +***************** + +We cheated a bit in the above examples: a module is type checked only if it imports the module typing. Here is a complete statically typed example from the previous section: + +.. code-block:: python + + import typing + + def greeting(name: str) -> str: + return 'Hello, {}'.format(name) + +The typing module contains many definitions that are useful in statically typed code. You can also use from ... import to import them (we'll explain Iterable later in this document): + +.. code-block:: python + + from typing import Iterable + + def greet_all(names: Iterable[str]) -> None: + for name in names: + print('Hello, {}'.format(name)) + +For brevity, we often omit the typing import in code examples, but you should always include it in modules that contain statically typed code. + +You can still have dynamically typed functions in modules that import typing: + +.. code-block:: python + + import typing + + def f(): + 1 + 'x' # No static type error (dynamically typed) + + def g() -> None: + 1 + 'x' # Type check error (statically typed) + +Mixing dynamic and static typing within a single file is often useful. For example, if you are migrating existing Python code to static typing, it may be easiest to do this incrementally, such as by migrating a few functions at a time. Also, when prototyping a new feature, you may decide to first implement the relevant code using dynamic typing and only add type signatures later, when the code is more stable. + +.. note:: + + Currently the type checker checks the top levels and annotated functions of all modules, even those that don't import typing. However, you should not rely on this, as this will change in the future. + +Type checking and running programs +********************************** + +You can type check a program by using the mypy tool, which is basically a linter — it checks you program for errors without actually running it:: + + $ mypy program.py + +You can always run a mypy program as a Python program, without type checking, even it it has type errors:: + + $ python3 program.py + +All errors reported by mypy are essentially warnings that you are free to ignore, if you so wish. + +The `README `_ explains how to download and install mypy. + +.. note:: + + Depending on how mypy is configured, you may have to explicitly use the Python interpreter to run mypy. The mypy tool is an ordinary mypy (and so also Python) program. + +Built-in types +************** + +These are examples of some of the most common built-in types: + +.. code:: python + + int # integer objects of arbitrary size + float # floating point number + bool # boolean value + str # unicode string + bytes # 8-bit string + object # the common base class + List[str] # list of str objects + Dict[str, int] # dictionary from str to int + Iterable[int] # iterable object containing ints + Sequence[bool] # sequence of booleans + Any # dynamically typed value + +The type Any and type constructors List, Dict, Iterable and Sequence are defined in the typing module. + +The type Dict is a *generic* class, signified by type arguments within [...]. For example, Dict[int, str] is a dictionary from integers to strings and and Dict[Any, Any] is a dictionary of dynamically typed (arbitrary) values and keys. List is another generic class. Dict and List are aliases for the built-ins dict and list, respectively. + +Iterable and Sequence are generic abstract base classes that correspond to Python protocols. For example, a str object is valid when Iterable[str] or Sequence[str] is expected. Note that even though they are similar to abstract base classes defined in abc.collections (formerly collections), they are not identical, since the built-in collection type objects do not support indexing. + +Type inference +************** + +The initial assignment defines a variable. If you do not explicitly specify the type of the variable, mypy infers the type based on the static type of the value expression: + +.. code:: python + + i = 1 # Infer type int for i + l = [1, 2] # Infer type List[int] for l + +Type inference is bidirectional and takes context into account. For example, the following is valid: + +.. code:: python + + def f(l: List[object]) -> None: + l = [1, 2] # Infer type List[object] for [1, 2] + +In an assignment, the type context is determined by the assignment target. In this case this is l, which has the type List[object]. The value expression [1, 2] is type checked in this context and given the type List[object]. In the previous example we introduced a new variable l, and here the type context was empty. + +Note that the following is not valid, since List[int] is not compatible with List[object]: + +.. code:: python + + def f(l: List[object], k: List[int]) -> None: + l = k # Type check error: incompatible types in assignment + +The reason why the above assignment is disallowed is that allowing the assignment could result in non-int values stored in a list of int: + +.. code:: python + + def f(l: List[object], k: List[int]) -> None: + l = k + l.append('x') + print(k[-1]) # Ouch; a string in List[int] + +You can still run the above program; it prints x. This illustrates the fact that static types are used during type checking, but they do not affect the runtime behavior of programs. You can run programs with type check failures, which is often very handy when performing a large refactoring. Thus you can always 'work around' the type system, and it doesn't really limit what you can do in your program. + +Type inference is not used in dynamically typed functions (those without an explicit return type) — every local variable type defaults to Any, which is discussed below. + +Explicit types for collections +****************************** + +The type checker cannot always infer the type of a list or a dictionary. This often arises when creating an empty list or dictionary and assigning it to a new variable without an explicit variable type. In these cases you can give the type explicitly using the type name as a constructor: + +.. code:: python + + l = List[int]() # Create empty list with type List[int] + d = Dict[str, int]() # Create empty dictionary (str -> int) + +Similarly, you can also give an explicit type when creating an empty set: + +.. code:: python + + s = Set[int]() + +Explicit types for variables +**************************** + +.. code:: python + + s = Undefined(str) # Declare type of x to be str. + s = 'x' # OK + s = 1 # Type check error + +The Undefined call evaluates to a special "Undefined" object that raises an exception on any operation: + +.. code:: python + + s = Undefined(str) + if s: # Runtime error: undefined value + print('hello') + +You can also override the inferred type of a variable by using a special comment after an assignment statement: + +.. code:: python + + x = [] # type: List[int] + +Here the # type comment applies both to the assignment target, in this case x, and also the initializer expression, via context. The above code is equivalent to this: + +.. code:: python + + x = List[int]() + +The type checker infers the value of a variable from the initializer, and if it is an empty collection such as [], the type is not well-defined. You can declare the collection type using one of the above syntax alternatives. + +User-defined types +****************** + +Each class is also a type. Any instance of a subclass is also compatible with all superclasses. All values are compatible with the object type (and also the Any type). + +.. code:: python + + class A: + def f(self) -> int: # Type of self inferred (A) + return 2 + + class B(A): + def f(self) -> int: + return 3 + def g(self) -> int: + return 4 + + a = B() # type: A # OK (explicit type for a; override type inference) + print(a.f()) # 3 + a.g() # Type check error: A has no method g + +The Any type +************ + +A value with the Any type is dynamically typed. Any operations are permitted on the value, and the operations are checked at runtime, similar to normal Python code. If you do not define a function return value or argument types, these default to Any. Also, a function without an explicit return type is dynamically typed. The body of a dynamically typed function is not checked statically. + +Any is compatible with every other type, and vice versa. No implicit type check is inserted when assigning a value of type Any to a variable with a more precise type: + +.. code:: python + + a, s = Undefined(Any), Undefined(str) + a = 2 # OK + s = a # OK + +Declared (and inferred) types are erased at runtime (they are basically treated as comments), and thus the above code does not generate a runtime error. + +Tuple types +*********** + +The type Tuple[t, ...] represents a tuple with the item types t, ...: + +.. code:: python + + def f(t: Tuple[int, str]) -> None: + t = 1, 'foo' # OK + t = 'foo', 1 # Type check error + +Class name forward references +***************************** + +Python does not allow references to a class object before the class is defined. Thus this code is does not work as expected: + +.. code:: python + + def f(x: A) -> None: # Error: Name A not defined + .... + + class A: + ... + +In cases like these you can enter the type as a string literal — this is a *forward reference*: + +.. code:: python + + def f(x: 'A') -> None: # OK + ... + + class A: + ... + +Of course, instead of using a string literal type, you could move the function definition after the class definition. This is not always desirable or even possible, though. + +Any type can be entered as a string literal, and youn can combine string-literal types with non-string-literal types freely: + +.. code:: python + + a = Undefined(List['A']) # OK + n = Undefined('int') # OK, though not useful + + class A: pass + +String literal types are never needed in # type comments. + +Instance and class attributes +***************************** + +Mypy type checker detects if you are trying to access a missing attribute, which is a very common programming error. For this to work correctly, instance and class attributes must be defined or initialized within the class. Mypy infers the types of attributes: + +.. code:: python + + class A: + def __init__(self, x: int) -> None: + self.x = x # Attribute x of type int + + a = A(1) + a.x = 2 # OK + a.y = 3 # Error: A has no attribute y + +This is a bit like each class having an implicitly defined __slots__ attribute. In Python semantics this is only enforced during type checking: at runtime we use standard Python semantics. You can selectively define a class as *dynamic*; dynamic classes have Python-like compile-time semantics, and they allow you to assign to arbitrary attributes anywhere in a program without the type checker complaining: + +.. code:: python + + from typing import Dynamic + + class A(Dynamic): + pass + + a = A() + a.x = 2 # OK, no need to define x explicitly. + +Mypy also lets you read arbitrary attributes of dynamic class instances. This limits type checking effectiveness, so you should only use dynamic classes when you really need them. + +.. note:: + + Dynamic classes are not implemented in the current mypy version. + +You can declare variables in the class body explicitly using Undefined or a type comment: + +.. code:: python + + class A: + x = Undefined(List[int]) # Declare attribute y of type List[int] + y = 0 # type: Any # Declare attribute x of type Any + + a = A() + a.x = [1] # OK + +As in Python, a variable defined in the class body can used as a class or an instance variable. + +Similarly, you can give explicit types to instance variables defined in a method: + +.. code:: python + + class A: + def __init__(self) -> None: + self.x = Undefined(List[int]) # OK + + def f(self) -> None: + self.y = 0 # type: Any # OK + +You can only define an instance variable within a method if you assign to it explicitly using self: + +.. code:: python + + class A: + def __init__(self) -> None: + self.y = 1 # Define y + a = self + a.x = 1 # Error: x not defined + +Overriding statically typed methods +*********************************** + +When overriding a statically typed method, mypy checks that the override has a compatible signature: + +.. code:: python + + class A: + def f(self, x: int) -> None: + ... + + class B(A): + def f(self, x: str) -> None: # Error: type of x incompatible + ... + + class C(A): + def f(self, x: int, y: int) -> None: # Error: too many arguments + ... + + class D(A): + def f(self, x: int) -> None: # OK + ... + +.. note:: + + You can also vary return types **covariantly** in overriding. For example, you could override the return type 'object' with a subtype such as 'int'. + +You can also override a statically typed method with a dynamically typed one. This allows dynamically typed code to override methods defined in library classes without worrying about their type signatures, similar to Python. + +There is no runtime enforcement that the method override returns a value that is compatible with the original return type, since types are erased in the Python semantics: + +.. code:: python + + class A: + def inc(self, x: int) -> int: + return x + 1 + + class B(A): + def inc(self, x): # Override, dynamically typed + return 'hello' + + b = B() + print(b.inc(1)) # hello + a = b # type: A + print(a.inc(1)) # hello + +Declaring multiple variable types on a line +******************************************* + +You can declare more than a single variable at a time. In order to nicely work with multiple assignment, you must give each variable a type separately: + +.. code:: python + + n, s = Undefined(int), Undefined(str) # Declare an integer and a string + i, found = 0, False # type: int, bool + +When using the latter form, you can optinally use parentheses around the types, assignment targets and assigned expression: + +.. code:: python + + i, found = 0, False # type: (int, bool) # OK + (i, found) = 0, False # type: int, bool # OK + i, found = (0, False) # type: int, bool # OK + (i, found) = (0, False) # type: (int, bool) # OK + +Dynamically typed code +********************** + +As mentioned earlier, bodies of functions that don't have have an explicit return type are dynamically typed (operations are checked at runtime). Code outside functions is statically typed by default, and types of variables are inferred. This does usually the right thing, but you can also make any variable dynamically typed by defining it explicitly with the type Any: + +.. code:: python + + from typing import Any + + s = 1 # Statically typed (type int) + d = 1 # type: Any # Dynamically typed (type Any) + s = 'x' # Type check error + d = 'x' # OK + +Alternatively, you can use the Undefined construct to define dynamically typed variables, as Any can be used anywhere any other type is valid: + +.. code:: python + + from typing import Undefined, Any + + d = Undefined(Any) + d = 1 # OK + d = 'x' # OK + +Additionally, if you don't import the typing module in a file, all code outside functions will be dynamically typed by default, and the file is not type checked at all. This mode makes it easy to include existing Python code that is not trivially compatible with static typing. + +.. note:: + + The current mypy version type checks all modules, even those that don't import typing. This will change in a future version. + +Abstract base classes and multiple inheritance +********************************************** + +Mypy uses Python abstract base classes for protocol types. There are several built-in abstract base classes types (for example, Sequence, Iterable and Iterator). You can define abstract base classes using the abc.ABCMeta metaclass and the abc.abstractmethod function decorator. + +.. code:: python + + from abc import ABCMeta, abstractmethod + import typing + + class A(metaclass=ABCMeta): + @abstractmethod + def foo(self, x: int) -> None: pass + + @abstractmethod + def bar(self) -> str: pass + + class B(A): + def foo(self, x: int) -> None: ... + def bar(self -> str: + return 'x' + + a = A() # Error: A is abstract + b = B() # OK + +Unlike most Python code, abstract base classes are likely to play a significant role in many complex mypy programs. + +A class can inherit any number of classes, both abstract and concrete. As with normal overrides, a dynamically typed method can implement a statically typed abstract method defined in an abstract base class. + +.. note:: + + There are also plans to support more Python-style "duck typing" in the type system. The details are still open. + +Function overloading +******************** + +You can define multiple instances of a function with the same name but different signatures. The first matching signature is selected at runtime when evaluating each individual call. This enables also a form of multiple dispatch. + +.. code:: python + + from typing import overload + + @overload + def abs(n: int) -> int: + return n if n >= 0 else -n + + @overload + def abs(n: float) -> float: + return n if n >= 0.0 else -n + + abs(-2) # 2 (int) + abs(-1.5) # 1.5 (float) + +Overloaded function variants still define a single runtime object; the following code is valid: + +.. code:: python + + my_abs = abs + my_abs(-2) # 2 (int) + my_abs(-1.5) # 1.5 (float) + +The overload variants must be adjacent in the code. This makes code clearer, and otherwise there would be awkward corner cases such as partially defined overloaded functions that could surprise the unwary programmer. + +.. note:: + + As generic type variables are erased at runtime, an overloaded function cannot dispatch based on a generic type argument, e.g. List[int] versus List[str]. + +Callable types and lambdas +************************** + +You can pass around function objects and bound methods in statically typed code. The type of a function that accepts arguments A1, ..., An and returns Rt is Function[[A1, ..., An], Rt]. Example: + +.. code:: python + + def twice(i: int, next: Function[[int], int]) -> int: + return next(next(i)) + + def add(i: int) -> int: + return i + 1 + + print(twice(3, add)) # 5 + +Lambdas are also supported. The lambda argument and return value types cannot be given explicitly; they are always inferred based on context using bidirectional type inference: + +.. code:: python + + l = map(lambda x: x + 1, [1, 2, 3]) # infer x as int and l as List[int] + +If you want to give the argument or return value types explicitly, use an ordinary, perhaps nested function definition. + +Casts +***** + +Mypy supports type casts that are usually used to coerce a statically typed value to a subtype. Unlike languages such as Java or C#, however, mypy casts are only used as hints for the type checker when using Python semantics, and they have no runtime effect. Use the function cast to perform a cast: + +.. code:: python + + from typing import cast + + o = [1] # type: object + x = cast(List[int], o) # OK + y = cast(List[str], o) # OK (cast performs no actual runtime check) + +Supporting runtime checking of casts such as the above when using Python semantics would require emulating reified generics and this would be difficult to do and would likely degrade performance and make code more difficult to read. You should not rely in your programs on casts being checked at runtime. Use an assertion if you want to perform an actual runtime check. Casts are used to silence spurious type checker warnings. + +You don't need a cast for expressions with type Any, of when assigning to a variable with type Any, as was explained earlier. + +You can cast to a dynamically typed value by just calling Any: + +.. code:: python + + from typing import Any + + def f(x: object) -> None: + Any(x).foo() # OK + +Notes about writing statically typed code +***************************************** + +Statically typed function bodies are often identical to normal Python code, but sometimes you need to do things slightly differently. This section introduces some of the most common cases which require different conventions in statically typed code. + +First, you need to specify the type when creating an empty list or dict and when you assign to a new variable, as mentioned earlier: + +.. code:: python + + a = List[int]() # Explicit type required in statically typed code + a = [] # Fine in a dynamically typed function, or if type + # of a has been declared or inferred before + +Sometimes you can avoid the explicit list item type by using a list comprehension. Here a type annotation is needed: + +.. code:: python + + l = List[int]() + for i in range(n): + l.append(i * i) + +.. note:: + + A future mypy version may be able to deal with cases such as the above without type annotations. + +No type annotation needed if using a list comprehension: + +.. code:: python + + l = [i * i for i in range(n)] + +However, in more complex cases the explicit type annotation can improve the clarity of your code, whereas a complex list comprehension can make your code difficult to understand. + +Second, each name within a function only has a single type. You can reuse for loop indices etc., but if you want to use a variable with multiple types within a single function, you may need to declare it with the Any type. + +.. code:: python + + def f() -> None: + n = 1 + ... + n = x # Type error: n has type int + +.. note:: + + This is another limitation that could be lifted in a future mypy version. + +Third, sometimes the inferred type is a subtype of the desired type. The type inference uses the first assignment to infer the type of a name: + +.. code:: python + + # Assume Shape is the base class of both Circle and Triangle. + shape = Circle() # Infer shape to be Circle + ... + shape = Triangle() # Type error: Triangle is not a Circle + +You can just give an explicit type for the variable in cases such the above example: + +.. code:: python + + shape = Circle() # type: Shape # The variable s can be any Shape, + # not just Circle + ... + shape = Triangle() # OK + +Fourth, if you use isinstance tests or other kinds of runtime type tests, you may have to add casts (this is similar to instanceof tests in Java): + +.. code:: python + + def f(o: object) -> None: + if isinstance(o, int): + n = cast(int, o) + n += 1 # o += 1 would be an error + ... + +Note that the object type used in the above example is similar to Object in Java: it only supports operations defined for all objects, such as equality and isinstance(). The type Any, in contrast, supports all operations, even if they may fail at runtime. The cast above would have been unnecessary if the type of o was Any. + +Some consider casual use of isinstance tests a sign of bad programming style. Often a method override or an overloaded function is a cleaner way of implementing functionality that depends on the runtime types of values. However, use whatever techniques that work for you. Sometimes isinstance tests *are* the cleanest way of implementing a piece of functionality. + +Type inference in mypy is designed to work well in common cases, to be predictable and to let the type checker give useful error messages. More powerful type inference strategies often have complex and difficult-to-prefict failure modes and could result in very confusing error messages. + +Defining generic classes +************************ + +The built-in collection classes are generic classes. Generic types have one or more type parameters, which can be arbitrary types. For example, Dict]int, str] has the type parameters int and str, and List[int] has a type parameter int. + +Programs can also define new generic classes. Here is a very simple generic class that represents a stack: + +.. code:: python + + from typing import typevar, Generic + + T = typevar('T') + + class Stack(Generic[T]): + def __init__(self) -> None: + self.items = List[T]() # Create an empty list with items of type T + + def push(self, item: T) -> None: + self.items.append(item) + + def pop(self) -> T: + return self.items.pop() + + def empty(self) -> bool: + return not self.items + +The Stack class can be used to represent a stack of any type: Stack[int], Stack[Tuple[int, str]], etc. + +Using Stack is similar to built-in container types: + +.. code:: python + + stack = Stack[int]() # Construct an empty Stack[int] instance + stack.push(2) + stack.pop() + stack.push('x') # Type error + +Type inference works for user-defined generic types as well: + +.. code:: python + + def process(stack: Stack[int]) -> None: ... + + process(Stack()) # Argument has inferred type Stack[int] + +Generic class internals +*********************** + +You may wonder what happens at runtime when you index Stack. Actually, indexing Stack just returns Stack: + +>>> print(Stack) + +>>> print(Stack[int]) + + +Note that built-in types list, dict and so on do not support indexing in Python. This is why we have the aliases List, Dict and so on in the typing module. Indexing these aliases just gives you the target class in Python, similar to Stack: + +>>> from typing import List +>>> List[int] + + +The above examples illustrate that type variables are erased at runtime when running in a Python VM. Generic Stack or list instances are just ordinary Python objects, and they have no extra runtime overhead or magic due to being generic, other than a metaclass that overloads the indexing operator. If you worry about the overhead introduced by the type indexing operation when constructing instances, you can often rewrite such code using a # type annotation, which has no runtime impact: + +.. code:: python + + x = List[int]() + x = [] # type: List[int] # Like the above but faster. + +The savings are rarely significant, but it could make a difference in a performance-critical loop or function. Function annotations, on the other hand, are only evaluated during the defintion of the function, not during every call. Constructing type objects in function signatures rarely has any noticeable performance impact. + +Generic functions +***************** + +Generic type variables can also be used to define generic functions: + +.. code:: python + + from typing import typevar, Sequence + + T = typevar('T') # Declare type variable + + def first(seq: Sequence[T]) -> T: # Generic function + return seq[0] + +As with generic classes, the type variable can be replaced with any type. That means first can we used with any sequence type, and the return type is derived from the sequence item type. For example: + +.. code:: python + + # Assume first defined as above. + + s = first('foo') # s has type str. + n = first([1, 2, 3]) # n has type int. + +Note also that a single definition of a type variable (such as T above) can be used in multiple generic functions or classes. In this example we use the same type variable in two generic functions: + +.. code:: python + + from typing typevar, Sequence + + T = typevar('T') # Declare type variable + + def first(seq: Sequence[T]) -> T: + return seq[0] + + def last(seq: Sequence[T]) -> T: + return seq[-1] + +You can also define generic methods — just use a type variable in the method signature that is different from class type variables. + +Supported Python features and modules +************************************* + +Lists of supported Python features and standard library modules are maintained in the mypy wiki: + +- `Supported Python features `_ +- `Supported Python modules `_ + +Runtime definition of methods and functions +******************************************* + +By default, mypy will not let you redefine functions or methods, and you can't add functions to a class or module outside its definition -- but only if this is visible to the type checker. This only affects static checking, as mypy performs no additional type checking at runtime. You can easily work around this. For example, you can use dynamically typed code or values with Any types, or you can use setattr or other introspection features. However, you need to be careful if you decide to do this. If used indiscriminately, you may have difficulty using static typing effectively, since the type checker cannot see functions defined at runtime. + +Additional features + +Several mypy features are not currently covered by this tutorial, including the following: + +- inheritance between generic classes + +- compatibility and subtyping of generic types, including covariance of generic types + +- super() + +Planned features +**************** + +This section introduces some language features that are still work in progress. + +None +---- + +Currently, None is a valid value for each type, similar to null or NULL in many languages. However, it is likely that this decision will be reversed, and types do not include None default. The Optional type modifier can be used to define a type variant that includes None, such as Optional(int): + +.. code:: python + + def f() -> Optional[int]: + return None # OK + + def g() -> int: + ... + return None # Error: None not compatible with int + +Also, most operations would not be supported on None values: + +.. code:: python + + def f(x: Optional[int]) -> int: + return x + 1 # Error: Cannot add None and int + +Instead, an explicit None check would be required. This would benefit from more powerful type inference: + +.. code:: python + + def f(x: Optional[int]) -> int: + if x is None: + return 0 + else: + # The inferred type of x is just int here. + return x + 1 + +We would infer the type of x to be int in the else block due to the check against None in the if condition. + +Union types +----------- + +Python functions often accept values of two or more different types. You can use overloading to model this in statically typed code, but union types can make code like this easier to write. + +Use the Union[...] type constructor to construct a union type. For example, the type Union[int, str] is compatible with both integers and strings. You can use an isinstance check to narrow down the type to a specific type: + +.. code:: python + + from typing import Union + + def f(x: Union[int, str]) -> None: + x + 1 # Error: str + int is not valid + if isinstance(x, int): + # Here type of x is int. + x + 1 # OK + else: + # Here type of x is str. + x + 'a' # OK + + f(1) # OK + f('x') # OK + f(1.1) # Error + +More general type inference +--------------------------- + +It may be useful to support type inference also for variables defined in multiple locations in an if/else statement, even if the initializer types are different: + +.. code:: python + + if x: + y = None # First definition of y + else: + y = 'a' # Second definition of y + +In the above example, both of the assignments would be used in type inference, and the type of y would be str. However, it is not obvious whether this would be generally desirable in more complex cases. + +Revision history +**************** + +List of major changes to this document: + +- Sep 15 2014: Migrated docs to Sphinx + +- Aug 25 2014: Don't discuss native semantics. There is only Python semantics. + +- Jul 2 2013: Rewrite to use new syntax. Shift focus to discussing Python semantics. Add more content, including short discussions of `generic functions `_ and `union types `_. + +- Dec 20 2012: Add new sections on explicit types for collections, declaring multiple variables, callable types, casts, generic classes and translation to Python. Add notes about writing statically typed code and links to the wiki. Also add a table of contents. Various other, more minor updates. + +- Dec 2 2012: Use new syntax for `list types `_ and `interfaces `_. Discuss `runtime redefinition of methods and functions `. Also did minor restructuring. From ad657c463eca5e07c677f85d01b2aad6cf71faad Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Mon, 15 Sep 2014 21:47:50 +0200 Subject: [PATCH 004/144] Fixed SemanticAnalyzer so it considers all lvalues in assignments to nested tuples. --- mypy/semanal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index eb0762fb5866..3b3727e07b36 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -731,6 +731,7 @@ def analyse_lvalue(self, lval: Node, nested: bool = False, Only if add_global is True, add name to globals table. If nested is true, the lvalue is within a tuple or list lvalue expression. """ + if isinstance(lval, NameExpr): nested_global = (not self.is_func_scope() and self.block_depth[-1] > 0 and @@ -791,7 +792,7 @@ def analyse_lvalue(self, lval: Node, nested: bool = False, elif isinstance(lval, ParenExpr): self.analyse_lvalue(lval.expr, nested, add_global, explicit_type) elif (isinstance(lval, TupleExpr) or - isinstance(lval, ListExpr)) and not nested: + isinstance(lval, ListExpr)): items = (Any(lval)).items for i in items: self.analyse_lvalue(i, nested=True, add_global=add_global, From 86f387c9cc0a2fb3d915a09e3f45eec49df3fac4 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sun, 21 Sep 2014 12:14:13 +0200 Subject: [PATCH 005/144] Recursively type check assignments to nested tupples/lists --- mypy/checker.py | 361 ++++++++++++++++++++++++------------------------ 1 file changed, 184 insertions(+), 177 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 044fbc5465b3..0d27ac54ee82 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -386,7 +386,7 @@ def visit_var_def(self, defn: VarDef) -> Type: if defn.items[0].type: # Explicit types. if len(defn.items) == 1: - self.check_single_assignment(defn.items[0].type, None, + self.check_nonindexed_assignment(defn.items[0].type, defn.init, defn.init) else: # Multiple assignment. @@ -919,75 +919,160 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> Type: Handle all kinds of assignment statements (simple, indexed, multiple). """ - self.check_assignments(self.expand_lvalues(s.lvalues[-1]), s.rvalue, - s.type) + self.check_assignment(s.lvalues[-1], s.rvalue, s.type) + if len(s.lvalues) > 1: # Chained assignment (e.g. x = y = ...). # Make sure that rvalue type will not be reinferred. rvalue = self.temp_node(self.type_map[s.rvalue], s) for lv in s.lvalues[:-1]: - self.check_assignments(self.expand_lvalues(lv), rvalue, - s.type) - - def check_assignments(self, lvalues: List[Node], - rvalue: Node, force_rvalue_type: Type=None) -> None: - # Collect lvalue types. Index lvalues require special consideration, - # since we cannot typecheck them until we know the rvalue type. - # For each lvalue, one of lvalue_types[i] or index_lvalues[i] is not - # None. - lvalue_types = [] # type: List[Type] # Each may be None - index_lvalues = [] # type: List[IndexExpr] # Each may be None - inferred = [] # type: List[Var] - is_inferred = False - - for lv in lvalues: - if self.is_definition(lv): - is_inferred = True - if isinstance(lv, NameExpr): - inferred.append(cast(Var, lv.node)) + self.check_assignment(lv, rvalue, s.type) + + + def check_assignment(self, lvalue: Node, rvalue: Node, force_rvalue_type: Type=None) -> None: + + if isinstance(lvalue, TupleExpr): + + # rhs must be TupleExpr, ListExpr, or types: iterable, tuple, any + + # TODO remove ParenExpr from rvalue + + if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr): + # Recursively go into Tuple or List expression rhs instead of + # using the type of rhs, because this allowed more fine grained + # control in cases like: a, b = [int, str] where rhs would get + # type List[object] + + rtuple = cast(TupleExpr, rvalue) # TODO + ltuple = cast(TupleExpr, lvalue) + + if len(rtuple.items) != len(ltuple.items): + self.msg.incompatible_value_count_in_assignment( + len(ltuple.items), len(rtuple.items), lvalue) else: - m = cast(MemberExpr, lv) - self.accept(m.expr) - inferred.append(m.def_var) - lvalue_types.append(None) - index_lvalues.append(None) - elif isinstance(lv, IndexExpr): - lvalue_types.append(None) - index_lvalues.append(lv) - inferred.append(None) - elif isinstance(lv, MemberExpr): - lvalue_types.append( - self.expr_checker.analyse_ordinary_member_access(lv, - True)) - self.store_type(lv, lvalue_types[-1]) - index_lvalues.append(None) - inferred.append(None) - elif isinstance(lv, NameExpr): - lvalue_types.append(self.expr_checker.analyse_ref_expr(lv)) - self.store_type(lv, lvalue_types[-1]) - index_lvalues.append(None) - inferred.append(None) + for lv, rv in zip(ltuple.items, rtuple.items): + self.check_assignment(lv, rv) # TODO force_rvalue_type else: - lvalue_types.append(self.accept(lv)) - index_lvalues.append(None) - inferred.append(None) - - if len(lvalues) == 1: - # Single lvalue. - rvalue_type = self.check_single_assignment(lvalue_types[0], - index_lvalues[0], rvalue, rvalue) - if rvalue_type and not force_rvalue_type: - self.binder.assign_type(lvalues[0], rvalue_type) + # TODO create type from lvalue to infer rvalues + + lvalues = lvalue.items # unpack tuple or list expr + + self.check_multi_assignment(lvalues, rvalue, lvalue) + + + elif isinstance(lvalue, ListExpr): + pass # TODO + elif isinstance(lvalue, ParenExpr): + self.check_assignment(lvalue.expr, rvalue) else: - rvalue_types = self.check_multi_assignment(lvalue_types, index_lvalues, - rvalue, rvalue) - if rvalue_types and not force_rvalue_type: - for lv, rt in zip(lvalues, rvalue_types): - self.binder.assign_type(lv, rt) - if is_inferred: - self.infer_variable_type(inferred, lvalues, self.accept(rvalue), - rvalue) - + lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) + + if lvalue_type: + rvalue_type = self.check_nonindexed_assignment(lvalue_type, rvalue, rvalue) + + if rvalue_type and not force_rvalue_type: + self.binder.assign_type(lvalue, rvalue_type) + elif index_lvalue: + self.check_indexed_assignment(index_lvalue, rvalue, rvalue) + + if inferred: + self.infer_variable_type(inferred, lvalue, self.accept(rvalue), + rvalue) + + + def check_multi_assignment(self, lvalues: List[Node], + rvalue: Node, + context: Context, + msg: str = None) -> List[Type]: + '''Check the assignment of one rvalue to a number of lvalues + for example from a ListExpr or TupleExpr''' + + if not msg: + msg = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT + + # First handle case where rvalue is of form Undefined, ... + rvalue_type = get_undefined_tuple(rvalue) + undefined_rvalue = True + if not rvalue_type: + # Infer the type of an ordinary rvalue expression. + rvalue_type = self.accept(rvalue) # TODO maybe elsewhere; redundant + undefined_rvalue = False + + if isinstance(rvalue_type, AnyType): + pass + elif isinstance(rvalue_type, TupleType): + # Rvalue with tuple type. + + if len(rvalue_type.items) != len(lvalues): + self.msg.incompatible_value_count_in_assignment( + len(lvalues), len(rvalue_type.items), context) + else: + if not undefined_rvalue: + # Create lvalue_type for type inference + # TODO do this better + + type_parameters = [] # type: List[Type] + for i in range(len(lvalues)): + sub_lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalues[i]) + + if sub_lvalue_type: + type_parameters.append(sub_lvalue_type) + else: # index lvalue + # TODO Figure out more precise type context, probably + # based on the type signature of the _set method. + type_parameters.append(rvalue_type.items[i]) + + lvalue_type = TupleType(type_parameters) + + # Infer rvalue again, now in the correct type context. + rvalue_type = cast(TupleType, self.accept(rvalue, + lvalue_type)) + + for lv, rv_type in zip(lvalues, rvalue_type.items): + self.check_assignment(lv, self.temp_node(rv_type)) + + elif (is_subtype(rvalue_type, + self.named_generic_type('typing.Iterable', + [AnyType()])) and + isinstance(rvalue_type, Instance)): + # Rvalue is iterable. + item_type = self.iterable_item_type(cast(Instance, rvalue_type)) + for lv in lvalues: + self.check_assignment(lv, + self.temp_node(item_type), + context, msg) + else: + self.fail(msg, context) + + + def check_lvalue(self, lvalue: Node) -> None: + lvalue_type = None + index_lvalue = None + inferred = None + + if self.is_definition(lvalue): + is_inferred = True + if isinstance(lvalue, NameExpr): + inferred = cast(Var, lvalue.node) + else: + m = cast(MemberExpr, lvalue) + self.accept(m.expr) + inferred = m.def_var + elif isinstance(lvalue, IndexExpr): + index_lvalue = lvalue + elif isinstance(lvalue, MemberExpr): + lvalue_type = self.expr_checker.analyse_ordinary_member_access(lvalue, + True) + self.store_type(lvalue, lvalue_type) + elif isinstance(lvalue, NameExpr): + lvalue_type = self.expr_checker.analyse_ref_expr(lvalue) + self.store_type(lvalue, lvalue_type) + else: + lvalue_type = self.accept(lvalue) + + return lvalue_type, index_lvalue, inferred + + def is_definition(self, s: Node) -> bool: if isinstance(s, NameExpr): if s.is_def: @@ -1005,6 +1090,8 @@ def is_definition(self, s: Node) -> bool: return False def expand_lvalues(self, n: Node) -> List[Node]: + print(n) + if isinstance(n, TupleExpr): return self.expr_checker.unwrap_list(n.items) elif isinstance(n, ListExpr): @@ -1014,7 +1101,8 @@ def expand_lvalues(self, n: Node) -> List[Node]: else: return [n] - def infer_variable_type(self, names: List[Var], lvalues: List[Node], + + def infer_variable_type(self, name: Var, lvalue: Node, init_type: Type, context: Context) -> None: """Infer the type of initialized variables from initializer type.""" if isinstance(init_type, Void): @@ -1029,34 +1117,7 @@ def infer_variable_type(self, names: List[Var], lvalues: List[Node], # Make the type more general (strip away function names etc.). init_type = strip_type(init_type) - if len(names) > 1: - if isinstance(init_type, TupleType): - # Initializer with a tuple type. - if len(init_type.items) == len(names): - for i in range(len(names)): - self.set_inferred_type(names[i], lvalues[i], - init_type.items[i]) - else: - self.msg.incompatible_value_count_in_assignment( - len(names), len(init_type.items), context) - elif (isinstance(init_type, Instance) and - is_subtype(init_type, - self.named_generic_type('typing.Iterable', - [AnyType()]))): - # Initializer with an iterable type. - item_type = self.iterable_item_type(cast(Instance, - init_type)) - for i in range(len(names)): - self.set_inferred_type(names[i], lvalues[i], item_type) - elif isinstance(init_type, AnyType): - for i in range(len(names)): - self.set_inferred_type(names[i], lvalues[i], AnyType()) - else: - self.fail(messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT, - context) - else: - for v in names: - self.set_inferred_type(v, lvalues[0], init_type) + self.set_inferred_type(name, lvalue, init_type) def set_inferred_type(self, var: Var, lvalue: Node, type: Type) -> None: """Store inferred variable type. @@ -1093,87 +1154,18 @@ def narrow_type_from_binder(self, expr: Node, known_type: Type) -> Type: return ans return known_type - def check_multi_assignment(self, lvalue_types: List[Type], - index_lvalues: List[IndexExpr], - rvalue: Node, - context: Context, - msg: str = None) -> List[Type]: - if not msg: - msg = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT - # First handle case where rvalue is of form Undefined, ... - rvalue_type = get_undefined_tuple(rvalue) - undefined_rvalue = True - if not rvalue_type: - # Infer the type of an ordinary rvalue expression. - rvalue_type = self.accept(rvalue) # TODO maybe elsewhere; redundant - undefined_rvalue = False - # Try to expand rvalue to lvalue(s). - rvalue_types = None # type: List[Type] - if isinstance(rvalue_type, AnyType): - pass - elif isinstance(rvalue_type, TupleType): - # Rvalue with tuple type. - items = [] # type: List[Type] - for i in range(len(lvalue_types)): - if lvalue_types[i]: - items.append(lvalue_types[i]) - elif i < len(rvalue_type.items): - # TODO Figure out more precise type context, probably - # based on the type signature of the _set method. - items.append(rvalue_type.items[i]) - if not undefined_rvalue: - # Infer rvalue again, now in the correct type context. - rvalue_type = cast(TupleType, self.accept(rvalue, - TupleType(items))) - if len(rvalue_type.items) != len(lvalue_types): - self.msg.incompatible_value_count_in_assignment( - len(lvalue_types), len(rvalue_type.items), context) - else: - # The number of values is compatible. Check their types. - for j in range(len(lvalue_types)): - self.check_single_assignment( - lvalue_types[j], index_lvalues[j], - self.temp_node(rvalue_type.items[j]), context, msg) - rvalue_types = rvalue_type.items - elif (is_subtype(rvalue_type, - self.named_generic_type('typing.Iterable', - [AnyType()])) and - isinstance(rvalue_type, Instance)): - # Rvalue is iterable. - rvalue_types = [] - item_type = self.iterable_item_type(cast(Instance, rvalue_type)) - for k in range(len(lvalue_types)): - type = self.check_single_assignment(lvalue_types[k], - index_lvalues[k], - self.temp_node(item_type), - context, msg) - rvalue_types.append(type) - else: - self.fail(msg, context) - return rvalue_types - - def check_single_assignment(self, - lvalue_type: Type, index_lvalue: IndexExpr, - rvalue: Node, context: Context, - msg: str = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT) -> Type: - """Type check an assignment. - - If lvalue_type is None, the index_lvalue argument must be the - index expr for indexed assignment (__setitem__). - Otherwise, lvalue_type is used as the type of the lvalue. - """ - if lvalue_type: - if refers_to_fullname(rvalue, 'typing.Undefined'): - # The rvalue is just 'Undefined'; this is always valid. - # Infer the type of 'Undefined' from the lvalue type. - self.store_type(rvalue, lvalue_type) - return None - rvalue_type = self.accept(rvalue, lvalue_type) - self.check_subtype(rvalue_type, lvalue_type, context, msg, - 'expression has type', 'variable has type') - return rvalue_type - elif index_lvalue: - self.check_indexed_assignment(index_lvalue, rvalue, context) + def check_nonindexed_assignment(self, lvalue_type: Type, rvalue: Node, + context: Node, + msg: str = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT) -> Type: + if refers_to_fullname(rvalue, 'typing.Undefined'): + # The rvalue is just 'Undefined'; this is always valid. + # Infer the type of 'Undefined' from the lvalue type. + self.store_type(rvalue, lvalue_type) + return None + rvalue_type = self.accept(rvalue, lvalue_type) + self.check_subtype(rvalue_type, lvalue_type, context, msg, + 'expression has type', 'variable has type') + return rvalue_type def check_indexed_assignment(self, lvalue: IndexExpr, rvalue: Node, context: Context) -> None: @@ -1318,7 +1310,7 @@ def visit_operator_assignment_stmt(self, if isinstance(s.lvalue, IndexExpr): lv = cast(IndexExpr, s.lvalue) - self.check_single_assignment(None, lv, s.rvalue, s.rvalue) + self.check_indexed_assignment(lv, s.rvalue, s.rvalue) else: if not is_subtype(rvalue_type, lvalue_type): self.msg.incompatible_operator_assignment(s.op, s) @@ -1360,7 +1352,7 @@ def visit_try_stmt(self, s: TryStmt) -> Type: if s.types[i]: t = self.exception_type(s.types[i]) if s.vars[i]: - self.check_assignments([s.vars[i]], + self.check_assignment(s.vars[i], self.temp_node(t, s.vars[i])) self.binder.push_frame() self.accept(s.handlers[i]) @@ -1464,23 +1456,38 @@ def analyse_iterable_item_type(self, expr: Node) -> Type: def analyse_index_variables(self, index: List[NameExpr], is_annotated: bool, - item_type: Type, context: Context) -> None: + item_type: Type, context: Context) -> None: """Type check or infer for loop or list comprehension index vars.""" if not is_annotated: + + # TODO FIX temp: + + return + + + # Create a temporary copy of variables with Node item type. # TODO this is ugly node_index = [] # type: List[Node] for i in index: node_index.append(i) + self.check_assignments(node_index, self.temp_node(item_type, context)) elif len(index) == 1: v = cast(Var, index[0].node) if v.type: - self.check_single_assignment(v.type, None, + self.check_nonindexed_assignment(v.type, self.temp_node(item_type), context, messages.INCOMPATIBLE_TYPES_IN_FOR) else: + + # TODO FIX temp: + + return + + + t = [] # type: List[Type] for ii in index: v = cast(Var, ii.node) @@ -1525,7 +1532,7 @@ def visit_with_stmt(self, s: WithStmt) -> Type: enter = echk.analyse_external_member_access('__enter__', ctx, expr) obj = echk.check_call(enter, [], [], expr)[0] if name: - self.check_assignments([name], self.temp_node(obj, expr)) + self.check_assignment(name, self.temp_node(obj, expr)) exit = echk.analyse_external_member_access('__exit__', ctx, expr) arg = self.temp_node(AnyType(), expr) echk.check_call(exit, [arg] * 3, [nodes.ARG_POS] * 3, expr) From 9e822f06518008e8ee99868d371c594304d3c2df Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sun, 21 Sep 2014 18:11:14 +0200 Subject: [PATCH 006/144] Cleanup and small fixes --- mypy/checker.py | 86 ++++++++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 48 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 0d27ac54ee82..035a55de51ca 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2,7 +2,7 @@ import itertools -from typing import Undefined, Any, Dict, Set, List, cast, overload, Tuple, Function, typevar +from typing import Undefined, Any, Dict, Set, List, cast, overload, Tuple, Function, typevar, Union from mypy.errors import Errors from mypy.nodes import ( @@ -386,7 +386,7 @@ def visit_var_def(self, defn: VarDef) -> Type: if defn.items[0].type: # Explicit types. if len(defn.items) == 1: - self.check_nonindexed_assignment(defn.items[0].type, + self.check_single_assignment(defn.items[0].type, defn.init, defn.init) else: # Multiple assignment. @@ -928,14 +928,15 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> Type: for lv in s.lvalues[:-1]: self.check_assignment(lv, rvalue, s.type) - def check_assignment(self, lvalue: Node, rvalue: Node, force_rvalue_type: Type=None) -> None: - if isinstance(lvalue, TupleExpr): + if isinstance(lvalue, ParenExpr): + self.check_assignment(lvalue.expr, rvalue) - # rhs must be TupleExpr, ListExpr, or types: iterable, tuple, any + elif isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr): + assert not force_rvalue_type or isinstance(force_rvalue_type, TuppleType) - # TODO remove ParenExpr from rvalue + rvalue = self.remove_parens(rvalue) if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr): # Recursively go into Tuple or List expression rhs instead of @@ -943,32 +944,31 @@ def check_assignment(self, lvalue: Node, rvalue: Node, force_rvalue_type: Type=N # control in cases like: a, b = [int, str] where rhs would get # type List[object] - rtuple = cast(TupleExpr, rvalue) # TODO - ltuple = cast(TupleExpr, lvalue) + rtuple = cast(Union[TupleExpr, ListExpr], rvalue) + ltuple = cast(Union[TupleExpr, ListExpr], lvalue) if len(rtuple.items) != len(ltuple.items): self.msg.incompatible_value_count_in_assignment( len(ltuple.items), len(rtuple.items), lvalue) else: - for lv, rv in zip(ltuple.items, rtuple.items): - self.check_assignment(lv, rv) # TODO force_rvalue_type + force_rvalue_types = None + if force_rvalue_type: + assert len(rtuple.items) == len(force_rvalue_type.items) + force_rvalue_types = force_rvalue_type.items + else: + force_rvalue_types = [None] * len(rtuple.items) + + for lv, rv, forced_type in zip(ltuple.items, rtuple.items, force_rvalue_types): + self.check_assignment(lv, rv, forced_type) else: - # TODO create type from lvalue to infer rvalues - lvalues = lvalue.items # unpack tuple or list expr - self.check_multi_assignment(lvalues, rvalue, lvalue) - - elif isinstance(lvalue, ListExpr): - pass # TODO - elif isinstance(lvalue, ParenExpr): - self.check_assignment(lvalue.expr, rvalue) else: lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) if lvalue_type: - rvalue_type = self.check_nonindexed_assignment(lvalue_type, rvalue, rvalue) + rvalue_type = self.check_single_assignment(lvalue_type, rvalue, rvalue) if rvalue_type and not force_rvalue_type: self.binder.assign_type(lvalue, rvalue_type) @@ -979,6 +979,11 @@ def check_assignment(self, lvalue: Node, rvalue: Node, force_rvalue_type: Type=N self.infer_variable_type(inferred, lvalue, self.accept(rvalue), rvalue) + def remove_parens(self, node: Node) -> Node: + if isinstance(node, ParenExpr): + return self.remove_parens(node.expr) + else: + return node def check_multi_assignment(self, lvalues: List[Node], rvalue: Node, @@ -1001,8 +1006,7 @@ def check_multi_assignment(self, lvalues: List[Node], if isinstance(rvalue_type, AnyType): pass elif isinstance(rvalue_type, TupleType): - # Rvalue with tuple type. - + # Rvalue with tuple type. if len(rvalue_type.items) != len(lvalues): self.msg.incompatible_value_count_in_assignment( len(lvalues), len(rvalue_type.items), context) @@ -1023,13 +1027,13 @@ def check_multi_assignment(self, lvalues: List[Node], type_parameters.append(rvalue_type.items[i]) lvalue_type = TupleType(type_parameters) - + # Infer rvalue again, now in the correct type context. rvalue_type = cast(TupleType, self.accept(rvalue, - lvalue_type)) + lvalue_type)) for lv, rv_type in zip(lvalues, rvalue_type.items): - self.check_assignment(lv, self.temp_node(rv_type)) + self.check_assignment(lv, self.temp_node(rv_type), rv_type) elif (is_subtype(rvalue_type, self.named_generic_type('typing.Iterable', @@ -1038,9 +1042,7 @@ def check_multi_assignment(self, lvalues: List[Node], # Rvalue is iterable. item_type = self.iterable_item_type(cast(Instance, rvalue_type)) for lv in lvalues: - self.check_assignment(lv, - self.temp_node(item_type), - context, msg) + self.check_assignment(lv, self.temp_node(item_type), item_type) else: self.fail(msg, context) @@ -1089,19 +1091,6 @@ def is_definition(self, s: Node) -> bool: return s.is_def return False - def expand_lvalues(self, n: Node) -> List[Node]: - print(n) - - if isinstance(n, TupleExpr): - return self.expr_checker.unwrap_list(n.items) - elif isinstance(n, ListExpr): - return self.expr_checker.unwrap_list(n.items) - elif isinstance(n, ParenExpr): - return self.expand_lvalues(n.expr) - else: - return [n] - - def infer_variable_type(self, name: Var, lvalue: Node, init_type: Type, context: Context) -> None: """Infer the type of initialized variables from initializer type.""" @@ -1154,18 +1143,19 @@ def narrow_type_from_binder(self, expr: Node, known_type: Type) -> Type: return ans return known_type - def check_nonindexed_assignment(self, lvalue_type: Type, rvalue: Node, - context: Node, - msg: str = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT) -> Type: + def check_single_assignment(self, lvalue_type: Type, rvalue: Node, + context: Node, + msg: str = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT) -> Type: if refers_to_fullname(rvalue, 'typing.Undefined'): # The rvalue is just 'Undefined'; this is always valid. # Infer the type of 'Undefined' from the lvalue type. self.store_type(rvalue, lvalue_type) return None - rvalue_type = self.accept(rvalue, lvalue_type) - self.check_subtype(rvalue_type, lvalue_type, context, msg, - 'expression has type', 'variable has type') - return rvalue_type + else: + rvalue_type = self.accept(rvalue, lvalue_type) + self.check_subtype(rvalue_type, lvalue_type, context, msg, + 'expression has type', 'variable has type') + return rvalue_type def check_indexed_assignment(self, lvalue: IndexExpr, rvalue: Node, context: Context) -> None: @@ -1477,7 +1467,7 @@ def analyse_index_variables(self, index: List[NameExpr], elif len(index) == 1: v = cast(Var, index[0].node) if v.type: - self.check_nonindexed_assignment(v.type, + self.check_single_assignment(v.type, self.temp_node(item_type), context, messages.INCOMPATIBLE_TYPES_IN_FOR) else: From a8bb094cbeefc05afbaf2bb6024f63d9d1262975 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 12:58:31 -0700 Subject: [PATCH 007/144] Remove obsolete file --- mypy/test/data/fixtures/icodegen.py | 35 ----------------------------- 1 file changed, 35 deletions(-) delete mode 100644 mypy/test/data/fixtures/icodegen.py diff --git a/mypy/test/data/fixtures/icodegen.py b/mypy/test/data/fixtures/icodegen.py deleted file mode 100644 index d7e06650feb2..000000000000 --- a/mypy/test/data/fixtures/icodegen.py +++ /dev/null @@ -1,35 +0,0 @@ -# These builtins stubs are used implicitly in parse-tree to icode generation -# test cases (testicodegen.py and test/data/icode-basic.test). - -from typing import typevar, Generic, builtinclass - -t = typevar('t') - -@builtinclass -class object: - def __init__(self) -> None: pass - -class type: pass -class str: pass - -# Primitive types are special in generated code. - -class int: - def __add__(self, n: int) -> int: pass - def __sub__(self, n: int) -> int: pass - def __mul__(self, n: int) -> int: pass - def __neg__(self) -> int: pass - def __pos__(self) -> int: pass - def __eq__(self, n: int) -> bool: pass - def __ne__(self, n: int) -> bool: pass - def __lt__(self, n: int) -> bool: pass - def __gt__(self, n: int) -> bool: pass - def __le__(self, n: int) -> bool: pass - def __ge__(self, n: int) -> bool: pass - -class float: pass -class bool: pass - -class list(Generic[t]): pass - -def print(*object) -> None: pass From 6dca530f42897e779dcc330818e32f04c1133572 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 14:12:17 -0700 Subject: [PATCH 008/144] Remove obsolete transform-related code --- mypy/build.py | 22 +- mypy/transform.py | 425 --------------------------- mypy/transformfunc.py | 368 ----------------------- mypy/transformtype.py | 660 ------------------------------------------ tests.py | 2 - 5 files changed, 1 insertion(+), 1476 deletions(-) delete mode 100644 mypy/transform.py delete mode 100644 mypy/transformfunc.py delete mode 100644 mypy/transformtype.py diff --git a/mypy/build.py b/mypy/build.py index bcb245fdef52..9b1dec06e2f8 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -26,7 +26,6 @@ from mypy.errors import Errors, CompileError from mypy import parse from mypy import stats -from mypy import transform debug = False @@ -35,7 +34,6 @@ # Build targets (for selecting compiler passes) SEMANTIC_ANALYSIS = 0 # Semantic analysis only TYPE_CHECK = 1 # Type check -TRANSFORM = 3 # Type check and transform for runtime type checking # Build flags @@ -274,9 +272,6 @@ class BuildManager: Item (m, n) indicates whether m depends on n (directly or indirectly). missing_modules: Set of modules that could not be imported encountered so far - - TODO Refactor code related to transformation to external objects. This module - should not directly depend on them. """ def __init__(self, data_dir: str, @@ -474,9 +469,7 @@ def is_module(self, id: str) -> bool: def final_passes(self, files: List[MypyFile], types: Dict[Node, Type]) -> None: """Perform the code generation passes for type checked files.""" - if self.target == TRANSFORM: - self.transform(files) - elif self.target in [SEMANTIC_ANALYSIS, TYPE_CHECK]: + if self.target in [SEMANTIC_ANALYSIS, TYPE_CHECK]: pass # Nothing to do. else: raise RuntimeError('Unsupported target %d' % self.target) @@ -492,19 +485,6 @@ def get_python_out_path(self, f: MypyFile) -> str: components[-1] += '.py' return os.path.join(self.output_dir, *components) - def transform(self, files: List[MypyFile]) -> None: - for f in files: - if f.fullname() == 'typing': - # The typing module is special and is currently not - # transformed. - continue - # Transform parse tree and produce pretty-printed output. - v = transform.DyncheckTransformVisitor( - self.type_checker.type_map, - self.semantic_analyzer.modules, - is_pretty=True) - f.accept(v) - def log(self, message: str) -> None: if VERBOSE in self.flags: print('LOG: %s' % message) diff --git a/mypy/transform.py b/mypy/transform.py deleted file mode 100644 index 96f77ceb3969..000000000000 --- a/mypy/transform.py +++ /dev/null @@ -1,425 +0,0 @@ -"""Transform program to include explicit coercions and wrappers. - -The transform performs these main changes: - - - add explicit coercions to/from any (or more generally, between different - levels of typing precision) - - add wrapper methods and functions for calling statically typed functions - in dynamically typed code - - add wrapper methods for overrides with a different signature - - add generic wrapper classes for coercions between generic types (e.g. - from List[Any] to List[str]) -""" - -from typing import Undefined, Dict, List, Tuple, cast - -from mypy.nodes import ( - Node, MypyFile, TypeInfo, ClassDef, VarDef, FuncDef, Var, - ReturnStmt, AssignmentStmt, IfStmt, WhileStmt, MemberExpr, NameExpr, MDEF, - CallExpr, SuperExpr, TypeExpr, CastExpr, OpExpr, CoerceExpr, ComparisonExpr, - GDEF, SymbolTableNode, IndexExpr, function_type -) -from mypy.traverser import TraverserVisitor -from mypy.types import Type, AnyType, Callable, TypeVarDef, Instance -from mypy.lex import Token -from mypy.transformtype import TypeTransformer -from mypy.transutil import ( - prepend_arg_type, is_simple_override, tvar_arg_name, dynamic_suffix, - add_arg_type_after_self -) -from mypy.coerce import coerce -from mypy.rttypevars import translate_runtime_type_vars_in_context - - -class DyncheckTransformVisitor(TraverserVisitor): - """Translate a parse tree to use runtime representation of generics. - - Translate generic type variables to ordinary variables and all make - all non-trivial coercions explicit. Also generate generic wrapper classes - for coercions between generic types and wrapper methods for overrides - and for more efficient access from dynamically typed code. - - This visitor modifies the parse tree in-place. - """ - - type_map = Undefined(Dict[Node, Type]) - modules = Undefined(Dict[str, MypyFile]) - is_pretty = False - type_tf = Undefined(TypeTransformer) - - # Stack of function return types - return_types = Undefined(List[Type]) - # Stack of dynamically typed function flags - dynamic_funcs = Undefined(List[bool]) - - # Associate a Node with its start end line numbers. - line_map = Undefined(Dict[Node, Tuple[int, int]]) - - is_java = False - - # The current type context (or None if not within a type). - _type_context = None # type: TypeInfo - - def type_context(self) -> TypeInfo: - return self._type_context - - def __init__(self, type_map: Dict[Node, Type], - modules: Dict[str, MypyFile], is_pretty: bool, - is_java: bool = False) -> None: - self.type_tf = TypeTransformer(self) - self.return_types = [] - self.dynamic_funcs = [False] - self.line_map = {} - self.type_map = type_map - self.modules = modules - self.is_pretty = is_pretty - self.is_java = is_java - - # - # Transform definitions - # - - def visit_mypy_file(self, o: MypyFile) -> None: - """Transform an file.""" - res = [] # type: List[Node] - for d in o.defs: - if isinstance(d, ClassDef): - self._type_context = d.info - res.extend(self.type_tf.transform_class_def(d)) - self._type_context = None - else: - d.accept(self) - res.append(d) - o.defs = res - - def visit_var_def(self, o: VarDef) -> None: - """Transform a variable definition in-place. - - This is not suitable for member variable definitions; they are - transformed in TypeTransformer. - """ - super().visit_var_def(o) - - if o.init is not None: - if o.items[0].type: - t = o.items[0].type - else: - t = AnyType() - o.init = self.coerce(o.init, t, self.get_type(o.init), - self.type_context()) - - def visit_func_def(self, fdef: FuncDef) -> None: - """Transform a global function definition in-place. - - This is not suitable for methods; they are transformed in - FuncTransformer. - """ - self.prepend_generic_function_tvar_args(fdef) - self.transform_function_body(fdef) - - def transform_function_body(self, fdef: FuncDef) -> None: - """Transform the body of a function.""" - self.dynamic_funcs.append(fdef.is_implicit) - # FIX overloads - self.return_types.append(cast(Callable, function_type(fdef)).ret_type) - super().visit_func_def(fdef) - self.return_types.pop() - self.dynamic_funcs.pop() - - def prepend_generic_function_tvar_args(self, fdef: FuncDef) -> None: - """Add implicit function type variable arguments if fdef is generic.""" - sig = cast(Callable, function_type(fdef)) - tvars = sig.variables - if not fdef.type: - fdef.type = sig - - tv = [] # type: List[Var] - ntvars = len(tvars) - if fdef.is_method(): - # For methods, add type variable arguments after the self arg. - for n in range(ntvars): - tv.append(Var(tvar_arg_name(-1 - n))) - fdef.type = add_arg_type_after_self(cast(Callable, fdef.type), - AnyType()) - fdef.args = [fdef.args[0]] + tv + fdef.args[1:] - else: - # For ordinary functions, prepend type variable arguments. - for n in range(ntvars): - tv.append(Var(tvar_arg_name(-1 - n))) - fdef.type = prepend_arg_type(cast(Callable, fdef.type), - AnyType()) - fdef.args = tv + fdef.args - fdef.init = List[AssignmentStmt]([None]) * ntvars + fdef.init - - # - # Transform statements - # - - def transform_block(self, block: List[Node]) -> None: - for stmt in block: - stmt.accept(self) - - def visit_return_stmt(self, s: ReturnStmt) -> None: - super().visit_return_stmt(s) - s.expr = self.coerce(s.expr, self.return_types[-1], - self.get_type(s.expr), self.type_context()) - - def visit_assignment_stmt(self, s: AssignmentStmt) -> None: - super().visit_assignment_stmt(s) - if isinstance(s.lvalues[0], IndexExpr): - index = cast(IndexExpr, s.lvalues[0]) - method_type = index.method_type - if self.dynamic_funcs[-1] or isinstance(method_type, AnyType): - lvalue_type = AnyType() # type: Type - else: - method_callable = cast(Callable, method_type) - # TODO arg_types[1] may not be reliable - lvalue_type = method_callable.arg_types[1] - else: - lvalue_type = self.get_type(s.lvalues[0]) - - s.rvalue = self.coerce2(s.rvalue, lvalue_type, self.get_type(s.rvalue), - self.type_context()) - - # - # Transform expressions - # - - def visit_member_expr(self, e: MemberExpr) -> None: - super().visit_member_expr(e) - - typ = self.get_type(e.expr) - - if self.dynamic_funcs[-1]: - e.expr = self.coerce_to_dynamic(e.expr, typ, self.type_context()) - typ = AnyType() - - if isinstance(typ, Instance): - # Reference to a statically-typed method variant with the suffix - # derived from the base object type. - suffix = self.get_member_reference_suffix(e.name, typ.type) - else: - # Reference to a dynamically-typed method variant. - suffix = self.dynamic_suffix() - e.name += suffix - - def visit_name_expr(self, e: NameExpr) -> None: - super().visit_name_expr(e) - if e.kind == MDEF and isinstance(e.node, FuncDef): - # Translate reference to a method. - suffix = self.get_member_reference_suffix(e.name, e.info) - e.name += suffix - # Update representation to have the correct name. - prefix = e.repr.components[0].pre - - def get_member_reference_suffix(self, name: str, info: TypeInfo) -> str: - if info.has_method(name): - fdef = cast(FuncDef, info.get_method(name)) - return self.type_suffix(fdef) - else: - return '' - - def visit_call_expr(self, e: CallExpr) -> None: - if e.analyzed: - # This is not an ordinary call. - e.analyzed.accept(self) - return - - super().visit_call_expr(e) - - # Do no coercions if this is a call to debugging facilities. - if self.is_debugging_call_expr(e): - return - - # Get the type of the callable (type variables in the context of the - # enclosing class). - ctype = self.get_type(e.callee) - - # Add coercions for the arguments. - for i in range(len(e.args)): - arg_type = AnyType() # type: Type - if isinstance(ctype, Callable): - arg_type = ctype.arg_types[i] - e.args[i] = self.coerce2(e.args[i], arg_type, - self.get_type(e.args[i]), - self.type_context()) - - # Prepend type argument values to the call as needed. - if isinstance(ctype, Callable) and cast(Callable, - ctype).bound_vars != []: - bound_vars = (cast(Callable, ctype)).bound_vars - - # If this is a constructor call (target is the constructor - # of a generic type or superclass __init__), include also - # instance type variables. Otherwise filter them away -- - # include only generic function type variables. - if (not (cast(Callable, ctype)).is_type_obj() and - not (isinstance(e.callee, SuperExpr) and - (cast(SuperExpr, e.callee)).name == '__init__')): - # Filter instance type variables; only include function tvars. - bound_vars = [(id, t) for id, t in bound_vars if id < 0] - - args = [] # type: List[Node] - for i in range(len(bound_vars)): - # Compile type variables to runtime type variable expressions. - tv = translate_runtime_type_vars_in_context( - bound_vars[i][1], - self.type_context(), - self.is_java) - args.append(TypeExpr(tv)) - e.args = args + e.args - - def is_debugging_call_expr(self, e): - return isinstance(e.callee, NameExpr) and e.callee.name in ['__print'] - - def visit_cast_expr(self, e: CastExpr) -> None: - super().visit_cast_expr(e) - if isinstance(self.get_type(e), AnyType): - e.expr = self.coerce(e.expr, AnyType(), self.get_type(e.expr), - self.type_context()) - - def visit_op_expr(self, e: OpExpr) -> None: - super().visit_op_expr(e) - if e.op in ['and', 'or']: - target = self.get_type(e) - e.left = self.coerce(e.left, target, - self.get_type(e.left), self.type_context()) - e.right = self.coerce(e.right, target, - self.get_type(e.right), self.type_context()) - else: - method_type = e.method_type - if self.dynamic_funcs[-1] or isinstance(method_type, AnyType): - e.left = self.coerce_to_dynamic(e.left, self.get_type(e.left), - self.type_context()) - e.right = self.coerce(e.right, AnyType(), - self.get_type(e.right), - self.type_context()) - elif method_type: - method_callable = cast(Callable, method_type) - operand = e.right - # TODO arg_types[0] may not be reliable - operand = self.coerce(operand, method_callable.arg_types[0], - self.get_type(operand), - self.type_context()) - e.right = operand - - def visit_comparison_expr(self, e: ComparisonExpr) -> None: - super().visit_comparison_expr(e) - # Dummy - - - def visit_index_expr(self, e: IndexExpr) -> None: - if e.analyzed: - # Actually a type application, not indexing. - e.analyzed.accept(self) - return - super().visit_index_expr(e) - method_type = e.method_type - if self.dynamic_funcs[-1] or isinstance(method_type, AnyType): - e.base = self.coerce_to_dynamic(e.base, self.get_type(e.base), - self.type_context()) - e.index = self.coerce_to_dynamic(e.index, self.get_type(e.index), - self.type_context()) - else: - method_callable = cast(Callable, method_type) - e.index = self.coerce(e.index, method_callable.arg_types[0], - self.get_type(e.index), self.type_context()) - - # - # Helpers - # - - def get_type(self, node: Node) -> Type: - """Return the type of a node as reported by the type checker.""" - return self.type_map[node] - - def set_type(self, node: Node, typ: Type) -> None: - self.type_map[node] = typ - - def type_suffix(self, fdef: FuncDef, info: TypeInfo = None) -> str: - """Return the suffix for a mangled name. - - This includes an optional type suffix for a function or method. - """ - if not info: - info = fdef.info - # If info is None, we have a global function => no suffix. Also if the - # method is not an override, we need no suffix. - if not info or (not info.bases or - not info.bases[0].type.has_method(fdef.name())): - return '' - elif is_simple_override(fdef, info): - return self.type_suffix(fdef, info.bases[0].type) - elif self.is_pretty: - return '`' + info.name() - else: - return '__' + info.name() - - def dynamic_suffix(self) -> str: - """Return the suffix of the dynamic wrapper of a method or class.""" - return dynamic_suffix(self.is_pretty) - - def wrapper_class_suffix(self) -> str: - """Return the suffix of a generic wrapper class.""" - return '**' - - def coerce(self, expr: Node, target_type: Type, source_type: Type, - context: TypeInfo, is_wrapper_class: bool = False) -> Node: - return coerce(expr, target_type, source_type, context, - is_wrapper_class, self.is_java) - - def coerce2(self, expr: Node, target_type: Type, source_type: Type, - context: TypeInfo, is_wrapper_class: bool = False) -> Node: - """Create coercion from source_type to target_type. - - Also include middle coercion do 'Any' if transforming a dynamically - typed function. - """ - if self.dynamic_funcs[-1]: - return self.coerce(self.coerce(expr, AnyType(), source_type, - context, is_wrapper_class), - target_type, AnyType(), context, - is_wrapper_class) - else: - return self.coerce(expr, target_type, source_type, context, - is_wrapper_class) - - def coerce_to_dynamic(self, expr: Node, source_type: Type, - context: TypeInfo) -> Node: - if isinstance(source_type, AnyType): - return expr - source_type = translate_runtime_type_vars_in_context( - source_type, context, self.is_java) - return CoerceExpr(expr, AnyType(), source_type, False) - - def add_line_mapping(self, orig_node: Node, new_node: Node) -> None: - """Add a line mapping for a wrapper. - - The node new_node has logically the same line numbers as - orig_node. The nodes should be FuncDef/ClassDef nodes. - """ - if orig_node.repr: - start_line = orig_node.line - end_line = start_line # TODO use real end line - self.line_map[new_node] = (start_line, end_line) - - def named_type(self, name: str) -> Instance: - # TODO combine with checker - # Assume that the name refers to a type. - sym = self.lookup(name, GDEF) - return Instance(cast(TypeInfo, sym.node), []) - - def lookup(self, fullname: str, kind: int) -> SymbolTableNode: - # TODO combine with checker - # TODO remove kind argument - parts = fullname.split('.') - n = self.modules[parts[0]] - for i in range(1, len(parts) - 1): - n = cast(MypyFile, ((n.names.get(parts[i], None).node))) - return n.names[parts[-1]] - - def object_member_name(self) -> str: - if self.is_java: - return '__o_{}'.format(self.type_context().name()) - else: - return '__o' diff --git a/mypy/transformfunc.py b/mypy/transformfunc.py deleted file mode 100644 index 21c18c92ef8f..000000000000 --- a/mypy/transformfunc.py +++ /dev/null @@ -1,368 +0,0 @@ -"""Transform functions for runtime type checking.""" - -from typing import Undefined, List, Tuple, cast - -from mypy.nodes import ( - FuncDef, Var, Node, Block, TypeInfo, NameExpr, MemberExpr, - CallExpr, ReturnStmt, ExpressionStmt, TypeExpr, function_type, VarDef -) -from mypy import nodes -from mypy.checker import map_type_from_supertype -from mypy.types import Callable, AnyType, Void, RuntimeTypeVar, Type -from mypy.replacetvars import replace_type_vars -import mypy.transform -from mypy.transutil import ( - is_simple_override, tvar_arg_name, self_expr, dynamic_sig, is_generic, - add_arg_type_after_self, translate_type_vars_to_bound_vars, - translate_function_type_vars_to_dynamic, replace_ret_type, - translate_type_vars_to_wrapper_vars, - translate_type_vars_to_wrapped_object_vars -) -from mypy.erasetype import erase_generic_types - - -# TODO -# - overloads -# - generate semantic analysis info during transform (e.g. -# transformMethodImplementation, Var constructors, NameExpr) - - -class FuncTransformer: - """Transform methods for runtime type checking. - - This is used by DyncheckTransformVisitor and TypeTransformer is logically - forms a single unit with these classes. - """ - - # Used for common transformation operations. - tf = Undefined('mypy.transform.DyncheckTransformVisitor') - - def __init__(self, tf: 'mypy.transform.DyncheckTransformVisitor') -> None: - self.tf = tf - - def transform_method(self, fdef: FuncDef) -> List[FuncDef]: - """Transform a method. - - The result is one or more methods. - """ - # Transform the body of the method. - self.tf.transform_function_body(fdef) - - res = Undefined # type: List[FuncDef] - - if fdef.is_constructor(): - # The method is a constructor. Constructors are transformed to one - # method. - res = [self.transform_method_implementation(fdef, fdef.name())] - else: - # Normal methods are transformed to 1-3 variants. The - # first is the main implementation of the method, and the - # second is the dynamically-typed wrapper. The third - # variant is for method overrides, and represents the - # overridden supertype method. - - res = [self.transform_method_implementation( - fdef, fdef.name() + self.tf.type_suffix(fdef))] - - if fdef.info.bases and fdef.info.mro[1].has_method(fdef.name()): - # Override. - # TODO do not assume single inheritance - - # Is is an override with a different signature? For - # trivial overrides we can inherit wrappers. - if not is_simple_override(fdef, fdef.info): - # Create a wrapper for overridden superclass method. - res.append(self.override_method_wrapper(fdef)) - # Create a dynamically-typed method wrapper. - res.append(self.dynamic_method_wrapper(fdef)) - else: - # Not an override. - - # Create a dynamically-typed method wrapper. - res.append(self.dynamic_method_wrapper(fdef)) - - return res - - def transform_method_implementation(self, fdef: FuncDef, - name: str) -> FuncDef: - """Transform the implementation of a method (i.e. unwrapped).""" - args = fdef.args - arg_kinds = fdef.arg_kinds - - typ = function_type(fdef) # type: Type - init = fdef.init_expressions() - - if fdef.name() == '__init__' and is_generic(fdef): - args, arg_kinds, init, typ = self.add_constructor_tvar_args( - fdef, typ, args, arg_kinds, init) - - fdef2 = FuncDef(name, args, arg_kinds, init, fdef.body, typ) - fdef2.info = fdef.info - - self.tf.prepend_generic_function_tvar_args(fdef2) - - return fdef2 - - def add_constructor_tvar_args( - self, fdef: FuncDef, typ: Type, - args: List[Var], arg_kinds: List[int], - init: List[Node]) -> Tuple[List[Var], List[int], List[Node], Type]: - """Add type variable arguments for __init__ of a generic type. - - Return tuple (new args, new kinds, new inits). - """ - tv = [] # type: List[Var] - ntvars = len(fdef.info.type_vars) - for n in range(ntvars): - tv.append(Var(tvar_arg_name(n + 1))) - typ = add_arg_type_after_self(cast(Callable, typ), AnyType()) - args = [args[0]] + tv + args[1:] - arg_kinds = [arg_kinds[0]] + [nodes.ARG_POS] * ntvars + arg_kinds[1:] - init = List[Node]([None]) * ntvars + init - return (args, arg_kinds, init, typ) - - def override_method_wrapper(self, fdef: FuncDef) -> FuncDef: - """Construct a method wrapper for an overridden method.""" - orig_fdef = fdef.info.mro[1].get_method(fdef.name()) - return self.method_wrapper(cast(FuncDef, orig_fdef), fdef, False, - False) - - def dynamic_method_wrapper(self, fdef: FuncDef) -> FuncDef: - """Construct a dynamically typed method wrapper.""" - return self.method_wrapper(fdef, fdef, True, False) - - def generic_method_wrappers(self, fdef: FuncDef) -> List[Node]: - """Construct wrapper class methods for a method of a generic class.""" - return [self.generic_static_method_wrapper(fdef), - self.generic_dynamic_method_wrapper(fdef)] - - def generic_static_method_wrapper(self, fdef: FuncDef) -> FuncDef: - """Construct statically typed wrapper class method.""" - return self.method_wrapper(fdef, fdef, False, True) - - def generic_dynamic_method_wrapper(self, fdef: FuncDef) -> FuncDef: - """Construct dynamically-typed wrapper class method.""" - return self.method_wrapper(fdef, fdef, True, True) - - def method_wrapper(self, act_as_func_def: FuncDef, - target_func_def: FuncDef, is_dynamic: bool, - is_wrapper_class: bool) -> FuncDef: - """Construct a method wrapper. - - It acts as a specific method (with the same signature), coerces - arguments, calls the target method and finally coerces the return - value. - """ - is_override = act_as_func_def.info != target_func_def.info - - # Determine suffixes. - target_suffix = self.tf.type_suffix(target_func_def) - wrapper_suffix = self.get_wrapper_suffix(act_as_func_def, is_dynamic) - - # Determine function signatures. - target_sig = self.get_target_sig(act_as_func_def, target_func_def, - is_dynamic, is_wrapper_class) - wrapper_sig = self.get_wrapper_sig(act_as_func_def, is_dynamic) - call_sig = self.get_call_sig(act_as_func_def, target_func_def.info, - is_dynamic, is_wrapper_class, is_override) - - if is_wrapper_class: - bound_sig = cast(Callable, - translate_type_vars_to_bound_vars(target_sig)) - else: - bound_sig = None - - call_stmt = self.call_wrapper(act_as_func_def, is_dynamic, - is_wrapper_class, target_sig, call_sig, - target_suffix, bound_sig) - - wrapper_args = self.get_wrapper_args(act_as_func_def, is_dynamic) - wrapper_func_def = FuncDef(act_as_func_def.name() + wrapper_suffix, - wrapper_args, - act_as_func_def.arg_kinds, - [None] * len(wrapper_args), - Block([call_stmt]), - wrapper_sig) - - self.tf.add_line_mapping(target_func_def, wrapper_func_def) - - if is_wrapper_class and not is_dynamic: - self.tf.prepend_generic_function_tvar_args(wrapper_func_def) - - return wrapper_func_def - - def get_target_sig(self, act_as_func_def: FuncDef, - target_func_def: FuncDef, - is_dynamic: bool, is_wrapper_class: bool) -> Callable: - """Return the target method signature for a method wrapper.""" - sig = cast(Callable, function_type(target_func_def)) - if is_wrapper_class: - if sig.is_generic() and is_dynamic: - sig = cast(Callable, - translate_function_type_vars_to_dynamic(sig)) - return cast(Callable, - translate_type_vars_to_wrapped_object_vars(sig)) - elif is_dynamic: - if sig.is_generic(): - return cast(Callable, - translate_function_type_vars_to_dynamic(sig)) - else: - return sig - else: - return sig - - def get_wrapper_sig(self, act_as_func_def: FuncDef, - is_dynamic: bool) -> Callable: - """Return the signature of the wrapper method. - - The wrapper method signature has an additional type variable - argument (with type 'Any'), and all type variables have been - erased. - """ - sig = cast(Callable, function_type(act_as_func_def)) - if is_dynamic: - return dynamic_sig(sig) - elif is_generic(act_as_func_def): - return cast(Callable, erase_generic_types(sig)) # FIX REFACTOR? - else: - return sig - - def get_call_sig(self, act_as_func_def: FuncDef, - current_class: TypeInfo, is_dynamic: bool, - is_wrapper_class: bool, is_override: bool) -> Callable: - """Return the source signature in a wrapped call. - - It has type variables replaced with 'Any', but as an - exception, type variables are intact in the return type in - generic wrapper classes. The exception allows omitting an - extra return value coercion, as the target return type and the - source return type will be the same. - """ - sig = cast(Callable, function_type(act_as_func_def)) - if is_dynamic: - return dynamic_sig(sig) - elif is_generic(act_as_func_def): - call_sig = sig - # If this is an override wrapper, keep type variables - # intact. Otherwise replace them with dynamic to get - # desired coercions that check argument types. - if not is_override or is_wrapper_class: - call_sig = (cast(Callable, replace_type_vars(call_sig, False))) - else: - call_sig = cast(Callable, map_type_from_supertype( - call_sig, current_class, act_as_func_def.info)) - if is_wrapper_class: - # Replace return type with the original return within - # wrapper classes to get rid of an unnecessary - # coercion. There will still be a coercion due to the - # extra coercion generated for generic wrapper - # classes. However, function generic type variables - # still need to be replaced, as the wrapper does not - # affect them. - ret = sig.ret_type - if is_dynamic: - ret = translate_function_type_vars_to_dynamic(ret) - call_sig = replace_ret_type( - call_sig, translate_type_vars_to_wrapper_vars(ret)) - return call_sig - else: - return sig - - def get_wrapper_args(self, act_as_func_def: FuncDef, - is_dynamic: bool) -> List[Var]: - """Return the formal arguments of a wrapper method. - - These may include the type variable argument. - """ - args = [] # type: List[Var] - for a in act_as_func_def.args: - args.append(Var(a.name())) - return args - - def call_wrapper(self, fdef: FuncDef, is_dynamic: bool, - is_wrapper_class: bool, target_ann: Callable, - cur_ann: Callable, target_suffix: str, - bound_sig: Callable) -> Node: - """Return the body of wrapper method. - - The body contains only a call to the wrapped method and a - return statement (if the call returns a value). Arguments are coerced - to the target signature. - """ - args = self.call_args(fdef.args, target_ann, cur_ann, is_dynamic, - is_wrapper_class, bound_sig, - ismethod=fdef.is_method()) - selfarg = args[0] - args = args[1:] - - member = fdef.name() + target_suffix - if not is_wrapper_class: - callee = MemberExpr(selfarg, member) - else: - callee = MemberExpr( - MemberExpr(self_expr(), self.tf.object_member_name()), member) - - call = CallExpr(callee, - args, - [nodes.ARG_POS] * len(args), - [None] * len(args)) # type: Node - if bound_sig: - call = self.tf.coerce(call, bound_sig.ret_type, - target_ann.ret_type, self.tf.type_context(), - is_wrapper_class) - call = self.tf.coerce(call, cur_ann.ret_type, bound_sig.ret_type, - self.tf.type_context(), is_wrapper_class) - else: - call = self.tf.coerce(call, cur_ann.ret_type, target_ann.ret_type, - self.tf.type_context(), is_wrapper_class) - if not isinstance(target_ann.ret_type, Void): - return ReturnStmt(call) - else: - return ExpressionStmt(call) - - def call_args(self, vars: List[Var], target_ann: Callable, - cur_ann: Callable, is_dynamic: bool, is_wrapper_class: bool, - bound_sig: Callable = None, - ismethod: bool = False) -> List[Node]: - """Construct the arguments of a wrapper call expression. - - Insert coercions as needed. - """ - args = [] # type: List[Node] - # Add ordinary arguments, including self (for methods). - for i in range(len(vars)): - a = vars[i] - name = NameExpr(a.name()) - if bound_sig is None: - args.append(self.tf.coerce(name, target_ann.arg_types[i], - cur_ann.arg_types[i], - self.tf.type_context(), - is_wrapper_class)) - else: - c = self.tf.coerce(name, bound_sig.arg_types[i], - cur_ann.arg_types[i], - self.tf.type_context(), is_wrapper_class) - args.append(self.tf.coerce(c, target_ann.arg_types[i], - bound_sig.arg_types[i], - self.tf.type_context(), - is_wrapper_class)) - # Add type variable arguments for a generic function. - for i in range(len(target_ann.variables)): - # Non-dynamic wrapper method in a wrapper class passes - # generic function type arguments to the target function; - # otherwise use dynamic types. - index = i - if ismethod: - index += 1 - if is_wrapper_class and not is_dynamic: - args.insert(index, - TypeExpr(RuntimeTypeVar(NameExpr(tvar_arg_name(-i - 1))))) - else: - args.insert(index, TypeExpr(AnyType())) - return args - - def get_wrapper_suffix(self, func_def: FuncDef, is_dynamic: bool) -> str: - if is_dynamic: - return self.tf.dynamic_suffix() - else: - return self.tf.type_suffix(func_def) diff --git a/mypy/transformtype.py b/mypy/transformtype.py deleted file mode 100644 index 2c4cf99d6c54..000000000000 --- a/mypy/transformtype.py +++ /dev/null @@ -1,660 +0,0 @@ -"""Transform classes for runtime type checking.""" - -from typing import Undefined, List, Set, Any, cast, Tuple, Dict - -from mypy.nodes import ( - ClassDef, Node, FuncDef, VarDef, Block, Var, ExpressionStmt, - TypeInfo, SuperExpr, NameExpr, CallExpr, MDEF, MemberExpr, ReturnStmt, - AssignmentStmt, TypeExpr, PassStmt, SymbolTableNode -) -from mypy import nodes -from mypy.semanal import self_type -from mypy.types import ( - Callable, Instance, Type, AnyType, BOUND_VAR, Void, RuntimeTypeVar, - UnboundType -) -from mypy.checkmember import analyse_member_access -from mypy.checkexpr import type_object_type -from mypy.subtypes import map_instance_to_supertype -import mypy.transform -from mypy.transformfunc import FuncTransformer -from mypy.transutil import ( - self_expr, tvar_slot_name, tvar_arg_name, prepend_arg_type -) -from mypy.rttypevars import translate_runtime_type_vars_locally -from mypy.compileslotmap import find_slot_origin -from mypy.coerce import coerce -from mypy.maptypevar import num_slots, get_tvar_access_path -from mypy import erasetype - - -class TypeTransformer: - """Class for transforming type definitions for runtime type checking. - - Transform a type definition by modifying it in-place. - - The following transformations are performed: - - * Represent generic type variables explicitly as attributes. - * Create generic wrapper classes used by coercions to different type - args. - * Create wrapper methods needed when overriding methods with different - signatures. - * Create wrapper methods for calling methods in dynamically typed code. - These perform the necessary coercions for arguments and return values - to/from 'Any'. - - This is used by DyncheckTransformVisitor and is logically aggregated within - that class. - """ - - # Used for common transformation operations. - tf = Undefined('mypy.transform.DyncheckTransformVisitor') - # Used for transforming methods. - func_tf = Undefined(FuncTransformer) - - def __init__(self, tf: 'mypy.transform.DyncheckTransformVisitor') -> None: - self.tf = tf - self.func_tf = FuncTransformer(tf) - - def transform_class_def(self, tdef: ClassDef) -> List[Node]: - """Transform a type definition. - - The result may be one or two definitions. The first is the - transformation of the original ClassDef. The second is a - wrapper type, which is generated for generic types only. - """ - defs = [] # type: List[Node] - - if tdef.info.type_vars: - # This is a generic type. Insert type variable slots in - # the class definition for new type variables, i.e. type - # variables not mapped to superclass type variables. - defs.extend(self.make_tvar_representation(tdef.info)) - - # Iterate over definitions and transform each of them. - vars = set() # type: Set[Var] - for d in tdef.defs.body: - if isinstance(d, FuncDef): - # Implicit cast from FuncDef[] to Node[] is safe below. - defs.extend(Any(self.func_tf.transform_method(d))) - elif isinstance(d, VarDef): - defs.extend(self.transform_var_def(d)) - for n in d.items: - vars.add(n) - elif isinstance(d, AssignmentStmt): - self.transform_assignment(d) - defs.append(d) - - # Add accessors for implicitly defined attributes. - for node in tdef.info.names.values(): - if isinstance(node.node, Var): - v = cast(Var, node.node) - if v.info == tdef.info and v not in vars: - defs.extend(self.make_accessors(v)) - - # For generic classes, add an implicit __init__ wrapper. - defs.extend(self.make_init_wrapper(tdef)) - - if tdef.is_generic() or (tdef.info.bases and - tdef.info.mro[1].is_generic()): - self.make_instance_tvar_initializer( - cast(FuncDef, tdef.info.get_method('__init__'))) - - if not defs: - defs.append(PassStmt()) - - if tdef.is_generic(): - gen_wrapper = self.generic_class_wrapper(tdef) - - tdef.defs = Block(defs) - - dyn_wrapper = self.make_type_object_wrapper(tdef) - - if not tdef.is_generic(): - return [tdef, dyn_wrapper] - else: - return [tdef, dyn_wrapper, gen_wrapper] - - def make_init_wrapper(self, tdef: ClassDef) -> List[Node]: - """Make and return an implicit __init__ if class needs it. - - Otherwise, return an empty list. We include an implicit - __init__ if the class is generic or if it extends a generic class - and if it does not define __init__. - - The __init__ of a generic class requires one or more extra type - variable arguments. The inherited __init__ may not accept these. - - For example, assume these definitions: - - . class A(Generic[T]): pass - . class B(A[int]): pass - - The constructor for B will be (equivalent to) - - . def __init__(self: B) -> None: - . self.__tv = - . super().__init__() - """ - - # FIX overloading, default args / varargs, keyword args - - info = tdef.info - - if '__init__' not in info.names and ( - tdef.is_generic() or (info.bases and - info.mro[1].is_generic())): - # Generic class with no explicit __init__ method - # (i.e. __init__ inherited from superclass). Generate a - # wrapper that initializes type variable slots and calls - # the superclass __init__ method. - - base = info.mro[1] - selftype = self_type(info) - callee_type = cast(Callable, analyse_member_access( - '__init__', selftype, None, False, True, None, None, - base)) - - # Now the callee type may contain the type variables of a - # grandparent as bound type variables, but we want the - # type variables of the parent class. Explicitly set the - # bound type variables. - callee_type = self.fix_bound_init_tvars(callee_type, - map_instance_to_supertype(selftype, base)) - - super_init = cast(FuncDef, base.get_method('__init__')) - - # Build argument list. - args = [Var('self')] - for i in range(1, len(super_init.args)): - args.append(Var(super_init.args[i].name())) - args[-1].type = callee_type.arg_types[i - 1] - - selft = self_type(self.tf.type_context()) - callee_type = prepend_arg_type(callee_type, selft) - - creat = FuncDef('__init__', args, - super_init.arg_kinds, [None] * len(args), - Block([])) - creat.info = tdef.info - creat.type = callee_type - creat.is_implicit = False - tdef.info.names['__init__'] = SymbolTableNode(MDEF, creat, - typ=creat.type) - - # Insert a call to superclass constructor. If the - # superclass is object, the constructor does nothing => - # omit the call. - if base.fullname() != 'builtins.object': - creat.body.body.append( - self.make_superclass_constructor_call(tdef.info, - callee_type)) - - # Implicit cast from FuncDef[] to Node[] is safe below. - return Any(self.func_tf.transform_method(creat)) - else: - return [] - - def fix_bound_init_tvars(self, callable: Callable, - typ: Instance) -> Callable: - """Replace bound type vars of callable with args from instance type.""" - a = [] # type: List[Tuple[int, Type]] - for i in range(len(typ.args)): - a.append((i + 1, typ.args[i])) - return Callable(callable.arg_types, callable.arg_kinds, - callable.arg_names, callable.ret_type, - callable.is_type_obj(), callable.name, - callable.variables, a) - - def make_superclass_constructor_call( - self, info: TypeInfo, callee_type: Callable) -> ExpressionStmt: - """Construct a statement that calls the superclass constructor. - - In particular, it passes any type variables arguments as needed. - """ - callee = SuperExpr('__init__') - callee.info = info - - # We do not handle generic constructors. Either pass runtime - # type variables from the current scope or perhaps require - # explicit constructor in this case. - - selftype = self_type(info) - - # FIX overloading - # FIX default args / varargs - - # Map self type to the superclass context. - base = info.mro[1] - selftype = map_instance_to_supertype(selftype, base) - - super_init = cast(FuncDef, base.get_method('__init__')) - - # Add constructor arguments. - args = [] # type: List[Node] - for n in range(1, callee_type.min_args): - args.append(NameExpr(super_init.args[n].name())) - self.tf.set_type(args[-1], callee_type.arg_types[n]) - - # Store callee type after stripping away the 'self' type. - self.tf.set_type(callee, nodes.method_callable(callee_type)) - - call = CallExpr(callee, args, [nodes.ARG_POS] * len(args)) - return ExpressionStmt(call) - - def transform_var_def(self, o: VarDef) -> List[Node]: - """Transform a member variable definition. - - The result may be one or more definitions. - """ - res = [o] # type: List[Node] - - self.tf.visit_var_def(o) - - # Add $x and set$x accessor wrappers for data attributes. These let - # derived classes redefine a data attribute as a property. - for n in o.items: - res.extend(self.make_accessors(n)) - - return res - - def transform_assignment(self, o: AssignmentStmt) -> None: - """Transform an assignment statement in class body.""" - self.tf.visit_assignment_stmt(o) - - def make_accessors(self, n: Var) -> List[Node]: - if n.type: - t = n.type - else: - t = AnyType() - return [self.make_getter_wrapper(n.name(), t), - self.make_setter_wrapper(n.name(), t), - self.make_dynamic_getter_wrapper(n.name(), t), - self.make_dynamic_setter_wrapper(n.name(), t)] - - def make_getter_wrapper(self, name: str, typ: Type) -> FuncDef: - """Create a getter wrapper for a data attribute. - - The getter will be of this form: - - . def $name*(self: C) -> type: - . return self.name! - """ - scope = self.make_scope() - selft = self.self_type() - selfv = scope.add('self', selft) - - member_expr = MemberExpr(scope.name_expr('self'), name, direct=True) - ret = ReturnStmt(member_expr) - - wrapper_name = '$' + name - sig = Callable([selft], [nodes.ARG_POS], [None], typ, False) - fdef = FuncDef(wrapper_name, - [selfv], - [nodes.ARG_POS], - [None], - Block([ret]), sig) - fdef.info = self.tf.type_context() - return fdef - - def make_dynamic_getter_wrapper(self, name: str, typ: Type) -> FuncDef: - """Create a dynamically-typed getter wrapper for a data attribute. - - The getter will be of this form: - - . def $name*(self: C) -> Any: - . return {Any <= typ self.name!} - """ - scope = self.make_scope() - selft = self.self_type() - selfv = scope.add('self', selft) - - member_expr = MemberExpr(scope.name_expr('self'), name, direct=True) - coerce_expr = coerce(member_expr, AnyType(), typ, - self.tf.type_context()) - ret = ReturnStmt(coerce_expr) - - wrapper_name = '$' + name + self.tf.dynamic_suffix() - sig = Callable([selft], [nodes.ARG_POS], [None], AnyType(), False) - return FuncDef(wrapper_name, - [selfv], - [nodes.ARG_POS], - [None], - Block([ret]), sig) - - def make_setter_wrapper(self, name: str, typ: Type) -> FuncDef: - """Create a setter wrapper for a data attribute. - - The setter will be of this form: - - . def set$name(self: C, name: typ) -> None: - . self.name! = name - """ - scope = self.make_scope() - selft = self.self_type() - selfv = scope.add('self', selft) - namev = scope.add(name, typ) - - lvalue = MemberExpr(scope.name_expr('self'), name, direct=True) - rvalue = scope.name_expr(name) - ret = AssignmentStmt([lvalue], rvalue) - - wrapper_name = 'set$' + name - sig = Callable([selft, typ], - [nodes.ARG_POS, nodes.ARG_POS], - [None, None], - Void(), False) - fdef = FuncDef(wrapper_name, - [selfv, namev], - [nodes.ARG_POS, nodes.ARG_POS], - [None, None], - Block([ret]), sig) - fdef.info = self.tf.type_context() - return fdef - - def make_dynamic_setter_wrapper(self, name: str, typ: Type) -> FuncDef: - """Create a dynamically-typed setter wrapper for a data attribute. - - The setter will be of this form: - - . def set$name*(self: C, name; Any) -> None: - . self.name! = {typ name} - """ - lvalue = MemberExpr(self_expr(), name, direct=True) - name_expr = NameExpr(name) - rvalue = coerce(name_expr, typ, AnyType(), self.tf.type_context()) - ret = AssignmentStmt([lvalue], rvalue) - - wrapper_name = 'set$' + name + self.tf.dynamic_suffix() - selft = self_type(self.tf.type_context()) - sig = Callable([selft, AnyType()], - [nodes.ARG_POS, nodes.ARG_POS], - [None, None], - Void(), False) - return FuncDef(wrapper_name, - [Var('self'), Var(name)], - [nodes.ARG_POS, nodes.ARG_POS], - [None, None], - Block([ret]), sig) - - def generic_accessor_wrappers(self, s: AssignmentStmt) -> List[Node]: - """Construct wrapper class methods for attribute accessors.""" - res = [] # type: List[Node] - assert len(s.lvalues) == 1 - assert isinstance(s.lvalues[0], NameExpr) - assert s.type is not None - name = cast(NameExpr, s.lvalues[0]) - for fd in [self.make_getter_wrapper(name.name, s.type), - self.make_setter_wrapper(name.name, s.type)]: - res.extend(self.func_tf.generic_method_wrappers(fd)) - return res - - def generic_class_wrapper(self, tdef: ClassDef) -> ClassDef: - """Construct a wrapper class for a generic type.""" - # FIX semanal meta-info for nodes + TypeInfo - - defs = [] # type: List[Node] - - # Does the type have a superclass, other than builtins.object? - base = tdef.info.mro[1] - has_proper_superclass = base.fullname() != 'builtins.object' - - if not has_proper_superclass or self.tf.is_java: - # Generate member variables for wrapper object. - defs.extend(self.make_generic_wrapper_member_vars(tdef)) - - for alt in [False, BOUND_VAR]: - defs.extend(self.make_tvar_representation(tdef.info, alt)) - - # Generate constructor. - defs.append(self.make_generic_wrapper_init(tdef.info)) - - # Generate method wrappers. - for d in tdef.defs.body: - if isinstance(d, FuncDef): - if not d.is_constructor(): - defs.extend(self.func_tf.generic_method_wrappers(d)) - elif isinstance(d, AssignmentStmt): - defs.extend(self.generic_accessor_wrappers(d)) - elif not isinstance(d, PassStmt): - raise RuntimeError( - 'Definition {} at line {} not supported'.format( - type(d), d.line)) - - base_type = self.tf.named_type('builtins.object') # type: Type - # Inherit superclass wrapper if there is one. - if has_proper_superclass: - base = self.find_generic_base_class(tdef.info) - if base: - # TODO bind the type somewhere - base_type = UnboundType(base.defn.name + - self.tf.wrapper_class_suffix()) - - # Build the type definition. - wrapper = ClassDef(tdef.name + self.tf.wrapper_class_suffix(), - Block(defs), - None, - [base_type]) - # FIX fullname - - self.tf.add_line_mapping(tdef, wrapper) - - return wrapper - - def find_generic_base_class(self, info: TypeInfo) -> TypeInfo: - base = info.mro[1] - while True: - if base.type_vars != []: - return base - if len(base.mro) <= 1: - return None - base = base.mro[1] - - def make_generic_wrapper_member_vars(self, tdef: ClassDef) -> List[Node]: - """Generate member variable definition for wrapped object (__o). - - This is added to a generic wrapper class. - """ - # The type is 'Any' since it should behave covariantly in subclasses. - return [VarDef([Var(self.object_member_name(tdef.info), - AnyType())], False, None)] - - def object_member_name(self, info: TypeInfo) -> str: - if self.tf.is_java: - return '__o_{}'.format(info.name) - else: - return '__o' - - def make_generic_wrapper_init(self, info: TypeInfo) -> FuncDef: - """Build constructor of a generic wrapper class.""" - nslots = num_slots(info) - - cdefs = [] # type: List[Node] - - # Build superclass constructor call. - base = info.mro[1] - if base.fullname() != 'builtins.object' and self.tf.is_java: - s = SuperExpr('__init__') - cargs = [NameExpr('__o')] # type: List[Node] - for n in range(num_slots(base)): - cargs.append(NameExpr(tvar_arg_name(n + 1))) - for n in range(num_slots(base)): - cargs.append(NameExpr(tvar_arg_name(n + 1, BOUND_VAR))) - c = CallExpr(s, cargs, [nodes.ARG_POS] * len(cargs)) - cdefs.append(ExpressionStmt(c)) - - # Create initialization of the wrapped object. - cdefs.append(AssignmentStmt([MemberExpr( - self_expr(), - self.object_member_name(info), - direct=True)], - NameExpr('__o'))) - - # Build constructor arguments. - args = [Var('self'), Var('__o')] - init = [None, None] # type: List[Node] - - for alt in [False, BOUND_VAR]: - for n in range(nslots): - args.append(Var(tvar_arg_name(n + 1, alt))) - init.append(None) - - nargs = nslots * 2 + 2 - fdef = FuncDef('__init__', - args, - [nodes.ARG_POS] * nargs, - init, - Block(cdefs), - Callable([AnyType()] * nargs, - [nodes.ARG_POS] * nargs, [None] * nargs, - Void(), - is_type_obj=False)) - fdef.info = info - - self.make_wrapper_slot_initializer(fdef) - - return fdef - - def make_tvar_representation(self, info: TypeInfo, - is_alt: Any = False) -> List[Node]: - """Return type variable slot member definitions. - - There are of form '__tv*: Any'. Only include new slots defined in the - type. - """ - defs = [] # type: List[Node] - base_slots = num_slots(info.mro[1]) - for n in range(len(info.type_vars)): - # Only include a type variable if it introduces a new slot. - slot = get_tvar_access_path(info, n + 1)[0] - 1 - if slot >= base_slots: - defs.append(VarDef([Var(tvar_slot_name(slot, is_alt), - AnyType())], False, None)) - return defs - - def make_instance_tvar_initializer(self, creat: FuncDef) -> None: - """Add type variable member initialization code to a constructor. - - Modify the constructor body directly. - """ - for n in range(num_slots(creat.info)): - rvalue = self.make_tvar_init_expression(creat.info, n) - init = AssignmentStmt([MemberExpr(self_expr(), - tvar_slot_name(n), - direct=True)], - rvalue) - self.tf.set_type(init.lvalues[0], AnyType()) - self.tf.set_type(init.rvalue, AnyType()) - creat.body.body.insert(n, init) - - def make_wrapper_slot_initializer(self, creat: FuncDef) -> None: - """Add type variable member initializations to a wrapper constructor. - - The function must be a constructor of a generic wrapper class. Modify - the constructor body directly. - """ - for alt in [BOUND_VAR, False]: - for n in range(num_slots(creat.info)): - rvalue = TypeExpr( - RuntimeTypeVar(NameExpr(tvar_slot_name(n, alt)))) - init = AssignmentStmt( - [MemberExpr(self_expr(), - tvar_slot_name(n, alt), direct=True)], - rvalue) - self.tf.set_type(init.lvalues[0], AnyType()) - self.tf.set_type(init.rvalue, AnyType()) - creat.body.body.insert(n, init) - - def make_tvar_init_expression(self, info: TypeInfo, slot: int) -> TypeExpr: - """Return the initializer for the given slot in the given type. - - This is the type expression that initializes the given slot - using the type arguments given to the constructor. - - Examples: - - In 'class C(Generic[T]) ...', the initializer for the slot 0 is - TypeExpr(RuntimeTypeVar(NameExpr('__tv'))). - - In 'class D(C[int]) ...', the initializer for the slot 0 is - TypeExpr(). - """ - # Figure out the superclass which defines the slot; also figure out - # the tvar index that maps to the slot. - origin, tv = find_slot_origin(info, slot) - - # Map self type to the superclass -> extract tvar with target index - # (only contains subclass tvars?? PROBABLY NOT). - selftype = self_type(info) - selftype = map_instance_to_supertype(selftype, origin) - tvar = selftype.args[tv - 1] - - # Map tvar to an expression; refer to local vars instead of member - # vars always. - tvar = translate_runtime_type_vars_locally(tvar) - - # Build the rvalue (initializer) expression - return TypeExpr(tvar) - - def make_type_object_wrapper(self, tdef: ClassDef) -> FuncDef: - """Construct dynamically typed wrapper function for a class. - - It simple calls the type object and returns the result. - """ - - # TODO keyword args, default args and varargs - # TODO overloads - - type_sig = cast(Callable, type_object_type(tdef.info, None)) - type_sig = cast(Callable, erasetype.erase_typevars(type_sig)) - - init = cast(FuncDef, tdef.info.get_method('__init__')) - arg_kinds = type_sig.arg_kinds - - # The wrapper function has a dynamically typed signature. - wrapper_sig = Callable([AnyType()] * len(arg_kinds), - arg_kinds, [None] * len(arg_kinds), - AnyType(), False) - - n = NameExpr(tdef.name) # TODO full name - args = self.func_tf.call_args( - init.args[1:], - type_sig, - wrapper_sig, - True, False) - call = CallExpr(n, args, arg_kinds) - ret = ReturnStmt(call) - - fdef = FuncDef(tdef.name + self.tf.dynamic_suffix(), - init.args[1:], - arg_kinds, [None] * len(arg_kinds), - Block([ret])) - - fdef.type = wrapper_sig - return fdef - - def self_type(self) -> Instance: - return self_type(self.tf.type_context()) - - def make_scope(self) -> 'Scope': - return Scope(self.tf.type_map) - - -class Scope: - """Maintain a temporary local scope during transformation.""" - def __init__(self, type_map: Dict[Node, Type]) -> None: - self.names = {} # type: Dict[str, Var] - self.type_map = type_map - - def add(self, name: str, type: Type) -> Var: - v = Var(name) - v.type = type - self.names[name] = v - return v - - def name_expr(self, name: str) -> NameExpr: - nexpr = NameExpr(name) - nexpr.kind = nodes.LDEF - node = self.names[name] - nexpr.node = node - self.type_map[nexpr] = node.type - return nexpr diff --git a/tests.py b/tests.py index 7c0495e971b4..60db26785331 100644 --- a/tests.py +++ b/tests.py @@ -14,7 +14,6 @@ from mypy.test import testcheck from mypy.test import testtypegen from mypy.test import testoutput -from mypy.test import testdyncheck class AllSuite(Suite): @@ -37,7 +36,6 @@ def __init__(self): self.test_check = testcheck.TypeCheckSuite() self.test_typegen = testtypegen.TypeExportSuite() self.test_output = testoutput.OutputSuite() - self.test_dyncheck = testdyncheck.DyncheckTransformSuite() super().__init__() From d972a1f1eb307495c0d5fcf708fa4419eb49e602 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 14:13:16 -0700 Subject: [PATCH 009/144] Remove obsolete test cases --- mypy/test/data/dyncheck-opgen.test | 125 -- mypy/test/data/dyncheck-trans-basic.test | 1392 ----------------- .../dyncheck-trans-generic-inheritance.test | 865 ---------- mypy/test/data/dyncheck-trans-generics.test | 710 --------- mypy/test/testdyncheck.py | 126 -- 5 files changed, 3218 deletions(-) delete mode 100644 mypy/test/data/dyncheck-opgen.test delete mode 100644 mypy/test/data/dyncheck-trans-basic.test delete mode 100644 mypy/test/data/dyncheck-trans-generic-inheritance.test delete mode 100644 mypy/test/data/dyncheck-trans-generics.test delete mode 100644 mypy/test/testdyncheck.py diff --git a/mypy/test/data/dyncheck-opgen.test b/mypy/test/data/dyncheck-opgen.test deleted file mode 100644 index e519538924cb..000000000000 --- a/mypy/test/data/dyncheck-opgen.test +++ /dev/null @@ -1,125 +0,0 @@ --- Test cases for generation of support code for runtime type opreations such --- as coercions. --- --- NOTE: This is mostly obsolete. These are only interesting as higher-level --- test cases for underlying functionality. - - -[case testEmptyFile] -[out] -def __InitSlotMap() - __SlotMap = std::Map( - ) -end - -def __InitTypeMap() - __TypeMap = std::Map( - ) - __TypeMapB = std::Map( - ) -end - -[case testNonGenericClass] -import typing -class A: pass -[out] -def __InitSlotMap() - __SlotMap = std::Map( - (A, A) : __ATypeToASlots, - (object, A) : __objectTypeToASlots, - ) -end -def __ATypeToASlots(t) - return [] -end -def __objectTypeToASlots(t) - return [] -end -def __InitTypeMap() - __TypeMap = std::Map( - A : __AValueToType, - ) - __TypeMapB = std::Map( - A : __AValueToTypeB, - ) -end -def __AValueToType(v) - return [] -end -def __AValueToTypeB(v) - return [] -end - -[case testSimpleGenericClass] -from typing import typevar, Generic -T = typevar('T') -class A(Generic[T]): pass -[out] -def __InitSlotMap() - __SlotMap = std::Map( - (A, A) : __ATypeToASlots, - (A, A___dyn) : __ATypeToASlots, - (object, A) : __objectTypeToASlots, - (object, A___dyn) : __objectTypeToASlots, - ) -end -def __ATypeToASlots(t) - return [t.args[0]] -end -def __objectTypeToASlots(t) - return [__Dyn] -end -def __InitTypeMap() - __TypeMap = std::Map( - A : __AValueToType, - A___dyn : __AValueToType, - ) - __TypeMapB = std::Map( - A : __AValueToTypeB, - A___dyn : __AValueToTypeB, - ) -end -def __AValueToType(v) - return [v.__tv] -end -def __AValueToTypeB(v) - return [v.__btv] -end - -[case testGenericInheritance] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -class A(Generic[T]): pass -class B(A[A[S]], Generic[T, S]): pass -[out] -... - (B, B) : __BTypeToBSlots, - (B, B___dyn) : __BTypeToBSlots, - (A, B) : __ATypeToBSlots, - (A, B___dyn) : __ATypeToBSlots, - (object, B) : __objectTypeToBSlots, - (object, B___dyn) : __objectTypeToBSlots, -... -def __BTypeToBSlots(t) - return [__Gen(A, [t.args[1]]), t.args[0]] -end -def __ATypeToBSlots(t) - return [t.args[0], __Dyn] -end -def __objectTypeToBSlots(t) - return [__Dyn, __Dyn] -end -... -def __AValueToType(v) - return [v.__tv] -end -def __BValueToType(v) - return [v.__tv2, v.__tv.args[0]] -end -def __AValueToTypeB(v) - return [v.__btv] -end -def __BValueToTypeB(v) - return [v.__btv2, v.__btv.args[0]] -end diff --git a/mypy/test/data/dyncheck-trans-basic.test b/mypy/test/data/dyncheck-trans-basic.test deleted file mode 100644 index 5686206acf3a..000000000000 --- a/mypy/test/data/dyncheck-trans-basic.test +++ /dev/null @@ -1,1392 +0,0 @@ --- Test cases for runtime (dynamic) checking transformation. --- --- Each test case consists of at least two sections. --- The first section contains [case NAME] followed by the input code, while --- the second section contains [out] followed by the output from the --- transformation. - --- Note that the test cases use a pretty-printed output syntax that is not --- valid mypy code. - - --- Empty method transformation, no inheritance --- ------------------------------------------- - - -[case testEmptyClass] -import typing -class A: pass -class B: pass -[out] -class A: - pass -def A*() -> Any: - return A() -class B: - pass -def B*() -> Any: - return B() - -[case testSimpleMethod] -import typing -class A: - def __init__(self) -> None: - pass - - def f(self) -> int: - pass -[out] -class A: - def __init__(self: A) -> None: - pass - def f(self: A) -> int: - pass - def f*(self: Any) -> Any: - return {Any <= int {A self}.f()} -def A*() -> Any: - return A() - -[case testMethodWithArguments] -import typing -class A: - def __init__(self) -> None: - pass - - def f(self, a: 'A') -> None: - pass -[out] -class A: - def __init__(self: A) -> None: - pass - def f(self: A, a: A) -> None: - pass - def f*(self: Any, a: Any) -> Any: - {A self}.f({A a}) -def A*() -> Any: - return A() - -[case testMethodWithDynamicTypesInArguments] -from typing import Any -class A: - def f(self, a: Any, b: Any) -> 'A': - pass -[out] -class A: - def f(self: A, a: Any, b: Any) -> A: - pass - def f*(self: Any, a: Any, b: Any) -> Any: - return {A self}.f(a, b) -def A*() -> Any: - return A() - -[case testMethodWithDynamicReturnType] -from typing import Any -class A: - def f(self, a: 'A') -> Any: - pass -[out] -class A: - def f(self: A, a: A) -> Any: - pass - def f*(self: Any, a: Any) -> Any: - return {A self}.f({A a}) -def A*() -> Any: - return A() - - --- Empty methods with inheritance --- ------------------------------ - - -[case testOverridingStaticWithDynamic] -from typing import Any -class A: - def f(self, a: float) -> int: - pass -class B(A): - def f(self, a: Any) -> Any: - pass -[out] -class A: - def f(self: A, a: float) -> int: - pass - def f*(self: Any, a: Any) -> Any: - return {Any <= int {A self}.f({float a})} -def A*() -> Any: - return A() -class B(A): - def f`B(self: B, a: Any) -> Any: - pass - def f(self: A, a: float) -> int: - return {int {B self}.f`B({Any <= float a})} - def f*(self: Any, a: Any) -> Any: - return {B self}.f`B(a) -def B*() -> Any: - return B() - -[case testOverridingDynamicWithStatic] -from typing import Any -class A: - def f(self, a: Any) -> Any: - pass -class B(A): - def f(self, a: A) -> int: - pass -[out] -class A: - def f(self: A, a: Any) -> Any: - pass - def f*(self: Any, a: Any) -> Any: - return {A self}.f(a) -... -class B(A): - def f`B(self: B, a: A) -> int: - pass - def f(self: A, a: Any) -> Any: - return {Any <= int {B self}.f`B({A a})} - def f*(self: Any, a: Any) -> Any: - return {Any <= int {B self}.f`B({A a})} -... - -[case testNewMethodInSubclass] -import typing -class A: - def f(self, a: 'A') -> 'B': - pass -class B(A): - def g(self, b: 'B') -> A: - pass -[out] -class A: - def f(self: A, a: A) -> B: - pass - def f*(self: Any, a: Any) -> Any: - return {A self}.f({A a}) -... -class B(A): - def g(self: B, b: B) -> A: - pass - def g*(self: Any, b: Any) -> Any: - return {B self}.g({B b}) -... - -[case testOverridingMethodInGrandparent] -from typing import Any -class A: - def f(self, a: int) -> 'B': - pass -class B(A): pass -class C(B): - def f(self, a: Any) -> Any: - pass -[out] -... -class B(A): - pass -... -class C(B): - def f`C(self: C, a: Any) -> Any: - pass - def f(self: A, a: int) -> B: - return {B {C self}.f`C({Any <= int a})} - def f*(self: Any, a: Any) -> Any: - return {C self}.f`C(a) -... - -[case testOverridingMethodTwice] -from typing import Any -class A: - def f(self, a: int) -> float: - pass -class B(A): - def f(self, a: Any) -> float: - pass -class C(B): - def f(self, a: int) -> Any: - pass -[out] -... -class B(A): - def f`B(self: B, a: Any) -> float: - pass - def f(self: A, a: int) -> float: - return {B self}.f`B({Any <= int a}) - def f*(self: Any, a: Any) -> Any: - return {Any <= float {B self}.f`B(a)} -... -class C(B): - def f`C(self: C, a: int) -> Any: - pass - def f`B(self: B, a: Any) -> float: - return {float {C self}.f`C({int a})} - def f*(self: Any, a: Any) -> Any: - return {C self}.f`C({int a}) -... - -[case testOverridingWithSameSig] -import typing -class A: - def f(self, a: 'A') -> 'B': - pass -class B(A): - def f(self, a: A) -> 'B': - return None -[out] -... -class B(A): - def f(self: B, a: A) -> B: - return None -def B*() -> Any: -... - -[case testOverridingDynamicWithDynamic] -from typing import Any -class A: - def f(self, a: Any) -> Any: - pass -class B(A): - def f(self, a: Any) -> Any: - return None -[out] -... -class B(A): - def f(self: B, a: Any) -> Any: - return None -def B*() -> Any: -... - -[case testOverridingFirstWithDynamicAndThenWithSameSig] -from typing import Any -class A: - def f(self, a: int) -> None: - self.f(a) -class B(A): - def f(self, a: Any) -> None: - self.f(a) -class C(B): - def f(self, a: Any) -> None: - self.f(a) -[out] -... -class B(A): - def f`B(self: B, a: Any) -> None: - self.f`B(a) - def f(self: A, a: int) -> None: - {B self}.f`B({Any <= int a}) - def f*(self: Any, a: Any) -> Any: - {B self}.f`B(a) -... -class C(B): - def f`B(self: C, a: Any) -> None: - self.f`B(a) -def C*() -> Any: -... - - --- Method calls --- ------------ - - -[case testCallStaticallyTypedMethod] -import typing -class A: - def f(self, b: 'B') -> 'A': - return b.g() -class B: - def g(self) -> A: - pass -[out] -class A: - def f(self: A, b: B) -> A: - return b.g() -... - -[case testCallDynamicallyTypedMethod] -from typing import Any -class A: - def f(self, b: Any) -> Any: - return b.g() -class B: - def g(self) -> A: - pass -[out] -class A: - def f(self: A, b: Any) -> Any: - return b.g*() -... - -[case testStaticCallWithArguments] -from typing import Any -class A: - def f(self, b: 'B') -> 'A': - return b.g(b, 1) -class B: - def g(self, b: 'B', d: Any) -> A: - pass -[out] -class A: - def f(self: A, b: B) -> A: - return b.g(b, {Any <= int 1}) -... - -[case testDynamicCallWithArguments] -from typing import Any -class A: - def f(self, d: Any, i: int) -> 'A': - return d.g(d, i) -[out] -class A: - def f(self: A, d: Any, i: int) -> A: - return {A d.g*(d, {Any <= int i})} -... - -[case testMethodCallWithInheritance] -import typing -class A: - def f1(self, b: 'B') -> 'A': - return b.f1(b) - def f2(self, b: 'B', a: 'A') -> 'B': - return b.g(a) -class B(A): - def g(self, a: A) -> 'B': - pass -[out] -class A: - def f1(self: A, b: B) -> A: - return b.f1(b) -... - def f2(self: A, b: B, a: A) -> B: - return b.g(a) -... - -[case testMethodCallWithOverride] -from typing import Any -class A: - def f(self, b: 'B') -> 'A': - return b.f(b) - def f2(self, a: 'A', b: 'B') -> 'A': - return a.f(b) -class B(A): - def f(self, b: 'B') -> Any: - pass -[out] -class A: - def f(self: A, b: B) -> A: - return {A b.f`B(b)} -... - def f2(self: A, a: A, b: B) -> A: - return a.f(b) -... - -[case testNestedMethodCalls] -from typing import Any -class A: - def f(self, a: 'A') -> Any: - return a.g(a.g(1)) - def g(self, a: Any) -> int: - pass -[out] -class A: - def f(self: A, a: A) -> Any: - return {Any <= int a.g({Any <= int a.g({Any <= int 1})})} -... - -[case testMethodCallViaSelf] -from typing import Any -class A: - def f(self, a: int) -> Any: - return self.g(a) - def g(self, a: Any) -> int: - pass -[out] -class A: - def f(self: A, a: int) -> Any: - return {Any <= int self.g({Any <= int a})} -... - - --- Statements --- ---------- - - -[case testDeclareVariableUsingUndefinedWithComment] -from typing import Undefined -x = Undefined # type: int -[out] -x: int = Undefined - -[case testDeclareVariableUsingUndefinedWithArgument] -from typing import Undefined -x = Undefined(int) -[out] -x: int = Undefined - -[case testReturnStatement1] -from typing import Any -class A: - def f(self, b: 'B') -> Any: - return b.g() -class B: - def g(self) -> A: - pass -[out] -class A: - def f(self: A, b: B) -> Any: - return b.g() - def f*(self: Any, b: Any) -> Any: - return {A self}.f({B b}) -... - -[case testReturnStatement2] -from typing import Any -class A: - def f(self, b: 'B') -> 'A': - return b.g() -class B: - def g(self) -> Any: - pass -[out] -class A: - def f(self: A, b: B) -> A: - return {A b.g()} - def f*(self: Any, b: Any) -> Any: - return {A self}.f({B b}) -... - -[case testIfStatement] -from typing import Undefined, Any -b = False -d = Undefined # type: Any -if b: - d = 1 -elif d: - d = 2 -else: - d = 3 -[builtins fixtures/ops.py] -[out] -... -if b: - d = {Any <= int 1} -elif d: - d = {Any <= int 2} -else: - d = {Any <= int 3} - -[case testWhileStatement] -from typing import Undefined, Any -b = False -d = Undefined # type: Any -while b: - while d: - d = 1 -[builtins fixtures/ops.py] -[out] -... -while b: - while d: - d = {Any <= int 1} - - --- Cast insertion (or non-insertion) in specific cases --- --------------------------------------------------- - - -[case testOmitSubtypeToSupertypeCast] -import typing -class A: - def f(self, b: 'B') -> 'A': - return b -class B(A): pass -[out] -class A: - def f(self: A, b: B) -> A: - return b -... - - --- __init__ and construction: object --- -------------------------------- - - -[case testConstructInstanceWithPreciseTypes] -import typing -class A: - def __init__(self, b: 'B') -> None: - pass - - def f(self, b: 'B') -> 'A': - return A(b) -class B: pass -[out] -class A: - def __init__(self: A, b: B) -> None: - pass - def f(self: A, b: B) -> A: - return A(b) -... -def A*(b: Any) -> Any: - return A({B b}) -... - -[case testClassWrapperWithTwoArguments] -from typing import Any -class A: - def __init__(self, a: Any, i: int) -> None: pass -[out] -... -def A*(a: Any, i: Any) -> Any: - return A(a, {int i}) - -[case testCreateInstanceWithTrivialReturnTypeCoercion] -from typing import Any -class A: - def f(self) -> Any: - return A() -[out] -class A: - def f(self: A) -> Any: - return A() -... - -[case testCreateInstanceWithArgumentCasts] -from typing import Any -class A: - def f(self, b: int, d: Any) -> 'B': - return B(b, d) -class B: - def __init__(self, d: Any, a: A) -> None: - pass -[out] -class A: - def f(self: A, b: int, d: Any) -> B: - return B({Any <= int b}, {A d}) -... - - --- Self expressions --- ---------------- - - -[case testSelf] -from typing import Any -class A: - def f(self, a: Any) -> None: - self.f(self) -[out] -class A: - def f(self: A, a: Any) -> None: - self.f(self) -... - - --- Data attributes --- --------------- - - -[case testDataAttributeAccessors] -from typing import Undefined -class A: - i = Undefined(int) -[out] -class A: - i: int = Undefined - def $i(self: A) -> int: - return self.i! - def set$i(self: A, i: int) -> None: - self.i! = i - def $i*(self: A) -> Any: - return {Any <= int self.i!} - def set$i*(self: A, i: Any) -> None: - self.i! = {int i} -def A*() -> Any: - return A() - -[case testMemberVariableAccessViaInstanceType] -from typing import Undefined, Any -class A: - a = Undefined(int) - d = Undefined # type: Any - def f(self) -> int: - return self.d - def g(self) -> Any: - return self.a -[out] -class A: -... - def f(self: A) -> int: - return {int self.d} -... - def g(self: A) -> Any: - return {Any <= int self.a} -... - -[case testMemberVariableAssignmentViaInstanceType] -from typing import Undefined, Any -class A: - a = Undefined(int) - d = Undefined # type: Any - def __init__(self, d: Any) -> None: - self.a = d - self.d = d -[out] -... - def __init__(self: A, d: Any) -> None: - self.a = {int d} - self.d = d -... - -[case testMemberVariableAccessViaAny] -from typing import Any -class A: - def f(self, d: Any) -> 'A': - return d.x -[out] -class A: - def f(self: A, d: Any) -> A: - return {A d.x*} -... - -[case testMemberVariableAssignmentViaDynamic] -from typing import Undefined, Any -d = Undefined # type: Any -d.x = 1 -[out] -... -d.x* = {Any <= int 1} - -[case testDataAttributeWithImplicitDefinition] -import typing -class A: - def __init__(self) -> None: - self.x = 0 -[out] -... - def __init__(self: A) -> None: - self.x = 0 -... - def $x(self: A) -> int: - return self.x! - def set$x(self: A, x: int) -> None: - self.x! = x - def $x*(self: A) -> Any: -... - def set$x*(self: A, x: Any) -> None: -... - - --- Casts --- ----- - - -[case testCast] -from typing import cast -class A: - def f(self, a: 'A') -> 'B': - return cast('B', a) -class B(A): pass -[out] -... -class A: - def f(self: A, a: A) -> B: - return cast(B, a) -... - -[case testDynamicCast] -from typing import Any -class A: - def f(self, a: int) -> float: - return Any(a) -[out] -class A: - def f(self: A, a: int) -> float: - return {float cast(Any, {Any <= int a})} -... - -[case testNestedCasts] -from typing import cast, Any -cast(int, Any(1)) -[out] -cast(int, cast(Any, {Any <= int 1})) - - --- Global expressions --- ------------------ - - -[case testGlobalExpression] -from typing import Any -class A: - def f(self, a: Any) -> None: - pass - -A().f(1) -[out] -... -A().f({Any <= int 1}) - - --- Void type --- --------- - - -[case testVoidReturn] -from typing import Any -class A: - def f(self, b: Any) -> None: - pass -class B(A): - def f(self, b: 'B') -> None: - pass -[out] -class A: - def f(self: A, b: Any) -> None: - pass - def f*(self: Any, b: Any) -> Any: - {A self}.f(b) -def A*() -> Any: - return A() -class B(A): - def f`B(self: B, b: B) -> None: - pass - def f(self: A, b: Any) -> None: - {B self}.f`B({B b}) - def f*(self: Any, b: Any) -> Any: - {B self}.f`B({B b}) -def B*() -> Any: - return B() - - --- Function definition and call --- ---------------------------- - - -[case testFunctionDefinitionAndCall] -from typing import Any, Undefined -def f(a: int) -> int: - return Any(1) -d = Undefined # type: Any -d = f(Any(1)) -[out] -def f(a: int) -> int: - return {int cast(Any, {Any <= int 1})} -d: Any = Undefined -d = {Any <= int f({int cast(Any, {Any <= int 1})})} - - --- Assignment and initialization --- ----------------------------- - - -[case testAssignmentToGlobal] -from typing import Undefined, Any -a = Undefined(int) -d = Undefined # type: Any -a = a -a = d -d = a -[out] -a: int = Undefined -d: Any = Undefined -a = a -a = {int d} -d = {Any <= int a} - -[case testGlobalInitialization] -from typing import Undefined, Any -a = Undefined(int) -d = Undefined # type: Any -dd = a # type: Any -aa = a # type: int -[out] -a: int = Undefined -d: Any = Undefined -dd: Any = {Any <= int a} -aa: int = a - -[case testLocalVariableAssignmentAndInit] -from typing import Any, Undefined -def f() -> None: - a = Undefined(int) - d = a # type: Any - aa = a # type: int - a = a - d = a -[out] -def f() -> None: - a: int = Undefined - d: Any = {Any <= int a} - aa: int = a - a = a - d = {Any <= int a} - -[case testMemberInitialization] -from typing import Any, Undefined -class A: - a = Undefined(int) - d = 1 # type: Any - b = 1 # type: int - - def __init__(self, a: int) -> None: - self.a = a -[out] -class A: -... - d: Any = {Any <= int 1} -... - b: int = 1 -... - - --- None --- ---- - - -[case testInitializationToNone] -from typing import Any -a = None # type: object -d = None # type: Any -[out] -a: object = None -d: Any = None - -[case testNoneAsFunctionArgument] -import typing -def f(a: 'A') -> None: - pass -f(None) -class A: pass -[out] -... -f(None) -... - - --- Displaying debugging information --- -------------------------------- - - -[case testSimplePrint] -import typing -# The arguments to debugging functions are not coerced. -__print('hello') -[out] -__print('hello') - -[case testComplexPrint] -from typing import Undefined -def f(a): pass -a = Undefined(int) -__print(a, f(a)) -[out] -... -a: int = Undefined -__print(a, f({Any <= int a})) - - --- Coercions from primitive types --- ------------------------------ - - -[case testCoercionFromPrimitiveToObject] -from typing import Undefined -o = Undefined # type: object -i = Undefined(int) -f = Undefined(float) -a = Undefined # type: A -o = i -o = f -o = a -i = i -class A: pass -[out] -... -a: A = Undefined -o = {object <= int i} -o = {object <= float f} -o = a -i = i -... - - --- Primitive operations --- -------------------- - - -[case testIntLiterals] -from typing import Any -a = 1 # type: int -d = 1 # type: Any -[out] -a: int = 1 -d: Any = {Any <= int 1} - -[case testIntArithmetic-skip] -from typing import Undefined, Any -i = Undefined(int) -d = Undefined # type: Any -i = 1 + 2 -i = (1 - 2) * 2 -i = 1 % 2 -i = 1 // 2 -i = -i -d = 1 + d -d = 1 - d -d = 1 * d -d = 1 % d -d = 1 // d -d = -d + d - d * d -d = d + 1 -d = d - 1 -d = d * 1 -d = d % d -d = d // d -[builtins fixtures/ops.py] -[out] -... -i = 1 + 2 -i = (1 - 2) * 2 -i = 1 % 2 -i = 1 // 2 -i = -i -d = {Any <= int 1 + {int d}} -d = {Any <= int 1 - {int d}} -d = {Any <= int 1 * {int d}} -d = {Any <= int 1 % {int d}} -d = {Any <= int 1 // {int d}} -d = -d + d - d * d -d = d + {Any <= int 1} -d = d - {Any <= int 1} -d = d * {Any <= int 1} -d = d % d -d = d // d - -[case testBinaryOperationOperands-skip] -from typing import Undefined, Any -d = Undefined # type: Any -i = Undefined(int) -i = d.g() + d.f() -i = d.g() * d.f() -i = i + d.f() -i = -d.g() -[builtins fixtures/ops.py] -[out] -... -i = {int d.g*() + d.f*()} -i = {int d.g*() * d.f*()} -i = i + {int d.f*()} -i = {int -d.g*()} - -[case testBools] -from typing import Undefined, Any -b = False -d = Undefined # type: Any -b = True -b = False -d = True -d = False -b = b and b -d = b or b -b = not b -d = not b -b = b and d -d = b or d -b = d and b -d = d or b -b = d and d -d = d or d -b = not d -[builtins fixtures/ops.py] -[out] -b = False -d: Any = Undefined -b = True -b = False -d = {Any <= bool True} -d = {Any <= bool False} -b = b and b -d = {Any <= bool b or b} -b = not b -d = {Any <= bool not b} -b = {bool {Any <= bool b} and d} -d = {Any <= bool b} or d -b = {bool d and {Any <= bool b}} -d = d or {Any <= bool b} -b = {bool d and d} -d = d or d -b = not d - -[case testIntComparisons-skip] -from typing import Undefined, Any -i = Undefined(int) -b = False -d = Undefined # type: Any -b = 1 == 2 -d = 1 != 2 -b = 1 < 2 -b = 1 <= 2 -b = 1 > 2 -b = 1 >= 2 -b = d == d -d = d == d -b = d != 1 -b = 1 < d -d = 1 > d -[builtins fixtures/ops.py] -[out] -... -b = 1 == {object <= int 2} -d = {Any <= bool 1 != {object <= int 2}} -b = 1 < 2 -b = 1 <= 2 -b = 1 > 2 -b = 1 >= 2 -b = {bool d == d} -d = d == d -b = {bool d != {Any <= int 1}} -b = 1 < {int d} -d = {Any <= bool 1 > {int d}} - -[case testNoneComparison] -from typing import Undefined -b = False -c = Undefined # type: C -b = b is None -b = c is not None -b = b == None -b = c == None -class C: pass -[builtins fixtures/ops.py] -[out] -... -b = b is None -b = c is not None -b = b == None -b = c == None -... - -[case testStrLiterals] -import typing -s = 'foo' # type: str -__print(s) -__print('bar') -[out] -s: str = 'foo' -__print(s) -__print('bar') - - --- Type inference --- -------------- - - -[case testSimpleTypeInference] -from typing import Any -y = 1 -a = y # type: Any -[out] -y = 1 -a: Any = {Any <= int y} - - --- Implicit types: Any --- ------------------ - - -[case testImplicitFunctionSig] -import typing -def f(a, b): - pass -def g() -> None: - i = f(1, 2) # type: int -[out] -def f(a: Any, b: Any) -> Any: - pass -def g() -> None: - i: int = {int f({Any <= int 1}, {Any <= int 2})} - -[case testImplicitMethodSig] -import typing -class A: - def f(self, a): - pass - def g(self) -> None: - i = self.f(1) # type: int - j = self.f(1) # type: int -[out] -class A: - def f(self: Any, a: Any) -> Any: - pass - def f*(self: Any, a: Any) -> Any: - return self.f(a) - def g(self: A) -> None: - i: int = {int self.f({Any <= int 1})} - j: int = {int self.f({Any <= int 1})} - def g*(self: Any) -> Any: - {A self}.g() -... - -[case testImplicitMethodSigAndOverride] -import typing -class A: - def f(self, a): - pass - def g(self, a: 'A') -> 'A': - pass -class B(A): - def f(self, a): - pass - def g(self, a): - pass -[out] -... -class B(A): - def f(self: Any, a: Any) -> Any: - pass - def g`B(self: Any, a: Any) -> Any: - pass - def g(self: A, a: A) -> A: - return {A self.g`B(a)} - def g*(self: Any, a: Any) -> Any: - return self.g`B(a) -... - -[case testDynamicallyTypedFunctionBody] -from typing import Undefined -def f(x): - y = 1 - i + y - o + o - x = g(o) -def g(x: int) -> int: - pass -i = Undefined(int) -o = Undefined # type: object -[out] -def f(x: Any) -> Any: - y = {Any <= int 1} - {Any <= int i} + y - {Any o} + o - x = {Any <= int g({int o})} -... - -[case testDynamicallyTypedMethodBody] -from typing import Undefined -class A: - i = Undefined(int) - def f(self, x): - x = self.g(x) - x = self.z() - x = self.i - x.y() - def g(self, x: int) -> int: - pass -[out] -class A: -... - def f(self: Any, x: Any) -> Any: - x = self.g*(x) - x = self.z*() - x = self.i* - x.y*() -... - - --- Operator overloading --- -------------------- - - -[case testBinaryOperatorOverloading-skip] -from typing import Undefined, Any -class A: - def __add__(self, x: 'A') -> int: pass -x = Undefined # type: Any -a = Undefined # type: A -x = a + x -[out] -... -a: A = Undefined -x = {Any <= int a + {A x}} - -[case testBinaryOperatorOverloading2] -from typing import Undefined, Any -class A: - def __mul__(self, x: int) -> 'A': pass -x = Undefined # type: Any -a = Undefined # type: A -x = a * x -[out] -... -a: A = Undefined -x = a * {int x} - -[case testIndexedGet] -from typing import Undefined, Any -class A: - def __getitem__(self, x: 'A') -> int: pass -x = Undefined # type: Any -a = Undefined # type: A -x = a[x] -[out] -... -a: A = Undefined -x = {Any <= int a[{A x}]} - -[case testIndexedGetWithAnyBase] -from typing import Undefined, Any -x = Undefined # type: Any -i = x[1] # type: int -[out] -x: Any = Undefined -i: int = {int x[{Any <= int 1}]} - -[case testIndexedGetInDynamicallyTypedFunction] -import typing -def f(): - 1[2] -[out] -... -def f() -> Any: - {Any <= int 1}[{Any <= int 2}] -... - -[case testIndexedSet] -from typing import Undefined, Any -class A: - def __setitem__(self, x: 'A', y: int) -> int: pass -x = Undefined # type: Any -a = Undefined # type: A -a[x] = 1 -a[a] = x -[out] -... -a: A = Undefined -a[{A x}] = 1 -a[a] = {int x} - -[case testIndexedSetInDynamicallyTypedFunction] -import typing -def f(): - 1[2] = 3 -[out] -... -def f() -> Any: - {Any <= int 1}[{Any <= int 2}] = {Any <= int 3} -... - -[case testIndexedSetWithAnyBase] -from typing import Undefined, Any -x = Undefined # type: Any -x[1] = 2 -[out] -x: Any = Undefined -x[{Any <= int 1}] = {Any <= int 2} - -[case testGenericOperatorMethod] -from typing import typevar, Undefined, Any -t = typevar('t') -class A: - def __add__(self, x: t) -> t: pass -x = Undefined # type: Any -a = Undefined # type: A -x = a + 1 -[out] -... -a: A = Undefined -x = a + {Any <= int 1} - -[case testGenericIndexedGet] -from typing import typevar, Generic, Undefined, Any -t = typevar('t') -class A: - def __getitem__(self, x: t) -> 'B[t]': pass -class B(Generic[t]): pass -x = Undefined # type: Any -a = Undefined # type: A -b = a[x] # type: B[int] -[out] -... -a: A = Undefined -b: B = a[{int x}] - -[case testInOperator-skip] -from typing import Undefined, Any -class A: - def __contains__(self, a: 'A') -> int: pass -x = Undefined # type: Any -a = Undefined # type: A -x = x in a -[out] -... -a: A = Undefined -x = {Any <= int {A x} in a} - -[case testUnaryMinusOperator] -from typing import Undefined, Any -class A: - def __neg__(self) -> bool: pass -x = Undefined # type: Any -a = Undefined # type: A -x = -a -[builtins fixtures/ops.py] -[out] -... -a: A = Undefined -x = {Any <= bool -a} - -[case testUnaryPlusOperator] -from typing import Undefined, Any -class A: - def __pos__(self) -> bool: pass -x = Undefined # type: Any -a = Undefined # type: A -x = +a -[builtins fixtures/ops.py] -[out] -... -a: A = Undefined -x = {Any <= bool +a} - -[case testTransformBinaryOpOperands] -from typing import Any, Undefined -class A: - def __add__(self, x: 'A') -> int: pass -def f(x: Any) -> A: pass -a = Undefined # type: A -a + f(1) -[out] -... -a: A = Undefined -a + f({Any <= int 1}) - -[case testTransformIndexExpr] -from typing import Any, Undefined -class A: - def __getitem__(self, x: 'A') -> int: pass -def f(x: Any) -> A: pass -a = Undefined # type: A -a[f(1)] -[out] -... -a: A = Undefined -a[f({Any <= int 1})] - -[case testTransformUnaryMinusOperand] -from typing import Any -class A: - def __neg__(self) -> int: pass -def f(x: Any) -> A: pass --f(1) -[out] -... --f({Any <= int 1}) - -[case testTransformUnaryPlusOperand] -from typing import Any -class A: - def __pos__(self) -> int: pass -def f(x: Any) -> A: pass -+f(1) -[out] -... -+f({Any <= int 1}) diff --git a/mypy/test/data/dyncheck-trans-generic-inheritance.test b/mypy/test/data/dyncheck-trans-generic-inheritance.test deleted file mode 100644 index 5a1f77a77725..000000000000 --- a/mypy/test/data/dyncheck-trans-generic-inheritance.test +++ /dev/null @@ -1,865 +0,0 @@ --- Test cases for runtime (dynamic) checking transformation and generic --- inheritance. --- --- See dyncheck-trans-basic.test for an overview of the file format. - - --- Non-generic class inherits a generic class --- ------------------------------------------ - - -[case testInheritingGenericClass] -from typing import typevar, Generic -T = typevar('T') -class A(Generic[T]): - def f(self, t: T) -> None: - pass -class C: pass -class B(A[C]): - def g(self, c: 'C') -> None: - self.f(c) -[out] -... -class B(A): - def g(self: B, c: C) -> None: - self.f(c) - def g*(self: Any, c: Any) -> Any: - {B self}.g({C c}) -... - -[case testInheritingGenericClassAndExternalAccess] -from typing import Undefined, typevar, Generic -T = typevar('T') -b = Undefined # type: B -c = Undefined # type: C -b.f(c) -b.g(c) -class A(Generic[T]): - def f(self, t: T) -> None: - pass -class C: pass -class B(A[C]): - def g(self, c: 'C') -> None: - self.f(c) -[out] -b: B = Undefined -c: C = Undefined -b.f(c) -b.g(c) -... - -[case testInheritingGenericClassAndOverriding] -from typing import Undefined, typevar, Generic -T = typevar('T') -a = Undefined # type: A[C] -b = Undefined # type: B -c = Undefined # type: C -a.f(c) -b.f(c) -class A(Generic[T]): - def f(self, t: T) -> None: - pass -class C: pass -class B(A[C]): - def f(self, c: 'C') -> None: - pass -[out] -... -a.f(c) -b.f`B(c) -... -class B(A): - def f`B(self: B, c: C) -> None: - pass - def f(self: A, t: Any) -> None: - {B self}.f`B(t) - def f*(self: Any, c: Any) -> Any: - {B self}.f`B({C c}) -... - -[case testInheritGenericClassAndConstructInstance] -from typing import typevar, Generic -T = typevar('T') -A[float]() -B() -class A(Generic[T]): pass -class B(A[int]): pass -[out] -A() -B() -... - -[case testInheritingGenericClassAndOverriding2] -from typing import typevar, Generic -T = typevar('T') -class A(Generic[T]): - def f(self, a: 'C[T]', b: 'D') -> None: - pass -class D: pass -class E: pass -class B(A[E]): - def f(self, a: 'C[E]', b: 'D') -> None: - pass -class C(Generic[T]): pass -[out] -... -class B(A): - def f`B(self: B, a: C, b: D) -> None: - pass - def f(self: A, a: C, b: D) -> None: - {B self}.f`B(a, b) - def f*(self: Any, a: Any, b: Any) -> Any: - {B self}.f`B({C[E] a}, {D b}) -... - - --- Generic class inherits a generic class; identical type variables --- ---------------------------------------------------------------- - - -[case testGenericInheritanceWithIdenticalTypeVars] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -# Single type variable -class A(Generic[T]): - def f(self, a: T, b: 'A[T]') -> None: - pass -class B(A[S], Generic[S]): - def f(self, a: S, b: A[S]) -> None: - pass -[out] -... -class B(A): - def f`B(self: B, a: Any*, b: A) -> None: - pass - def f(self: A, a: Any, b: A) -> None: - {B[self.__tv] self}.f`B(a, b) - def f*(self: Any, a: Any, b: Any) -> Any: - {B[self.__tv] self}.f`B({self.__tv a}, {A[self.__tv] b}) -... -class B**(A**): -... - def f`B(self: B, a: Any, b: A) -> None: - self.__o.f`B({self.__o.__tv {self.__btv a}}, \ - {A[self.__o.__tv] {A[self.__btv] b}}) - def f*(self: Any, a: Any, b: Any) -> Any: - self.__o.f`B({self.__o.__tv {self.__btv a}}, \ - {A[self.__o.__tv] {A[self.__btv] b}}) -... - -[case testGenericInheritanceWithIdenticalTypeVars2] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -# Multiple type variables -class A(Generic[T, S]): - def f(self, a: T, b: 'A[S, T]') -> None: - pass -class B(A[T, S], Generic[T, S]): - def f(self, a: T, b: A[S, T]) -> None: - pass -[out] -... -class B(A): -... - def f`B(self: B, a: Any*, b: A) -> None: - pass - def f(self: A, a: Any, b: A) -> None: - {B[self.__tv, self.__tv2] self}.f`B(a, b) - def f*(self: Any, a: Any, b: Any) -> Any: - {B[self.__tv, self.__tv2] self}.f`B({self.__tv a}, \ - {A[self.__tv2, self.__tv] b}) -... - -[case testGenericInheritanceWithDifferentSig] -from typing import typevar, Generic, Any -T = typevar('T') -S = typevar('S') -# Override with dynamic -class A(Generic[T]): - def f(self, a: T, b: 'A[T]', i: int) -> T: - pass -class B(A[S], Generic[S]): - def f(self, a: Any, b: Any, i: Any) -> Any: - return None -[out] -... -class B(A): - def f`B(self: B, a: Any, b: Any, i: Any) -> Any: - return None - def f(self: A, a: Any, b: A, i: int) -> Any: - return {self.__tv {B[self.__tv] self}.f`B(a, b, {Any <= int i})} - def f*(self: Any, a: Any, b: Any, i: Any) -> Any: - return {B[self.__tv] self}.f`B(a, b, i) -... - - --- Generic class inherits a generic class; different type variables --- ---------------------------------------------------------------- - - -[case testGenericClassInheritsGenericsClassAndOverrides] -from typing import typevar, Generic -S = typevar('S') -T = typevar('T') -class A(Generic[S, T]): - def f(self, s: S, t: T) -> None: - pass -class C: pass -class B(A[C, S], Generic[S]): - def f(self, s: 'C', t: S) -> None: - pass -[out] -... -class B(A): - def f`B(self: B, s: C, t: Any*) -> None: - pass - def f(self: A, s: Any, t: Any) -> None: - {B[self.__tv2] self}.f`B(s, t) - def f*(self: Any, s: Any, t: Any) -> Any: - {B[self.__tv2] self}.f`B({C s}, {self.__tv2 t}) -... - -[case testNonGenericClassInheritsGenericClass] -from typing import typevar, Generic, Any -T = typevar('T') -class A(Generic[T]): - def f(self, t: T) -> None: - pass -class B(A[int]): - def g(self, d: Any) -> None: - self.f(d) -[out] -... -class B(A): - def g(self: B, d: Any) -> None: - self.f({int d}) - def g*(self: Any, d: Any) -> Any: - {B self}.g(d) -... - -[case testGenericInheritanceAndCoercionsWithArgShuffling] -from typing import typevar, Generic, Any -T = typevar('T') -S = typevar('S') -X = typevar('X') -Y = typevar('Y') -class A(Generic[T, S]): - def f(self, t: T, s: S) -> None: - pass -class B(A[Y, X], Generic[X, Y]): - def g(self, x: X, y: Y) -> None: - pass - def h(self, d: Any) -> None: - self.f(d, d) - self.g(d, d) -[out] -... -class B(A): - def g(self: B, x: Any*, y: Any*) -> None: - pass - def g*(self: Any, x: Any, y: Any) -> Any: - {B[self.__tv2, self.__tv] self}.g({self.__tv2 x}, {self.__tv y}) - def h(self: B, d: Any) -> None: - self.f({self.__tv d}, {self.__tv2 d}) - self.g({self.__tv2 d}, {self.__tv d}) -... -class B**(A**): -... - def g(self: B, x: Any, y: Any) -> None: - self.__o.g({self.__o.__tv2 {self.__btv2 x}}, \ - {self.__o.__tv {self.__btv y}}) - def g*(self: Any, x: Any, y: Any) -> Any: - self.__o.g({self.__o.__tv2 {self.__btv2 x}}, \ - {self.__o.__tv {self.__btv y}}) -... - -[case testGenericInheritanceAndCoercionsWithNestedArg] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -class A(Generic[T]): - def f(self, t: T) -> None: - pass -class B(A[A[S]], Generic[S]): - def f(self, s: A[S]) -> None: - pass -[out] -... -class B(A): - def f`B(self: B, s: A) -> None: - pass - def f(self: A, t: Any) -> None: - {B[self.__tv.args[0]] self}.f`B(t) - def f*(self: Any, s: Any) -> Any: - {B[self.__tv.args[0]] self}.f`B({A[self.__tv.args[0]] s}) -... -class B**(A**): -... - def f`B(self: B, s: A) -> None: - self.__o.f`B({A[self.__o.__tv.args[0]] {A[self.__btv.args[0]] s}}) - def f*(self: Any, s: Any) -> Any: - self.__o.f`B({A[self.__o.__tv.args[0]] {A[self.__btv.args[0]] s}}) - -[case testGenericInheritanceWithNestedArgs2] -from typing import typevar, Generic, Any -T = typevar('T') -S = typevar('S') -# More complex path to subtype type variable -class A(Generic[T]): pass -class C(Generic[S, T]): pass -class X: pass -class B(A[C[X, A[T]]], Generic[T]): - def f(self, a: T, d: Any) -> None: - a = d -[out] -... -class B(A): -... - def f(self: B, a: Any*, d: Any) -> None: - a = {self.__tv.args[1].args[0] d} -... - -[case testGenericInheritanceMultipleLevels] -from typing import typevar, Generic, Any -T = typevar('T') -# Two levels of inheritance -class A(Generic[T]): - def f(self, t: T) -> None: - pass -class B(A[int]): pass -class C(B, Generic[T]): - def g(self, t: T, d: Any) -> None: - self.f(d) - t = d -[out] -... -class C(B): -... - def g(self: C, t: Any*, d: Any) -> None: - self.f({int d}) - t = {self.__tv2 d} -... - - --- Constructors with generic inheritance --- ------------------------------------- - - --- 1. Wrapper that calls superclass __init__ - - -[case testInheritingGenericClassWithDefaultConstructor] -from typing import typevar, Generic -T = typevar('T') -class A(Generic[T]): pass -class B(A[int]): pass -[out] -... -class B(A): - def __init__(self: B) -> None: - self.__tv! = - super().__init__() -... - -[case testInheritingGenericClassWithDefaultConstructor2] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -# Two type arguments -class A(Generic[T, S]): pass -class B(A[int, object]): pass -[out] -... -class B(A): - def __init__(self: B) -> None: - self.__tv! = - self.__tv2! = - super().__init__(, ) -... - -[case testInheritingGenericClassWithNonDefaultConstructor] -from typing import typevar, Generic -T = typevar('T') -class A(Generic[T]): - def __init__(self, o: object) -> None: - pass -class B(A[int]): pass -[out] -... -class B(A): - def __init__(self: B, o: object) -> None: - self.__tv! = - super().__init__(, o) -... - -[case testInheritingGenericClassWithNonDefaultConstructor2] -from typing import typevar, Generic -T = typevar('T') -# Two arguments, one with tvar type -class A(Generic[T]): - def __init__(self, t: T, o: object) -> None: - pass -class B(A[int]): pass -[out] -... -class B(A): - def __init__(self: B, t: int, o: object) -> None: - self.__tv! = - super().__init__(, t, o) -... - -[case testInheritingGenericClassFromNonGenericWithDefaultConstructor] -from typing import typevar, Generic -T = typevar('T') -class A: pass -class B(A, Generic[T]): pass -[out] -... -class B(A): - __tv: Any - def __init__(self: B, __tv: Any) -> None: - self.__tv! = <__tv> - super().__init__() -... - -[case testInheritingGenericClassFromNonGenericWithDefaultConstructor2] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -# Superclass defines a non-trivial constructor; two tvars -class A: - def __init__(self, n: int) -> None: - pass -class B(A, Generic[T, S]): pass -[out] -... -class B(A): - __tv: Any - __tv2: Any - def __init__(self: B, __tv: Any, __tv2: Any, n: int) -> None: - self.__tv! = <__tv> - self.__tv2! = <__tv2> - super().__init__(n) -... - -[case testInheritingGenericClassFromGenericWithDefaultConstructor] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -U = typevar('U') -class A(Generic[T, S]): pass -class C(Generic[T]): pass -class B(A[int, C[U]], Generic[U]): pass -[out] -... -class B(A): - def __init__(self: B, __tv: Any) -> None: - self.__tv! = - self.__tv2! = - super().__init__(, ) -... - -[case testInitWrapperWithDeepHierarchy] -from typing import typevar, Generic -T = typevar('T') -class A(Generic[T]): pass -class B(A[int]): pass -class C(B, Generic[T]): pass -[out] -... -class C(B): - __tv2: Any - def __init__(self: C, __tv: Any) -> None: - self.__tv! = - self.__tv2! = <__tv> - super().__init__() -... - -[case testInitWrapperWithDeepHierarchy2] -from typing import typevar, Generic -T = typevar('T') -class A: pass -class B(A): pass -class C(B, Generic[T]): pass -[out] -... -class C(B): - __tv: Any - def __init__(self: C, __tv: Any) -> None: - self.__tv! = <__tv> - super().__init__() -... - - --- 2. Define new constructor in subclass - - -[case testInitWithGenericInheritance] -from typing import typevar, Generic -T = typevar('T') -U = typevar('U') -S = typevar('S') -class A(Generic[T, U]): pass -class B(A[int, S], Generic[S]): - def __init__(self, n: int) -> None: - pass -[out] -... -class B(A): - def __init__(self: B, __tv: Any, n: int) -> None: - self.__tv! = - self.__tv2! = <__tv> - pass -... - - --- Constructor in a wrapper class; generic inheritance --- --------------------------------------------------- - - -[case testInitInWrapperClassWithGenericInheritance] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -U = typevar('U') -# Generic class inherits another generic class -class A(Generic[T, S]): pass -class C(Generic[T]): pass -class B(A[int, C[U]], Generic[U]): pass -[out] -... -class B**(A**): - def __init__(self: Any, __o: Any, __tv: Any, __tv2: Any, \ - __btv: Any, __btv2: Any) -> None: - self.__tv! = <__tv> - self.__tv2! = <__tv2> - self.__btv! = <__btv> - self.__btv2! = <__btv2> - self.__o! = __o -... - -[case testInitInWrapperClassWithInheritance2] -from typing import typevar, Generic -T = typevar('T') -# Original create has arguments; this does not affect the wrapper -class A(Generic[T]): - def __init__(self, n: int) -> None: - pass -class B(A[T], Generic[T]): pass -[out] -... -class B**(A**): - def __init__(self: Any, __o: Any, __tv: Any, __btv: Any) -> None: -... - -[case testInitInWrapperClassWithInheritance3] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -# Multi-level inheritance hierarchy; note that there is no wrapper class -# for B. -class A(Generic[T]): pass -class B(A[int]): pass -class C(B, Generic[T, S]): pass -[out] -... -class C**(A**): - __tv2: Any - __tv3: Any - __btv2: Any - __btv3: Any - def __init__(self: Any, __o: Any, __tv: Any, __tv2: Any, __tv3: Any, \ - __btv: Any, __btv2: Any, __btv3: Any) -> None: - self.__tv! = <__tv> - self.__tv2! = <__tv2> - self.__tv3! = <__tv3> - self.__btv! = <__btv> - self.__btv2! = <__btv2> - self.__btv3! = <__btv3> - self.__o! = __o - - --- Type variable definitions in subclasses; generic inheritance --- ------------------------------------------------------------ - - -[case testTvarDefinitionsWithGenericInheritance] -from typing import typevar, Generic -T = typevar('T') -# Generic class inherits generic class; no new type variables -class A(Generic[T]): pass -class B(A[T], Generic[T]): pass -[out] -... -class B(A): - def __init__(self: B, __tv: Any) -> None: -... -class B**(A**): - def __init__(self: Any, __o: Any, __tv: Any, __btv: Any) -> None: -... - -[case testTvarDefinitionsWithGenericInheritance] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -# Generic class inherits generic class; introduce new type variable -class A(Generic[T]): pass -class B(A[T], Generic[S, T]): pass -[out] -... -class B(A): - __tv2: Any - def __init__(self: B, __tv: Any, __tv2: Any) -> None: -... -class B**(A**): - __tv2: Any - __btv2: Any - def __init__(self: Any, __o: Any, __tv: Any, __tv2: Any, \ - __btv: Any, __btv2: Any) -> None: -... - - --- Calling superclass create explicitly --- ------------------------------------ - - -[case testGenericInheritanceAndCallToSuperclassInit] -from typing import typevar, Generic -T = typevar('T') -# Non-generic class inherits a generic class -class A(Generic[T]): - def __init__(self, n: 'C') -> None: - pass - -class C: pass - -class B(A[int]): - def __init__(self) -> None: - super().__init__(C()) -[out] -... -class B(A): - def __init__(self: B) -> None: - self.__tv! = - super().__init__(, C()) -... - -[case testGenericInheritanceAndCallToSuperclassInit2-skip] -from typing import typevar, Generic -T = typevar('T') -# Non-generic class inherits a generic class -class A(Generic[T]): pass - -class B(A[int]): - def __init__(self) -> None: - super().__init__() -[out] -... -class B(A): - def __init__(self: B) -> None: - self.__tv! = - super().__init__() -... - -[case testGenericInheritanceAndCallToSuperclassInit] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -# Generic class inherits a generic class -class A(Generic[T, S]): - def __init__(self, t: T) -> None: - pass - -class B(A[S, T], Generic[T, S]): - def __init__(self, t: S) -> None: - super().__init__(t) -[out] -... -class B(A): - def __init__(self: B, __tv: Any, __tv2: Any, t: Any*) -> None: - self.__tv! = <__tv2> - self.__tv2! = <__tv> - super().__init__(, , t) -... - - --- Mixed generic inheritance --- ------------------------- - - -[case testMixedGenericInheritance-skip] -from typing import typevar, Generic, Any -T = typevar('T') -# Non-generic class extends generic -class A(Generic[T]): - def f(self, t: T) -> T: - pass -class B(A[int]): - def f(self, t: Any) -> Any: - pass -[out] -... -class B(A): - def f`B(self: B, t: Any) -> Any: - pass - def f(self: A, t: Any) -> Any: --- TODO not sure about the coercions... - return {Any <= int {int {B self}.f`B({Any <= int t})}} - def f*(self: Any, t: Any) -> Any: - return {B self}.f`B(t) -... - -[case testMixedGenericInheritance2] -from typing import typevar, Generic, Any -T = typevar('T') -S = typevar('S') -# Generic class extends generic -class A(Generic[T]): - def f(self, t: T) -> T: - pass -class B(A[S], Generic[T, S]): - def f(self, t: Any) -> Any: - pass -[out] -... -class B(A): - __tv2: Any - def f`B(self: B, t: Any) -> Any: - pass - def f(self: A, t: Any) -> Any: - return {self.__tv {B[self.__tv2, self.__tv] self}.f`B(t)} - def f*(self: Any, t: Any) -> Any: - return {B[self.__tv2, self.__tv] self}.f`B(t) -... -class B**(A**): -... - def f`B(self: B, t: Any) -> Any: - return self.__o.f`B(t) - def f*(self: Any, t: Any) -> Any: - return self.__o.f`B(t) - - --- Generic inheritance with multiple ways of accessing subclass tvars --- ------------------------------------------------------------------ - - --- Example: class C[t](D[t, t]): ... - - -[case testAmbiguousTvarMappingAndGenericInheritance] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -class D(Generic[T, S]): - def f(self) -> S: - pass -class C(D[T, T], Generic[T]): - def f(self) -> T: - pass -[out] -... -class C(D): -... - def __init__(self: C, __tv: Any) -> None: - self.__tv! = <__tv> - self.__tv2! = <__tv> - super().__init__(, ) -... -class C**(D**): - def __init__(self: Any, __o: Any, __tv: Any, __tv2: Any, \ - __btv: Any, __btv2: Any) -> None: - self.__tv! = <__tv> - self.__tv2! = <__tv2> - self.__btv! = <__btv> - self.__btv2! = <__btv2> - self.__o! = __o - def f`C(self: C) -> Any: - return {self.__tv {self.__btv self.__o.f`C()}} - def f*(self: Any) -> Any: - return {self.__btv self.__o.f`C()} - - --- Generic inheritance + member variables --- -------------------------------------- - - -[case testMemberVarsAndGenericClass] -from typing import typevar, Generic, Undefined, Any -T = typevar('T') -class A(Generic[T]): - x = Undefined # type: T - def f(self, a: Any) -> None: - self.x = a - a = self.x -[out] -class A: - __tv: Any - x: Any* = Undefined - def f(self: A, a: Any) -> None: - self.x = {self.__tv a} - a = self.x -... - def $x(self: A) -> Any*: - return self.x! - def set$x(self: A, x: Any*) -> None: - self.x! = x - def $x*(self: A) -> Any: - return self.x! - def set$x*(self: A, x: Any) -> None: - self.x! = {self.__tv x} -... -class A**: - __o: Any - __tv: Any - __btv: Any - def __init__(self: Any, __o: Any, __tv: Any, __btv: Any) -> None: - self.__tv! = <__tv> - self.__btv! = <__btv> - self.__o! = __o - def $x(self: A) -> Any: - return {self.__tv {self.__btv self.__o.$x()}} - def $x*(self: Any) -> Any: - return {self.__btv self.__o.$x()} - def set$x(self: A, x: Any) -> None: - self.__o.set$x({self.__o.__tv {self.__btv x}}) - def set$x*(self: Any, x: Any) -> Any: - self.__o.set$x({self.__o.__tv {self.__btv x}}) - def f(self: A, a: Any) -> None: - self.__o.f(a) - def f*(self: Any, a: Any) -> Any: - self.__o.f(a) -... - -[case testMemberVarsAndGenericInheritance] -from typing import typevar, Generic, Undefined, Any -T = typevar('T') -class A(Generic[T]): - x = Undefined # type: T - def f(self, a: Any) -> None: - self.x = a - a = self.x -class C(Generic[T]): pass -class B(A[C[T]], Generic[T]): - y = Undefined # type: T - def g(self, a: Any) -> None: - self.y = a - a = self.y -[out] -... -class B(A): - y: Any* = Undefined - def g(self: B, a: Any) -> None: - self.y = {self.__tv.args[0] a} - a = self.y -... - def $y(self: B) -> Any*: - return self.y! - def set$y(self: B, y: Any*) -> None: - self.y! = y - def $y*(self: B) -> Any: - return self.y! - def set$y*(self: B, y: Any) -> None: - self.y! = {self.__tv.args[0] y} -... diff --git a/mypy/test/data/dyncheck-trans-generics.test b/mypy/test/data/dyncheck-trans-generics.test deleted file mode 100644 index 1d0e37836aca..000000000000 --- a/mypy/test/data/dyncheck-trans-generics.test +++ /dev/null @@ -1,710 +0,0 @@ --- Test cases for runtime (dynamic) checking transformation. --- --- Each test case consists of at least two sections. --- The first section contains [case NAME] followed by the input code, while --- the second section contains [out] followed by the output from the --- transformation. - --- Note that the test cases use a pretty-printed output syntax that is not --- valid Alore code. - - --- Generics basics --- --------------- - - -[case testSimplestGenericClass] -from typing import typevar, Generic -T = typevar('T') -class C(Generic[T]): pass -[out] -class C: - __tv: Any - def __init__(self: C, __tv: Any) -> None: - self.__tv! = <__tv> -def C*() -> Any: - return C() -class C**: - __o: Any - __tv: Any - __btv: Any - def __init__(self: Any, __o: Any, __tv: Any, __btv: Any) -> None: - self.__tv! = <__tv> - self.__btv! = <__btv> - self.__o! = __o - -[case testSimpleGenericClass] -from typing import typevar, Generic, Undefined -T = typevar('T') -class C(Generic[T]): - o = Undefined # type: T - def __init__(self, o: T) -> None: - self.o = o - def get(self) -> T: - return self.o - def num(self) -> int: - return 1 -[out] -class C: - __tv: Any - o: Any* = Undefined - def __init__(self: C, __tv: Any, o: Any*) -> None: - self.__tv! = <__tv> - self.o = o - def get(self: C) -> Any*: - return self.o - def get*(self: Any) -> Any: - return {C[self.__tv] self}.get() - def num(self: C) -> int: - return 1 - def num*(self: Any) -> Any: - return {Any <= int {C[self.__tv] self}.num()} - def $o(self: C) -> Any*: - return self.o! - def set$o(self: C, o: Any*) -> None: - self.o! = o - def $o*(self: C) -> Any: - return self.o! - def set$o*(self: C, o: Any) -> None: - self.o! = {self.__tv o} -def C*(o: Any) -> Any: - return C(, o) -class C**: - __o: Any - __tv: Any - __btv: Any - def __init__(self: Any, __o: Any, __tv: Any, __btv: Any) -> None: - self.__tv! = <__tv> - self.__btv! = <__btv> - self.__o! = __o - def $o(self: C) -> Any: - return {self.__tv {self.__btv self.__o.$o()}} - def $o*(self: Any) -> Any: - return {self.__btv self.__o.$o()} - def set$o(self: C, o: Any) -> None: - self.__o.set$o({self.__o.__tv {self.__btv o}}) - def set$o*(self: Any, o: Any) -> Any: - self.__o.set$o({self.__o.__tv {self.__btv o}}) - def get(self: C) -> Any: - return {self.__tv {self.__btv self.__o.get()}} - def get*(self: Any) -> Any: - return {self.__btv self.__o.get()} - def num(self: C) -> int: - return self.__o.num() - def num*(self: Any) -> Any: - return {Any <= int self.__o.num()} - -[case testGenericMethodWithArguments] -from typing import typevar, Generic -T = typevar('T') -class C(Generic[T]): - def f(self, a: 'A', t: T) -> None: - pass -class A: pass -[out] -class C: - __tv: Any - def f(self: C, a: A, t: Any*) -> None: - pass - def f*(self: Any, a: Any, t: Any) -> Any: - {C[self.__tv] self}.f({A a}, {self.__tv t}) -... -class C**: -... - def f(self: C, a: A, t: Any) -> None: - self.__o.f(a, {self.__o.__tv {self.__btv t}}) - def f*(self: Any, a: Any, t: Any) -> Any: - self.__o.f({A a}, {self.__o.__tv {self.__btv t}}) -... - -[case testAccessingGenericClassMethodInTypedContext] -from typing import typevar, Generic, Undefined, Any -T = typevar('T') -class A: pass -class B: pass -class C(Generic[T]): - def __init__(self) -> None: - pass - def f(self, a: A) -> None: - pass -c = Undefined # type: C[B] -c.f(Any(A())) -[out] -... -c: C = Undefined -c.f({A cast(Any, A())}) - -[case testAccessingMethodInGenericClassAndTypedContextViaSelf] -from typing import typevar, Generic -T = typevar('T') -class A: pass -class C(Generic[T]): - def f(self, a: A) -> None: - self.f(a) -[out] -... - def f(self: C, a: A) -> None: - self.f(a) -... - -[case testConstructingGenericInstance] -from typing import typevar, Generic, Any -T = typevar('T') -class C(Generic[T]): - def __init__(self) -> None: - pass -class A: pass -a = C[A]() # type: C[A] -d = C[Any]() # type: C[Any] -[out] -... -class A: -... -a: C = C() -d: C = C() - -[case testConstructingGenericInstanceWithGenericArg] -from typing import typevar, Generic -T = typevar('T') -class C(Generic[T]): - def __init__(self) -> None: - pass -class A: pass -a = C[C[A]]() # type: C[C[A]] -[out] -... -class A: -... -a: C = C() - -[case testCastFromAnyToGenericType] -from typing import typevar, Generic, Undefined, Any -T = typevar('T') -class A(Generic[T]): - a = Undefined # type: T - def __init__(self, a: T) -> None: - self.a = a - def f(self) -> T: - return self.a -class B: pass -class C: pass -d = A[Any](B()) # type: Any -b = Undefined # type: B -b = d.f() -aa = d # type: A[C] -aa.f() # Failure at runtime -[out] -... -d: Any = A(, B()) -b: B = Undefined -b = {B d.f*()} -aa: A = {A[C] d} -aa.f() - -[case testCastWithDynamicAndTypedArguments-skip] -from typing import typevar, Generic, Undefined, Any -T = typevar('T') -class A(Generic[T]): pass -class B: pass -a = Undefined # type: A[B] -d = Undefined # type: A[Any] -a = d -d = a -[out] -... -d: A = Undefined -a = {A[B] d} -d = {A[Any] a} - -[case testNestedGenerics-skip] -from typing import typevar, Generic, Undefined, Any -T = typevar('T') -class A(Generic[T]): - def f(self) -> None: - pass -class B: pass -a = Undefined # type: A[A[B]] -d = Undefined # type: A[Any] -d = a -a = d -a.f() -d.f() -[out] -... -d: A = Undefined -d = {A[Any] a} -a = {A[A[B]] d} -a.f() -d.f() - -[case testGenericWrapperWithNonGenericTypesInSignatures] -from typing import typevar, Generic -T = typevar('T') -class A(Generic[T]): - def f(self, b: 'B', ab: 'A[B]') -> 'A[B]': - pass - def g(self) -> 'B': - pass -class B: pass -[out] -... -class A**: -... - def f*(self: Any, b: Any, ab: Any) -> Any: - return self.__o.f({B b}, {A[B] ab}) -... - def g*(self: Any) -> Any: - return self.__o.g() -... - - --- Multiple type arguments --- ----------------------- - - -[case testSimplestClassWithTwoTypeArguments] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -class A(Generic[T, S]): pass -[out] -class A: - __tv: Any - __tv2: Any - def __init__(self: A, __tv: Any, __tv2: Any) -> None: - self.__tv! = <__tv> - self.__tv2! = <__tv2> -def A*() -> Any: - return A(, ) -class A**: - __o: Any - __tv: Any - __tv2: Any - __btv: Any - __btv2: Any - def __init__(self: Any, __o: Any, __tv: Any, __tv2: Any, __btv: Any, __btv2: Any) -> None: - self.__tv! = <__tv> - self.__tv2! = <__tv2> - self.__btv! = <__btv> - self.__btv2! = <__btv2> - self.__o! = __o - -[case testConstructingInstanceWithTwoTypeArguments] -from typing import typevar, Generic, Undefined -T = typevar('T') -S = typevar('S') -class A(Generic[T, S]): pass -a = Undefined # type: A[int, float] -a = A[int, float]() -[out] -... -a: A = Undefined -a = A(, ) - -[case testCallingEmptyMethodWithTwoTypeArguments] -from typing import typevar, Generic, Undefined -T = typevar('T') -S = typevar('S') -class A(Generic[T, S]): - def f(self) -> None: - pass -class B: pass -class C: pass - -a = Undefined # type: A[B, C] -a.f() -[out] -... - def f*(self: Any) -> Any: - {A[self.__tv, self.__tv2] self}.f() -... -class A**: -... - def f(self: A) -> None: - self.__o.f() - def f*(self: Any) -> Any: - self.__o.f() -... -a: A = Undefined -a.f() - -[case testAccessingMultipleTypeArguments] -from typing import typevar, Generic, Undefined -T = typevar('T') -S = typevar('S') -class A(Generic[T, S]): - x = Undefined # type: T - def f(self, y: S) -> T: - return self.x -[out] -... -class A**: -... - def f(self: A, y: Any) -> Any: - return {self.__tv {self.__btv self.__o.f(\ - {self.__o.__tv2 {self.__btv2 y}})}} - def f*(self: Any, y: Any) -> Any: - return {self.__btv self.__o.f({self.__o.__tv2 {self.__btv2 y}})} -... - -[case testAccessingGenericMethodInTypedContextViaSelfAndMultipleArgs] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -class A: pass -class B: pass -class C(Generic[T, S]): - def f(self, a: A) -> None: - self.f(a) -[out] -... - def f(self: C, a: A) -> None: - self.f(a) -... - - --- Coercions involving type variables --- ---------------------------------- - - --- NOTE: Some of the wrapper test cases above also coerce to/from type --- variables. - - -[case testSimpleTypeVarCoercionWithMultipleTypeVariables] -from typing import typevar, Generic, Undefined, Any -T = typevar('T') -S = typevar('S') -class A(Generic[T, S]): - t = Undefined # type: T - s = Undefined # type: S - def f(self) -> None: - d = Undefined # type: Any - d = self.t - self.t = d - self.s = d -[out] -... - def f(self: A) -> None: - d: Any = Undefined - d = self.t - self.t = {self.__tv d} - self.s = {self.__tv2 d} -... - -[case testTypeVarCoercionsWithGenericTypes] -from typing import typevar, Generic, Undefined, Any -T = typevar('T') -class A(Generic[T]): - a = Undefined # type: A[T] - def f(self) -> None: - d = Undefined # type: Any - d = self.a - self.a = d -[out] -... - def f(self: A) -> None: - d: Any = Undefined - d = self.a - self.a = {A[self.__tv] d} -... - -[case testConstructGenericInstanceBasedOnTypeVar] -from typing import typevar, Generic, Undefined, Any -T = typevar('T') -class A(Generic[T]): - def f(self) -> None: - a = A[T]() - d = Undefined # type: Any - a = d -[out] -... - def f(self: A) -> None: - a = A() - d: Any = Undefined - a = {A[self.__tv] d} -... - - --- Type erasure --- ------------ - - -[case testTypeErasureOfFunctionSignatures] -from typing import typevar, Generic -T = typevar('T') -class A(Generic[T]): pass -class B: - def m(self, a: 'A[B]') -> None: - pass -def f() -> A[B]: - pass -[out] -... -class B: - def m(self: B, a: A) -> None: - pass - def m*(self: Any, a: Any) -> Any: - {B self}.m({A[B] a}) -def B*() -> Any: - return B() -def f() -> A: - pass - - --- Generic functions --- ----------------- - - -[case testSimpleGenericFunction] -from typing import typevar -T = typevar('T') -def f(x: T) -> T: - return x -[out] -def f(__ftv: Any, x: Any*) -> Any*: - return x - -[case testGenericFunctionWithTwoTvars] -from typing import typevar -T = typevar('T') -S = typevar('S') -def f(x: T, y: S) -> T: - return x -[out] -def f(__ftv: Any, __ftv2: Any, x: Any*, y: Any*) -> Any*: - return x - -[case testCallingSimpleGenericFunction] -from typing import typevar, Any -T = typevar('T') -def f(x: T) -> T: - return x -def g() -> None: - c = C() # type: C - c = f['C'](c) - d = c # type: Any - d = f['C'](d) - c = f[Any](c) - -class C: pass -class D: pass -[out] -... -def g() -> None: - c: C = C() - c = f(, c) - d: Any = c - d = f(, {C d}) - c = {C f(, c)} -... - -[case testTypeVarReferenceInGenericFunction] -from typing import typevar, Any, Generic -T = typevar('T') -def f(x: Any) -> 'C[T]': - a = C[T]() - f[T](x) - return x - -class C(Generic[T]): pass -[out] -def f(__ftv: Any, x: Any) -> C: - a = C(<__ftv>) - f(<__ftv>, x) - return {C[__ftv] x} -... - - --- Generic methods --- --------------- - - -[case testSimpleGenericMethod] -from typing import typevar -T = typevar('T') -class A: - def f(self, x: T) -> T: - return x -[out] -class A: - def f(self: A, __ftv: Any, x: Any*) -> Any*: - return x - def f*(self: Any, x: Any) -> Any: - return {A self}.f(, x) -... - -[case testGenericMethodInGenericClass] -from typing import typevar, Generic -T = typevar('T') -S = typevar('S') -class A(Generic[T]): - def f(self, x: S) -> S: - return x -[out] -class A: - __tv: Any - def f(self: A, __ftv: Any, x: Any*) -> Any*: - return x - def f*(self: Any, x: Any) -> Any: - return {A[self.__tv] self}.f(, x) - def __init__(self: A, __tv: Any) -> None: - self.__tv! = <__tv> -def A*() -> Any: - return A() -class A**: - __o: Any - __tv: Any - __btv: Any - def __init__(self: Any, __o: Any, __tv: Any, __btv: Any) -> None: - self.__tv! = <__tv> - self.__btv! = <__btv> - self.__o! = __o - def f(__ftv: Any, self: A, x: Any) -> Any: - return self.__o.f(<__ftv>, x) - def f*(self: Any, x: Any) -> Any: - return self.__o.f(, x) - -[case testCalllingGenericMethod] -from typing import typevar -T = typevar('T') -class C: pass -class A: - def f(self, x: T) -> T: - pass - def g(self) -> C: - return self.f(C()) -c = A().f(C()) -[out] -... - def g(self: A) -> C: - return self.f(, C()) -... -c = A().f(, C()) -... - -[case testCalllingGenericMethod2] -from typing import typevar, Generic -S = typevar('S') -T = typevar('T') -class C: pass -class D: pass -class A(Generic[S]): - def f(self, x: T) -> T: - pass - def g(self, x: S) -> S: - return self.f(x) -c = A[D]().f(C()) -[out] -... - def g(self: A, x: Any*) -> Any*: - return self.f(, x) -... -c = A().f(, C()) -... - -[case testAccessingGenericMethodTvars] -from typing import typevar, Generic -S = typevar('S') -T = typevar('T') -U = typevar('U') -class A(Generic[S]): - def f(self) -> 'C[T, U, S]': - return C[T, U, S]() -class C(Generic[T, S, U]): pass -[out] -... - def f(self: A, __ftv: Any, __ftv2: Any) -> C: - return C(<__ftv>, <__ftv2>, ) - def f*(self: Any) -> Any: - return {A[self.__tv] self}.f(, ) -... -class A**: -... - def f(__ftv: Any, __ftv2: Any, self: A) -> C: - return {C[__ftv, __ftv2, self.__tv] {C[__ftv, __ftv2, self.__btv] \ - self.__o.f(<__ftv>, <__ftv2>)}} - def f*(self: Any) -> Any: - return {C[Any, Any, self.__btv] self.__o.f(, )} -... - - --- Dynamically typed functions --- --------------------------- - - -[case testCallGenericTypeInDynamicallyTypedFunction] -from typing import typevar, Generic -T = typevar('T') -def f(): - A() -class A(Generic[T]): - pass -[out] -def f() -> Any: - A() -... - - --- Misc test cases --- --------------- - - -[case testPairExample] -from typing import typevar, Generic, Undefined -H = typevar('H') -T = typevar('T') -# This test case is adapted from an early example program. -class P(Generic[H, T]): - head = Undefined # type: H - tail = Undefined # type: T - name = Undefined # type: Name - def __init__(self, head: H, tail: T, name: 'Name') -> None: - self.head = head - self.tail = tail - self.name = name - def set_head(self, new_head: H, new_name: 'Name') -> 'P[H, T]': - return P[H, T](new_head, self.tail, new_name) -class Name: pass -[out] -class P: -... - def set_head(self: P, new_head: Any*, new_name: Name) -> P: - return P(, , new_head, self.tail, new_name) - def set_head*(self: Any, new_head: Any, new_name: Any) -> Any: - return {P[self.__tv, self.__tv2] self}.set_head(\ - {self.__tv new_head}, {Name new_name}) -... -def P*(head: Any, tail: Any, name: Any) -> Any: - return P(, , head, tail, {Name name}) -class P**: -... - def set_head(self: P, new_head: Any, new_name: Name) -> P: - return {P[self.__tv, self.__tv2] \ - {P[self.__btv, self.__btv2] self.__o.set_head(\ - {self.__o.__tv {self.__btv new_head}}, new_name)}} - def set_head*(self: Any, new_head: Any, new_name: Any) -> Any: - return {P[self.__btv, self.__btv2] self.__o.set_head(\ - {self.__o.__tv {self.__btv new_head}}, {Name new_name})} -class Name: - pass -... - -[case testImplicitMethodSigInGenericClass] -from typing import typevar, Generic -T = typevar('T') -class A(Generic[T]): - def f(self, a): - pass - def g(self) -> None: - i = self.f(1) # type: int - j = self.f(1) # type: int -[out] -... -class A**: -... - def f(self: Any, a: Any) -> Any: - return self.__o.f(a) - def f*(self: Any, a: Any) -> Any: - return self.__o.f(a) -... diff --git a/mypy/test/testdyncheck.py b/mypy/test/testdyncheck.py deleted file mode 100644 index 08ed292d6197..000000000000 --- a/mypy/test/testdyncheck.py +++ /dev/null @@ -1,126 +0,0 @@ -"""Test case runner for runtime type checking transform.""" - -import os -import os.path -import shutil - -import typing - -from mypy import build -from mypy.myunit import Suite, run_test -from mypy.test.helpers import assert_string_arrays_equal_wildcards -from mypy.test.data import parse_test_cases -from mypy.test.config import test_data_prefix, test_temp_dir -from mypy.test.testoutput import remove_prefix -from mypy.transform import DyncheckTransformVisitor -from mypy.pprinter import PrettyPrintVisitor -from mypy.errors import CompileError - - -# The builtins stub used during transformation in test cases. -TRANSFORM_BUILTINS = 'fixtures/transform.py' - - -class DyncheckTransformSuite(Suite): - test_case_files = ['dyncheck-trans-basic.test', - 'dyncheck-trans-generics.test', - 'dyncheck-trans-generic-inheritance.test'] - - def cases(self): - c = [] - for f in self.test_case_files: - c += parse_test_cases( - os.path.join(test_data_prefix, f), - builtins_wrapper(test_transform, - os.path.join(test_data_prefix, - TRANSFORM_BUILTINS)), - test_temp_dir, True) - return c - - -def test_transform(testcase): - """Perform a runtime checking transformation test case.""" - expected = remove_comment_lines(testcase.output) - try: - # Construct input as a single single. - src = '\n'.join(testcase.input) - # Parse and type check the input program. Perform transform manually - # so that we can skip some files. - result = build.build(program_path='main', - target=build.TRANSFORM, - program_text=src, - flags=[build.TEST_BUILTINS], - alt_lib_path=test_temp_dir) - a = [] - first = True - # Transform each file separately. - for fnam in sorted(result.files.keys()): - f = result.files[fnam] - # Skip the builtins module and files with '_skip.' in the path. - if (not f.path.endswith('/builtins.py') and - not f.path.endswith('/typing.py') and - not f.path.endswith('/abc.py') and '_skip.' not in f.path): - if not first: - # Display path for files other than the first. - a.append('{}:'.format(remove_prefix(f.path, - test_temp_dir))) - - # Pretty print the transformed tree. - v2 = PrettyPrintVisitor() - f.accept(v2) - s = v2.output() - if s != '': - a += s.split('\n') - first = False - except CompileError as e: - a = e.messages - assert_string_arrays_equal_wildcards( - expected, a, - 'Invalid source code output ({}, line {})'.format(testcase.file, - testcase.line)) - - -def remove_comment_lines(a): - """Return a copy of array with comments removed. - - Lines starting with '--' (but not with '---') are removed. - """ - r = [] - for s in a: - if s.strip().startswith('--') and not s.strip().startswith('---'): - pass - else: - r.append(s) - return r - - -def builtins_wrapper(func, path): - """Decorate a function that implements a data-driven test case to copy an - alternative builtins module implementation in place before performing the - test case. Clean up after executing the test case. - """ - return lambda testcase: perform_test(func, path, testcase) - - -def perform_test(func, path, testcase): - for path, _ in testcase.files: - if os.path.basename(path) == 'builtins.py': - default_builtins = False - break - else: - # Use default builtins. - builtins = os.path.join(test_temp_dir, 'builtins.py') - shutil.copyfile(path, builtins) - default_builtins = True - - # Actually peform the test case. - func(testcase) - - if default_builtins: - # Clean up. - os.remove(builtins) - - -if __name__ == '__main__': - import sys - run_test(DyncheckTransformSuite(), sys.argv[1:]) From 620b675a2f0ecaa2de146cedd445c1e241d45ddd Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 14:15:02 -0700 Subject: [PATCH 010/144] Remove obsolete code --- mypy/compilemapd.py | 139 ----------------------------------------- mypy/compileslotmap.py | 68 -------------------- mypy/opgen.py | 128 ------------------------------------- mypy/rttypevars.py | 60 ------------------ 4 files changed, 395 deletions(-) delete mode 100644 mypy/compilemapd.py delete mode 100644 mypy/compileslotmap.py delete mode 100644 mypy/opgen.py delete mode 100644 mypy/rttypevars.py diff --git a/mypy/compilemapd.py b/mypy/compilemapd.py deleted file mode 100644 index 2172e366f312..000000000000 --- a/mypy/compilemapd.py +++ /dev/null @@ -1,139 +0,0 @@ -from typing import Undefined, List, Tuple, cast - -from nodes import TypeInfo -from types import Instance, Type, TypeVar, AnyType - - -class MapPremise: pass - - -class MapExpr: pass - - -class AssertClass(MapPremise): - i = Undefined(MapExpr) - c = Undefined(TypeInfo) - - def __init__(self, i: MapExpr, c: TypeInfo) -> None: - self.i = i - self.c = c - - def __str__(self): - return str(self.i) + ' = ' + self.c.name + '[...]' - - -class AssertDyn(MapPremise): - i = Undefined(MapExpr) - - def __init__(self, i: MapExpr) -> None: - self.i = i - - def __str__(self): - return str(self.i) + ' = Any' - - -class AssertEq(MapPremise): - i1 = Undefined(MapExpr) - i2 = Undefined(MapExpr) - - def __init__(self, i1: MapExpr, i2: MapExpr) -> None: - self.i1 = i1 - self.i2 = i2 - - def __str__(self): - return str(self.i1) + ' = ' + str(self.i2) - - -class TypeVarRef(MapExpr): - n = 0 - - def __init__(self, n: int) -> None: - self.n = n - - def __str__(self): - return str(self.n) - - -class TypeArgRef(MapExpr): - base = Undefined(MapExpr) - n = 0 - - def __init__(self, base: MapExpr, n: int) -> None: - self.base = base - self.n = n - - def __str__(self): - return str(self.base) + '.' + str(self.n) - - -class DefaultArg(MapExpr): - def __str__(self): - return 'd' - - -def compile_subclass_mapping(num_subtype_type_vars: int, - super_type: Instance) -> Tuple[List[MapPremise], - List[MapExpr]]: - """Compile mapping from superclass to subclass type variables. - - Args: - num_subtype_type_vars: number of type variables in subclass - super_type: definition of supertype; this may contain type - variable references - """ - - # TODO describe what's going on - - premises = find_eq_premises(super_type, None) - exprs = [] # type: List[MapExpr] - - for i in range(1, num_subtype_type_vars + 1): - paths = find_all_paths(i, super_type, None) - if len(paths) == 0: - exprs.append(DefaultArg()) - else: - exprs.append(paths[0]) - if len(paths) > 1: - # Multiple paths; make sure they are all the same. - for j in range(1, len(paths)): - premises.append(AssertEq(paths[0], paths[j])) - - return premises, exprs - - -def find_all_paths(tv: int, typ: Type, expr: MapExpr) -> List[MapExpr]: - if isinstance(typ, TypeVar) and (cast(TypeVar, typ)).id == tv: - return [expr] - elif isinstance(typ, Instance) and (cast(Instance, typ)).args != []: - inst = cast(Instance, typ) - res = [] # type: List[MapExpr] - for i in range(len(inst.args)): - e = Undefined # type: MapExpr - if not expr: - e = TypeVarRef(i + 1) - else: - e = TypeArgRef(expr, i + 1) - res += find_all_paths(tv, inst.args[i], e) - return res - else: - return [] - - -def find_eq_premises(typ: Type, expr: MapExpr) -> List[MapPremise]: - if isinstance(typ, Instance): - inst = cast(Instance, typ) - res = [] # type: List[MapPremise] - if expr: - res.append(AssertClass(expr, inst.type)) - for i in range(len(inst.args)): - e = Undefined # type: MapExpr - if not expr: - e = TypeVarRef(i + 1) - else: - e = TypeArgRef(expr, i + 1) - res += find_eq_premises(inst.args[i], e) - return res - elif isinstance(typ, AnyType): - return [AssertDyn(expr)] - else: - return [] diff --git a/mypy/compileslotmap.py b/mypy/compileslotmap.py deleted file mode 100644 index d38f8d206f0c..000000000000 --- a/mypy/compileslotmap.py +++ /dev/null @@ -1,68 +0,0 @@ -from typing import List, Tuple - -from mypy.types import Type -from mypy.nodes import TypeInfo -from mypy.semanal import self_type -from mypy.subtypes import map_instance_to_supertype -from mypy.maptypevar import num_slots, get_tvar_access_path - - -def compile_slot_mapping(typ: TypeInfo) -> List[Type]: - """Return types that represent values of type variable slots of a type. - - The returned types are in terms of type variables of the type. - - For example, assume these definitions: - - class D(Generic[S]): ... - class C(D[E[S]], Generic[T, S]): ... - - Now slot mappings for C is [E[S], T] (S and T refer to type variables of - C). - """ - exprs = [] # type: List[Type] - - for slot in range(num_slots(typ)): - # Figure out the superclass which defines the slot; also figure out - # the tvar index that maps to the slot. - origin, tv = find_slot_origin(typ, slot) - - # Map self type to the superclass -> extract tvar with target index - # (only contains subclass tvars?? PROBABLY NOT). - selftype = self_type(typ) - selftype = map_instance_to_supertype(selftype, origin) - tvar = selftype.args[tv - 1] - - # tvar is the representation of the slot in terms of type arguments. - exprs.append(tvar) - - return exprs - - -def find_slot_origin(info: TypeInfo, slot: int) -> Tuple[TypeInfo, int]: - """Determine class and type variable index that directly maps to the slot. - - The result defines which class in inheritance hierarchy of info introduced - the slot. All subclasses inherit this slot. The result TypeInfo always - refers to one of the base classes of info (or info itself). - - Examples: - - In 'class C(Generic[T]): ...', the slot 0 in C is mapped to - type var 1 (T) in C. - - In 'class D(C[U], Generic[S, U]): ...', the slot 0 in D is mapped - to type var 1 (T) in C; the slot 1 of D is mapped to type variable 1 - of D. - """ - base = info.bases[0].type - super_slots = num_slots(base) - if slot < super_slots: - # A superclass introduced the slot. - return find_slot_origin(base, slot) - else: - # This class introduced the slot. Figure out which type variable maps - # to the slot. - for tv in range(1, len(info.type_vars) + 1): - if get_tvar_access_path(info, tv)[0] - 1 == slot: - return (info, tv) - - raise RuntimeError('Could not map slot') diff --git a/mypy/opgen.py b/mypy/opgen.py deleted file mode 100644 index 68143cf6cd78..000000000000 --- a/mypy/opgen.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Alore code generation for runtime type operations. - -TODO This is mostly obsolete, but this is kept anyway, since some of the - code or at least some ideas can probably be reused. -""" - -from typing import List, cast - -from nodes import MypyFile, ClassDef, TypeInfo -from types import Instance, TypeVar, BOUND_VAR, Type -import transform -from maptypevar import num_slots, get_tvar_access_path -from compileslotmap import compile_slot_mapping -from transutil import dynamic_suffix, tvar_slot_name - - -def generate_runtime_support(f: MypyFile) -> str: - return generate_slot_map(f) + '\n' + generate_type_map(f) - - -# Type-to-slot mapping - - -def generate_slot_map(f: MypyFile) -> str: - map = [] # type: List[str] - ops = [] # type: List[str] - map.append('def __InitSlotMap()') - map.append(' __SlotMap = std::Map(') - for d in f.defs: - if isinstance(d, ClassDef): - td = cast(ClassDef, d) - if td.info: - add_slot_map_data(map, ops, td.info) - map.append(' )') - map.append('end') - return '\n'.join(map) + '\n' + '\n'.join(ops) - - -def add_slot_map_data(map: List[str], ops: List[str], typ: TypeInfo) -> None: - base = typ - while base: - add_slot_map_support_for_type_pair(map, ops, base, typ) - base = base.base - - -def add_slot_map_support_for_type_pair(map: List[str], ops: List[str], - base: TypeInfo, typ: TypeInfo) -> None: - op = '__{}TypeTo{}Slots'.format(base.name(), typ.name()) - map.append(' ({}, {}) : {},'.format(base.name(), typ.name(), op)) - if typ.is_generic(): - map.append(' ({}, {}) : {},'.format(base.name(), typ.name() + - dynamic_suffix(False), - op)) - generate_slot_map_op(ops, op, base, typ) - - -def generate_slot_map_op(ops: List[str], op: str, base: TypeInfo, - typ: TypeInfo) -> None: - ops.append('def {}(t)'.format(op)) - nslots = num_slots(typ) - slots = compile_slot_mapping(base) - a = [] # type: List[str] - for t in slots: - a.append(transform_type_to_runtime_repr(t)) - for i in range(len(slots), nslots): - a.append('__Dyn') - ops.append(' return [' + ', '.join(a) + ']') - ops.append('end') - - -def transform_type_to_runtime_repr(t: Type) -> str: - if isinstance(t, Instance): - inst = cast(Instance, t) - if inst.args == []: - return inst.type.name() - else: - args = [] # type: List[str] - for a in inst.args: - args.append(transform_type_to_runtime_repr(a)) - return '__Gen({}, [{}])'.format(inst.type.name(), ', '.join(args)) - elif isinstance(t, TypeVar): - tv = cast(TypeVar, t) - return 't.args[{}]'.format(tv.id - 1) - else: - raise TypeError('{} not supported'.format(t)) - - -# Slot-to-type mapping - - -def generate_type_map(f: MypyFile) -> str: - map = [] # type: List[str] - ops = [] # type: List[str] - map.append('def __InitTypeMap()') - for alt, suffix in [(None, ''), (BOUND_VAR, 'B')]: - map.append(' __TypeMap{} = std::Map('.format(suffix)) - for d in f.defs: - if isinstance(d, ClassDef): - td = cast(ClassDef, d) - if td.info is not None: - add_type_map_support_for_type(map, ops, td.info, alt, - suffix) - map.append(' )') - map.append('end') - return '\n'.join(map) + '\n' + '\n'.join(ops) - - -def add_type_map_support_for_type(map: List[str], ops: List[str], - typ: TypeInfo, alt, suffix: str) -> None: - op = '__{}ValueToType{}'.format(typ.name(), suffix) - map.append(' {} : {},'.format(typ.name(), op)) - if typ.is_generic(): - map.append(' {} : {},'.format(typ.name() + dynamic_suffix(False), - op)) - generate_type_map_op(ops, op, typ, alt) - - -def generate_type_map_op(ops: List[str], op: str, typ, alt) -> None: - ops.append('def {}(v)'.format(op)) - a = [] # type: List[str] - for i in range(len(typ.type_vars)): - p = get_tvar_access_path(typ, i + 1) - expr = 'v.' + tvar_slot_name(p[0] - 1, alt) - for j in p[1:]: - expr += '.args[{}]'.format(j - 1) - a.append(expr) - ops.append(' return [{}]'.format(', '.join(a))) - ops.append('end') diff --git a/mypy/rttypevars.py b/mypy/rttypevars.py deleted file mode 100644 index 37f81b9f9b18..000000000000 --- a/mypy/rttypevars.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Translation of type variables to runtime type variable expressions. - -Source-level type variables are mapped to references to type variable slots -in instancse or local variables that contain the runtime values of type -variables. -""" - -from typing import Any - -from mypy.types import Type, TypeTranslator, TypeVar, RuntimeTypeVar -from mypy.nodes import NameExpr, TypeInfo -from mypy.transutil import tvar_arg_name -from mypy.maptypevar import get_tvar_access_expression - - -def translate_runtime_type_vars_locally(typ: Type) -> Type: - """Replace type variable references in a type with runtime type variables. - - The type variable references refer to runtime local variables (__tv* etc.), - i.e. this assumes a generic class constructor context. - """ - return typ.accept(TranslateRuntimeTypeVarsLocallyVisitor()) - - -class TranslateRuntimeTypeVarsLocallyVisitor(TypeTranslator): - """Reuse most of the implementation by inheriting TypeTranslator.""" - def visit_type_var(self, t: TypeVar) -> Type: - # FIX function type variables - return RuntimeTypeVar(NameExpr(tvar_arg_name(t.id))) - - -def translate_runtime_type_vars_in_context(typ: Type, context: TypeInfo, - is_java: Any) -> Type: - """Replace type variable types within a type with runtime type variables. - - Perform the translation in the context of the given type. - - For example, assuming class A(Generic[T, S]) ... and - class B(A[X, Y[U]], Generic[U]) ...: - - TranslateRuntimeTypeVarsInContext(C[U`1], ) == - C[RuntimeTypeVar()] (<...> uses node repr.) - """ - return typ.accept(ContextualRuntimeTypeVarTranslator(context, is_java)) - - -class ContextualRuntimeTypeVarTranslator(TypeTranslator): - """Reuse most of the implementation by inheriting TypeTranslator.""" - def __init__(self, context, is_java): - self.context = context - self.is_java = is_java - - def visit_type_var(self, t: TypeVar) -> Type: - if t.id < 0: - # Generic function type variable; always in a local variable. - return RuntimeTypeVar(NameExpr(tvar_arg_name(t.id))) - else: - # Instance type variables are stored in the instance. - return get_tvar_access_expression(self.context, t.id, - t.is_wrapper_var, self.is_java) From b4ad29eb93fd442246d142cae42a532bc3dd5e5f Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 14:16:26 -0700 Subject: [PATCH 011/144] Remove obsolete code --- mypy/maptypevar.py | 149 -------------------------------- mypy/pprinter.py | 1 - mypy/transutil.py | 206 --------------------------------------------- 3 files changed, 356 deletions(-) delete mode 100644 mypy/maptypevar.py delete mode 100644 mypy/transutil.py diff --git a/mypy/maptypevar.py b/mypy/maptypevar.py deleted file mode 100644 index 421b5038e4a4..000000000000 --- a/mypy/maptypevar.py +++ /dev/null @@ -1,149 +0,0 @@ -from typing import Any, List, cast - -from mypy.types import RuntimeTypeVar, OBJECT_VAR, Instance, Type, TypeVar -from mypy.nodes import TypeInfo, Node, MemberExpr, IndexExpr, IntExpr -from mypy.transutil import self_expr, tvar_slot_name - - -def get_tvar_access_expression(typ: TypeInfo, tvindex: int, alt: Any, - is_java: Any) -> RuntimeTypeVar: - """Return a type expression that maps from runtime type variable slots - to the type variable in the given class with the given index. - - For example, assuming class A(Generic[T, S]): ... and - class B(A[X, Y[U]], Generic[U]): ...: - - get_tvar_access_expression(, 1) == - RuntimeTypeVar() (with <...> represented as nodes) - """ - # First get the description of how to get from supertype type variables to - # a subtype type variable. - mapping = get_tvar_access_path(typ, tvindex) - - # The type checker should have noticed if there is no mapping. Be defensive - # and make sure there is one. - if mapping is None: - raise RuntimeError('Could not find a typevar mapping') - - # Build the expression for getting at the subtype type variable - # progressively. - - # First read the value of a supertype runtime type variable slot. - s = self_expr() # type: Node - if alt == OBJECT_VAR: - o = '__o' - if is_java: - o = '__o_{}'.format(typ.name) - s = MemberExpr(s, o) - expr = MemberExpr(s, tvar_slot_name(mapping[0] - 1, alt)) # type: Node - - # Then, optionally look into arguments based on the description. - for i in mapping[1:]: - expr = MemberExpr(expr, 'args') - expr = IndexExpr(expr, IntExpr(i - 1)) - - # Than add a final wrapper so that we have a valid type. - return RuntimeTypeVar(expr) - - -def get_tvar_access_path(typ: TypeInfo, tvindex: int) -> List[int]: - """Determine how to calculate the value of a type variable of a type. - - The description is based on operations on type variable slot values - embedded in an instance. The type variable and slot indexing is 1-based. - - - If tvar slot x maps directly to tvar tvindex in the type, return [x]. - - If argument y of slot x maps to tvar tvindex, return [x, y]. For - argument z of argument y of x return [x, y, z], etc. - - If there is no relation, return None. - - For example, assume these definitions: - - class A(Generic[S, U]): ... - class B(A[X, Y[T]], Generic[T]): ... - - Now we can query the access path to type variable 1 (T) of B: - - get_tvar_access_path(, 1) == [2, 1] (slot 2, lookup type argument 1). - """ - if not typ.bases: - return None - - # Check argument range. - if tvindex < 1 or tvindex > len(typ.type_vars): - raise RuntimeError('{} does not have tvar #{}'.format(typ.name, - tvindex)) - - # Figure out the superclass instance type. - base = typ.bases[0] - - # Go through all the supertype tvars to find a match. - mapping = None # type: List[int] - for i in range(len(base.args)): - mapping = find_tvar_mapping(base.args[i], tvindex) - if mapping is not None: - if base.type.bases[0]: - return get_tvar_access_path(base.type, i + 1) + mapping - else: - return [i + 1] + mapping - - # The type variable was introduced in this type. - return [tvar_slot_index(typ, tvindex)] - - -def find_tvar_mapping(t: Type, index: int) -> List[int]: - """Recursively search for a type variable instance (with given index) - within the type t, which represents a supertype definition. Return the - path to the first found instance. - - - If t is a bare type variable with correct index, return [] as the path. - - If type variable is within instance arguments, return the indexing - operations required to get it. - - If no result could be found, return None. - - Examples: - find_tvar_mapping(T`1, 1) == [] - find_tvar_mapping(A, 1) == [2] - find_tvar_mapping(A, T`1>, 2) == [0, 1] - find_tvar_mapping(A, T`1) == None (no T`1 within t) - find_tvar_mapping(A, T`1) == [0] (first match) - """ - if isinstance(t, Instance) and (cast(Instance, t)).args != []: - inst = cast(Instance, t) - for argi in range(len(inst.args)): - mapping = find_tvar_mapping(inst.args[argi], index) - if mapping is not None: - return get_tvar_access_path(inst.type, argi + 1) + mapping - return None - elif isinstance(t, TypeVar) and (cast(TypeVar, t)).id == index: - return [] - else: - return None - - -def tvar_slot_index(typ: TypeInfo, tvindex: int) -> int: - """If the specified type variable was introduced as a new variable in type, - return the slot index (1 = first type varible slot) of the type variable. - """ - base_slots = num_slots(typ.bases[0].type) - - for i in range(1, tvindex): - if get_tvar_access_path(typ, i)[0] > base_slots: - base_slots += 1 - - return base_slots + 1 - - -def num_slots(typ: TypeInfo) -> int: - """Return the number of type variable slots used by a type. - - If type is None, the result is 0. - """ - if not typ or not typ.bases: - return 0 - slots = num_slots(typ.bases[0].type) - ntv = len(typ.type_vars) - for i in range(ntv): - n = get_tvar_access_path(typ, i + 1)[0] - slots = max(slots, n) - return slots diff --git a/mypy/pprinter.py b/mypy/pprinter.py index b11acb28f0f9..43456c000517 100644 --- a/mypy/pprinter.py +++ b/mypy/pprinter.py @@ -8,7 +8,6 @@ from mypy.visitor import NodeVisitor from mypy.types import Void, TypeVisitor, Callable, Instance, Type, UnboundType from mypy.maptypevar import num_slots -from mypy.transutil import tvar_arg_name from mypy import coerce from mypy import nodes diff --git a/mypy/transutil.py b/mypy/transutil.py deleted file mode 100644 index 35ab7de79e6f..000000000000 --- a/mypy/transutil.py +++ /dev/null @@ -1,206 +0,0 @@ -from typing import cast, Any, List - -from mypy.types import ( - Callable, Type, AnyType, TypeTranslator, TypeVar, BOUND_VAR, OBJECT_VAR, - replace_leading_arg_type -) -from mypy.nodes import FuncDef, TypeInfo, NameExpr, LDEF -from mypy import nodes -from mypy.noderepr import FuncRepr, FuncArgsRepr, CallExprRepr -from mypy.lex import Token -from mypy.nodes import function_type -from mypy.sametypes import is_same_type -from mypy.parse import none - - -def prepend_arg_type(t: Callable, arg_type: Type) -> Callable: - """Prepend an argument with the given type to a callable type.""" - return Callable([arg_type] + t.arg_types, - [nodes.ARG_POS] + t.arg_kinds, - List[str]([None]) + t.arg_names, - t.ret_type, - t.is_type_obj(), - t.name, - t.variables, - t.bound_vars, - t.line, None) - - -def add_arg_type_after_self(t: Callable, arg_type: Type) -> Callable: - """Add an argument with the given type to a callable type after 'self'.""" - return Callable([t.arg_types[0], arg_type] + t.arg_types[1:], - [t.arg_kinds[0], nodes.ARG_POS] + t.arg_kinds[1:], - [t.arg_names[0], None] + t.arg_names[1:], - t.ret_type, - t.is_type_obj(), - t.name, - t.variables, - t.bound_vars, - t.line, None) - - -def replace_ret_type(t: Callable, ret_type: Type) -> Callable: - """Return a copy of a callable type with a different return type.""" - return Callable(t.arg_types, - t.arg_kinds, - t.arg_names, - ret_type, - t.is_type_obj(), - t.name, - t.variables, - t.bound_vars, - t.line, None) - - -def dynamic_sig(sig: Callable) -> Callable: - """Translate callable type to type erased (dynamically-typed) callable. - - Preserve the number and kinds of arguments. - """ - return Callable([AnyType()] * len(sig.arg_types), - sig.arg_kinds, - sig.arg_names, - AnyType(), - sig.is_type_obj()) - - -def translate_type_vars_to_wrapper_vars(typ: Type) -> Type: - """Translate any instance type variables in a type into wrapper tvars. - - (Wrapper tvars are type variables that refer to values stored in a generic - class wrapper). - """ - return typ.accept(TranslateTypeVarsToWrapperVarsVisitor()) - - -class TranslateTypeVarsToWrapperVarsVisitor(TypeTranslator): - """Visitor that implements TranslateTypeVarsToWrapperVarsVisitor.""" - def visit_type_var(self, t: TypeVar) -> Type: - if t.id > 0: - return TypeVar(t.name, t.id, t.values, True, t.line, t.repr) - else: - return t - - -def translate_type_vars_to_bound_vars(typ: Type) -> Type: - return typ.accept(TranslateTypeVarsToBoundVarsVisitor()) - - -class TranslateTypeVarsToBoundVarsVisitor(TypeTranslator): - def visit_type_var(self, t: TypeVar) -> Type: - if t.id > 0: - return TypeVar(t.name, t.id, t.values, BOUND_VAR, t.line, t.repr) - else: - return t - - -def translate_type_vars_to_wrapped_object_vars(typ: Type) -> Type: - return typ.accept(TranslateTypeVarsToWrappedObjectVarsVisitor()) - - -class TranslateTypeVarsToWrappedObjectVarsVisitor(TypeTranslator): - def visit_type_var(self, t: TypeVar) -> Type: - if t.id > 0: - return TypeVar(t.name, t.id, t.values, OBJECT_VAR, t.line, t.repr) - else: - return t - - -def translate_function_type_vars_to_dynamic(typ: Type) -> Type: - """Translate any function type variables in a type into type 'Any'.""" - return typ.accept(TranslateFunctionTypeVarsToDynamicVisitor()) - - -class TranslateFunctionTypeVarsToDynamicVisitor(TypeTranslator): - """Visitor that implements TranslateTypeVarsToWrapperVarsVisitor.""" - def visit_type_var(self, t: TypeVar) -> Type: - if t.id < 0: - return AnyType() - else: - return t - - -def is_generic(fdef: FuncDef) -> bool: - """Is a function a method of a generic type? - - (Note that this may return False even if the function itself is generic.) - """ - return fdef.info is not None and fdef.info.type_vars != [] - - -def is_simple_override(fdef: FuncDef, info: TypeInfo) -> bool: - """Is function an override with the same type precision as the original? - - Compare to the original method in the superclass of info. - """ - # If this is not an override, this can't be a simple override either. - # Generic inheritance is not currently supported, since we need to map - # type variables between types; in the future this restriction can be - # lifted. - if len(info.mro) <= 1: - return False - base = info.mro[1] - if base.type_vars != []: - return False - orig = base.get_method(fdef.name()) - # Ignore the first argument (self) when determining type sameness. - # TODO overloads - newtype = cast(Callable, function_type(fdef)) - newtype = replace_leading_arg_type(newtype, AnyType()) - origtype = cast(Callable, function_type(orig)) - origtype = replace_leading_arg_type(origtype, AnyType()) - return is_same_type(newtype, origtype) - - -def tvar_slot_name(n: int, is_alt: Any = False) -> str: - """Return the name of the member that holds the runtime value of the given - type variable slot. - """ - if is_alt != BOUND_VAR: - if n == 0: - return '__tv' - else: - return '__tv{}'.format(n + 1) - else: - # Greatest lower bound - if n == 0: - return '__btv' - else: - return '__btv{}'.format(n + 1) - - -def tvar_arg_name(n: int, is_alt: Any = False) -> str: - """Return the name of the implicit function/constructor argument that - contains the runtime value of a type variable. n is 1, 2, ... for instance - type variables and -1, -2, ... for function type variables. - """ - if is_alt != BOUND_VAR: - if n > 0: - # Equivalent to slot name. - return tvar_slot_name(n - 1) - elif n == -1: - return '__ftv' - else: - return '__ftv{}'.format(-n) - else: - if n > 0: - # Equivalent to slot name. - return tvar_slot_name(n - 1, BOUND_VAR) - elif n == -1: - return '__bftv' # FIX do we need this? - else: - return '__bftv{}'.format(-n) # FIX do we need this? - - -def dynamic_suffix(is_pretty: bool) -> str: - """Return the suffix of the dynamic wrapper of a method or class.""" - if is_pretty: - return '*' - else: - return '___dyn' - - -def self_expr() -> NameExpr: - n = NameExpr('self') - n.kind = LDEF - return n From 30e0f70db5b04621bda139da317f035b8ef22f92 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 12:43:59 -0700 Subject: [PATCH 012/144] Include tuple instance type in TupleType instances --- mypy/checker.py | 14 ++++++++------ mypy/checkexpr.py | 4 ++-- mypy/expandtype.py | 2 +- mypy/join.py | 3 ++- mypy/meet.py | 4 +++- mypy/parsetype.py | 2 +- mypy/semanal.py | 17 ++++++++++++++++- mypy/test/testinfer.py | 9 ++++++--- mypy/test/testtypes.py | 12 ++++++------ mypy/typeanal.py | 21 +++++++++++++++++---- mypy/types.py | 17 ++++++++++++++--- 11 files changed, 76 insertions(+), 29 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 044fbc5465b3..b17d7318c218 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1101,7 +1101,7 @@ def check_multi_assignment(self, lvalue_types: List[Type], if not msg: msg = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT # First handle case where rvalue is of form Undefined, ... - rvalue_type = get_undefined_tuple(rvalue) + rvalue_type = get_undefined_tuple(rvalue, self.named_type('builtins.tuple')) undefined_rvalue = True if not rvalue_type: # Infer the type of an ordinary rvalue expression. @@ -1123,8 +1123,10 @@ def check_multi_assignment(self, lvalue_types: List[Type], items.append(rvalue_type.items[i]) if not undefined_rvalue: # Infer rvalue again, now in the correct type context. - rvalue_type = cast(TupleType, self.accept(rvalue, - TupleType(items))) + rvalue_type = cast(TupleType, + self.accept( + rvalue, + TupleType(items, self.named_type('builtins.tuple')))) if len(rvalue_type.items) != len(lvalue_types): self.msg.incompatible_value_count_in_assignment( len(lvalue_types), len(rvalue_type.items), context) @@ -1757,7 +1759,7 @@ def lookup_qualified(self, name: str) -> SymbolTableNode: parts = name.split('.') n = self.modules[parts[0]] for i in range(1, len(parts) - 1): - n = cast(MypyFile, ((n.names.get(parts[i], None).node))) + n = cast(MypyFile, n.names.get(parts[i], None).node) return n.names[parts[-1]] def enter(self) -> None: @@ -1832,7 +1834,7 @@ class C(D[E[T]], Generic[T]) ... return expand_type_by_instance(typ, inst_type) -def get_undefined_tuple(rvalue: Node) -> Type: +def get_undefined_tuple(rvalue: Node, tuple_type: Instance) -> Type: """Get tuple type corresponding to a tuple of Undefined values. The type is Tuple[Any, ...]. If rvalue is not of the right form, return @@ -1843,7 +1845,7 @@ def get_undefined_tuple(rvalue: Node) -> Type: if not refers_to_fullname(item, 'typing.Undefined'): break else: - return TupleType([AnyType()] * len(rvalue.items)) + return TupleType([AnyType()] * len(rvalue.items), tuple_type) return None diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 5663cfe60545..49651b52121c 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1070,7 +1070,7 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: tt = self.accept(item, ctx.items[i]) self.check_not_void(tt, e) items.append(tt) - return TupleType(items) + return TupleType(items, self.named_type('builtins.tuple')) def visit_dict_expr(self, e: DictExpr) -> Type: # Translate into type checking a generic function call. @@ -1080,7 +1080,7 @@ def visit_dict_expr(self, e: DictExpr) -> Type: # The callable type represents a function like this: # # def (*v: Tuple[kt, vt]) -> Dict[kt, vt]: ... - constructor = Callable([TupleType([tv1, tv2])], + constructor = Callable([TupleType([tv1, tv2], self.named_type('builtins.tuple'))], [nodes.ARG_STAR], [None], self.chk.named_generic_type('builtins.dict', diff --git a/mypy/expandtype.py b/mypy/expandtype.py index 326eeffaa182..c33866e3711f 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -92,7 +92,7 @@ def visit_overloaded(self, t: Overloaded) -> Type: return Overloaded(items) def visit_tuple_type(self, t: TupleType) -> Type: - return TupleType(self.expand_types(t.items), t.line, t.repr) + return TupleType(self.expand_types(t.items), t.fallback, t.line, t.repr) def visit_union_type(self, t: UnionType) -> Type: return UnionType(self.expand_types(t.items), t.line, t.repr) diff --git a/mypy/join.py b/mypy/join.py index ab1a38c3c37d..1184f1efcf90 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -146,7 +146,8 @@ def visit_tuple_type(self, t: TupleType) -> Type: for i in range(t.length()): items.append(self.join(t.items[i], (cast(TupleType, self.s)).items[i])) - return TupleType(items) + # TODO: What if the fallback types are different? + return TupleType(items, t.fallback) else: return self.default(self.s) diff --git a/mypy/meet.py b/mypy/meet.py index e9454f926108..325d1051b6c3 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -13,6 +13,7 @@ def meet_types(s: Type, t: Type, basic: BasicTypes) -> Type: + """Return the greatest lower bound of two types.""" if isinstance(s, ErasedType): return s if isinstance(s, AnyType): @@ -177,7 +178,8 @@ def visit_tuple_type(self, t: TupleType) -> Type: for i in range(t.length()): items.append(self.meet(t.items[i], (cast(TupleType, self.s)).items[i])) - return TupleType(items) + # TODO: What if the fallbacks are different? + return TupleType(items, t.fallback) else: return self.default(self.s) diff --git a/mypy/parsetype.py b/mypy/parsetype.py index 56fce32b4ba9..428665586018 100644 --- a/mypy/parsetype.py +++ b/mypy/parsetype.py @@ -79,7 +79,7 @@ def parse_types(self) -> Type: while self.current_token_str() == ',': self.skip() items.append(self.parse_type()) - type = TupleType(items) + type = TupleType(items, None) if parens: self.expect(')') return type diff --git a/mypy/semanal.py b/mypy/semanal.py index eb0762fb5866..27e5bbe9b3fa 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -675,7 +675,9 @@ def visit_var_def(self, defn: VarDef) -> None: def anal_type(self, t: Type) -> Type: if t: - a = TypeAnalyser(self.lookup_qualified, self.stored_vars, self.fail) + a = TypeAnalyser(self.lookup_qualified, + self.lookup_fully_qualified, + self.stored_vars, self.fail) return t.accept(a) else: return None @@ -1385,6 +1387,19 @@ def lookup_qualified(self, name: str, ctx: Context) -> SymbolTableNode: n = self.normalize_type_alias(n, ctx) return n + def lookup_fully_qualified(self, name: str) -> SymbolTableNode: + """Lookup a fully qualified name. + + Assume that the name is defined. This happens in the global namespace -- the local + module namespace is ignored. + """ + assert '.' in name + parts = name.split('.') + n = self.modules[parts[0]] + for i in range(1, len(parts) - 1): + n = cast(MypyFile, n.names[parts[i]].node) + return n.names[parts[-1]] + def qualified_name(self, n: str) -> str: return self.cur_mod_id + '.' + n diff --git a/mypy/test/testinfer.py b/mypy/test/testinfer.py index e3305955662e..51d286e8d5e6 100644 --- a/mypy/test/testinfer.py +++ b/mypy/test/testinfer.py @@ -74,17 +74,20 @@ def test_tuple_star(self): [ARG_STAR], [ARG_POS], [[0]], - TupleType([AnyType()])) + self.tuple_type([AnyType()])) self.assert_vararg_map( [ARG_STAR], [ARG_POS, ARG_POS], [[0], [0]], - TupleType([AnyType(), AnyType()])) + self.tuple_type([AnyType(), AnyType()])) self.assert_vararg_map( [ARG_STAR], [ARG_POS, ARG_OPT, ARG_OPT], [[0], [0], []], - TupleType([AnyType(), AnyType()])) + self.tuple_type([AnyType(), AnyType()])) + + def tuple_tupe(self, args): + return TupleType(args, None) def test_named_args(self): self.assert_map( diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index a062bf11be03..2d9d59a92f53 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -71,9 +71,9 @@ def test_callable_type_with_var_args(self): assert_equal(str(c3), 'def (X? =, *Y?) -> Any') def test_tuple_type(self): - assert_equal(str(TupleType([])), 'Tuple[]') - assert_equal(str(TupleType([self.x])), 'Tuple[X?]') - assert_equal(str(TupleType([self.x, AnyType()])), 'Tuple[X?, Any]') + assert_equal(str(TupleType([], None)), 'Tuple[]') + assert_equal(str(TupleType([self.x], None)), 'Tuple[X?]') + assert_equal(str(TupleType([self.x, AnyType()], None)), 'Tuple[X?, Any]') def test_type_variable_binding(self): assert_equal(str(TypeVarDef('X', 1, None)), 'X') @@ -222,7 +222,7 @@ def test_is_proper_subtype(self): # Helpers def tuple(self, *a): - return TupleType(a) + return TupleType(a, self.fx.std_tuple) def callable(self, vars, *a) -> Callable: """callable(args, a1, ..., an, r) constructs a callable with @@ -454,7 +454,7 @@ def assert_simple_join(self, s, t, join): '{} not subtype of {}'.format(t, result)) def tuple(self, *a): - return TupleType(a) + return TupleType(a, self.fx.std_tuple) def callable(self, *a): """callable(a1, ..., an, r) constructs a callable with argument types @@ -658,7 +658,7 @@ def assert_simple_meet(self, s, t, meet): '{} not subtype of {}'.format(result, t)) def tuple(self, *a): - return TupleType(a) + return TupleType(a, self.fx.std_tuple) def callable(self, *a): """callable(a1, ..., an, r) constructs a callable with argument types diff --git a/mypy/typeanal.py b/mypy/typeanal.py index cc606acfc434..a1f7b5ab9e9f 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -64,10 +64,13 @@ def analyse_node(lookup: Function[[str, Context], SymbolTableNode], class TypeAnalyser(TypeVisitor[Type]): """Semantic analyzer for types (semantic analysis pass 2).""" - def __init__(self, lookup_func: Function[[str, Context], SymbolTableNode], + def __init__(self, + lookup_func: Function[[str, Context], SymbolTableNode], + lookup_fqn_func: Function[[str], SymbolTableNode], stored_vars: Dict[Node, Type], fail_func: Function[[str, Context], None]) -> None: self.lookup = lookup_func + self.lookup_fqn_func = lookup_fqn_func self.fail = fail_func self.stored_vars = stored_vars @@ -89,7 +92,8 @@ def visit_unbound_type(self, t: UnboundType) -> Type: elif sym.node.fullname() == 'typing.Any': return AnyType() elif sym.node.fullname() == 'typing.Tuple': - return TupleType(self.anal_array(t.args)) + return TupleType(self.anal_array(t.args), + self.builtin_type('builtins.tuple')) elif sym.node.fullname() == 'typing.Union': return UnionType(self.anal_array(t.args)) elif sym.node.fullname() == 'typing.Function': @@ -104,7 +108,9 @@ def visit_unbound_type(self, t: UnboundType) -> Type: return t info = cast(TypeInfo, sym.node) if len(t.args) > 0 and info.fullname() == 'builtins.tuple': - return TupleType(self.anal_array(t.args), t.line, t.repr) + return TupleType(self.anal_array(t.args), + Instance(info, [], t.line), + t.line, t.repr) else: # Analyze arguments and construct Instance type. The # number of type arguments and their values are @@ -146,7 +152,9 @@ def visit_callable(self, t: Callable) -> Type: return res def visit_tuple_type(self, t: TupleType) -> Type: - return TupleType(self.anal_array(t.items), t.line, t.repr) + return TupleType(self.anal_array(t.items), + self.builtin_type('builtins.tuple'), + t.line, t.repr) def visit_union_type(self, t: UnionType) -> Type: return UnionType(self.anal_array(t.items), t.line, t.repr) @@ -183,6 +191,11 @@ def anal_var_defs(self, var_defs: List[TypeVarDef]) -> List[TypeVarDef]: vd.line, vd.repr)) return a + def builtin_type(self, fully_qualified_name: str) -> Instance: + node = self.lookup_fqn_func(fully_qualified_name) + info = cast(TypeInfo, node.node) + return Instance(info, []) + class TypeAnalyserPass3(TypeVisitor[None]): """Analyze type argument counts and values of generic types. diff --git a/mypy/types.py b/mypy/types.py index 93937ab037fa..34be0a4e7068 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -371,13 +371,22 @@ def accept(self, visitor: 'TypeVisitor[T]') -> T: class TupleType(Type): - """The tuple type Tuple[T1, ..., Tn] (at least one type argument).""" + """The tuple type Tuple[T1, ..., Tn] (at least one type argument). + + Instance variables: + items -- tuple item types + fallback -- the underlying instance type that is used for non-tuple methods + (this is currently always builtins.tuple, but it could be different for named + tuples, for example) + """ items = Undefined(List[Type]) + fallback = Undefined(Instance) - def __init__(self, items: List[Type], line: int = -1, + def __init__(self, items: List[Type], fallback: Instance, line: int = -1, repr: Any = None) -> None: self.items = items + self.fallback = fallback super().__init__(line, repr) def length(self) -> int: @@ -562,7 +571,9 @@ def visit_callable(self, t: Callable) -> Type: t.line, t.repr) def visit_tuple_type(self, t: TupleType) -> Type: - return TupleType(self.translate_types(t.items), t.line, t.repr) + return TupleType(self.translate_types(t.items), + Any(t.fallback.accept(self)), + t.line, t.repr) def visit_union_type(self, t: UnionType) -> Type: return UnionType(self.translate_types(t.items), t.line, t.repr) From 192f80b0e8cd50a14e1e840cbdf624cf11425399 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 12:57:53 -0700 Subject: [PATCH 013/144] Fixes to test cases --- mypy/test/data/check-expressions.test | 3 ++- mypy/test/data/check-generics.test | 1 + mypy/test/data/fixtures/bool.py | 3 +-- mypy/test/data/fixtures/dict.py | 2 ++ mypy/test/data/fixtures/exception.py | 1 + mypy/test/data/fixtures/isinstance.py | 2 ++ mypy/test/data/fixtures/isinstancelist.py | 3 ++- mypy/test/data/fixtures/ops.py | 1 + mypy/test/data/fixtures/primitives.py | 1 + mypy/test/data/fixtures/set.py | 1 + mypy/test/data/fixtures/slice.py | 1 + mypy/test/data/lib-stub/builtins.py | 3 ++- mypy/test/testinfer.py | 8 ++++---- 13 files changed, 21 insertions(+), 9 deletions(-) diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index d903d3153870..5166784b2322 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -89,6 +89,7 @@ class A: pass class object: def __init__(self): pass class bytes: pass +class tuple: pass [case testUnicodeLiteralInPython3] from typing import Undefined @@ -479,7 +480,7 @@ class A(P): pass class B(P): pass - + class X: def __lt__(self, o: 'Y') -> A: pass def __gt__(self, o: 'Y') -> A: pass diff --git a/mypy/test/data/check-generics.test b/mypy/test/data/check-generics.test index b643a83290ba..6cd5f0430bf8 100644 --- a/mypy/test/data/check-generics.test +++ b/mypy/test/data/check-generics.test @@ -636,6 +636,7 @@ class list(Iterable[T], Generic[T]): def __setitem__(self, x: int, v: T) -> None: pass class int: pass class type: pass +class tuple: pass [case testMultipleAsssignmentWithIterable] from typing import Iterable, Undefined, typevar diff --git a/mypy/test/data/fixtures/bool.py b/mypy/test/data/fixtures/bool.py index a080e720b865..09098e221794 100644 --- a/mypy/test/data/fixtures/bool.py +++ b/mypy/test/data/fixtures/bool.py @@ -7,7 +7,6 @@ class object: def __init__(self) -> None: pass class type: pass - +class tuple: pass class bool: pass - class int: pass diff --git a/mypy/test/data/fixtures/dict.py b/mypy/test/data/fixtures/dict.py index 6d065de76678..c849383bf9a4 100644 --- a/mypy/test/data/fixtures/dict.py +++ b/mypy/test/data/fixtures/dict.py @@ -16,3 +16,5 @@ class str: pass # for keyword argument key type class list(Iterable[T], Generic[T]): # needed by some test cases def __iter__(self) -> Iterator[T]: pass def __mul__(self, x: int) -> list[T]: pass + +class tuple: pass diff --git a/mypy/test/data/fixtures/exception.py b/mypy/test/data/fixtures/exception.py index 1cffcc99268e..af92b3373635 100644 --- a/mypy/test/data/fixtures/exception.py +++ b/mypy/test/data/fixtures/exception.py @@ -3,5 +3,6 @@ class object: def __init__(self): pass class type: pass +class tuple: pass class BaseException: pass diff --git a/mypy/test/data/fixtures/isinstance.py b/mypy/test/data/fixtures/isinstance.py index 8e9fdfcbb901..86ea733e1f45 100644 --- a/mypy/test/data/fixtures/isinstance.py +++ b/mypy/test/data/fixtures/isinstance.py @@ -16,3 +16,5 @@ class int: pass class bool(int): pass @builtinclass class str: pass + +class tuple: pass diff --git a/mypy/test/data/fixtures/isinstancelist.py b/mypy/test/data/fixtures/isinstancelist.py index 21f3b7ee1e64..86b71f6a9b07 100644 --- a/mypy/test/data/fixtures/isinstancelist.py +++ b/mypy/test/data/fixtures/isinstancelist.py @@ -8,6 +8,8 @@ def __init__(self) -> None: pass class type: def __init__(self, x) -> None: pass +class tuple: pass + def isinstance(x: object, t: type) -> bool: pass @builtinclass @@ -28,4 +30,3 @@ def __mul__(self, x: int) -> list[T]: pass def __setitem__(self, x: int, v: T) -> None: pass def __getitem__(self, x: int) -> T: pass def __add__(self, x: List[T]) -> T: pass - diff --git a/mypy/test/data/fixtures/ops.py b/mypy/test/data/fixtures/ops.py index ecd2851051c7..46e7bd892f17 100644 --- a/mypy/test/data/fixtures/ops.py +++ b/mypy/test/data/fixtures/ops.py @@ -9,6 +9,7 @@ def __eq__(self, o: 'object') -> 'bool': pass def __ne__(self, o: 'object') -> 'bool': pass class type: pass +class tuple: pass class bool: pass diff --git a/mypy/test/data/fixtures/primitives.py b/mypy/test/data/fixtures/primitives.py index ffa9194908a0..523f105fc539 100644 --- a/mypy/test/data/fixtures/primitives.py +++ b/mypy/test/data/fixtures/primitives.py @@ -11,3 +11,4 @@ class str: pass class float: pass class bool: pass class bytes: pass +class tuple: pass diff --git a/mypy/test/data/fixtures/set.py b/mypy/test/data/fixtures/set.py index 3a009ed9b055..b56e74598cc0 100644 --- a/mypy/test/data/fixtures/set.py +++ b/mypy/test/data/fixtures/set.py @@ -11,5 +11,6 @@ class type: pass class int: pass class str: pass +class tuple: pass class set(Generic[T]): pass diff --git a/mypy/test/data/fixtures/slice.py b/mypy/test/data/fixtures/slice.py index 7aab8aa67682..8b5b39130609 100644 --- a/mypy/test/data/fixtures/slice.py +++ b/mypy/test/data/fixtures/slice.py @@ -6,5 +6,6 @@ def __init__(self): pass class type: pass class int: pass +class tuple: pass class slice: pass diff --git a/mypy/test/data/lib-stub/builtins.py b/mypy/test/data/lib-stub/builtins.py index f531dfbfb713..54513409ec81 100644 --- a/mypy/test/data/lib-stub/builtins.py +++ b/mypy/test/data/lib-stub/builtins.py @@ -14,5 +14,6 @@ class int: pass @builtinclass class str: pass -# Definition of None is implicit +class tuple: pass +# Definition of None is implicit diff --git a/mypy/test/testinfer.py b/mypy/test/testinfer.py index 51d286e8d5e6..2a8760671940 100644 --- a/mypy/test/testinfer.py +++ b/mypy/test/testinfer.py @@ -74,19 +74,19 @@ def test_tuple_star(self): [ARG_STAR], [ARG_POS], [[0]], - self.tuple_type([AnyType()])) + self.tuple(AnyType())) self.assert_vararg_map( [ARG_STAR], [ARG_POS, ARG_POS], [[0], [0]], - self.tuple_type([AnyType(), AnyType()])) + self.tuple(AnyType(), AnyType())) self.assert_vararg_map( [ARG_STAR], [ARG_POS, ARG_OPT, ARG_OPT], [[0], [0], []], - self.tuple_type([AnyType(), AnyType()])) + self.tuple(AnyType(), AnyType())) - def tuple_tupe(self, args): + def tuple(self, *args): return TupleType(args, None) def test_named_args(self): From c10e3db13304947f8758b7e35834bb37a6d13282 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 13:00:59 -0700 Subject: [PATCH 014/144] Remove tuple from BasicTypes --- mypy/checkmember.py | 4 ++-- mypy/erasetype.py | 2 +- mypy/types.py | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index da9b82586bce..68ea626bbbd4 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -67,8 +67,8 @@ def analyse_member_access(name: str, typ: Type, node: Context, is_lvalue: bool, msg.disable_type_names -= 1 return UnionType.make_simplified_union(results) elif isinstance(typ, TupleType): - # Actually look up from the 'tuple' type. - return analyse_member_access(name, basic_types.tuple, node, is_lvalue, + # Actually look up from the fallback instance type. + return analyse_member_access(name, typ.fallback, node, is_lvalue, is_super, basic_types, msg) elif (isinstance(typ, FunctionLike) and cast(FunctionLike, typ).is_type_obj()): diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 350c1c1e5f6c..8a340ecf3fd7 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -65,7 +65,7 @@ def visit_overloaded(self, t: Overloaded) -> Type: return t.items()[0].accept(self) def visit_tuple_type(self, t: TupleType) -> Type: - return self.basic.tuple + return t.fallback def visit_union_type(self, t: UnionType) -> Type: return AnyType() # XXX: return underlying type if only one? diff --git a/mypy/types.py b/mypy/types.py index 34be0a4e7068..2d2f86fd795c 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -815,7 +815,6 @@ def __init__(self, object: Instance, type_type: Instance, tuple: Type, function: Type) -> None: self.object = object self.type_type = type_type - self.tuple = tuple self.function = function From 77b9d904ecd9dbede14c868b7244b802fcf46438 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 14:50:52 -0700 Subject: [PATCH 015/144] Add fallback type to callable types --- mypy/checker.py | 40 ++++++++++-------- mypy/checkexpr.py | 35 ++++++++-------- mypy/checkmember.py | 49 ++++++++++++----------- mypy/erasetype.py | 5 +-- mypy/expandtype.py | 4 +- mypy/join.py | 10 ++++- mypy/nodes.py | 32 +++++++-------- mypy/parse.py | 14 +++---- mypy/parsetype.py | 4 +- mypy/semanal.py | 8 +++- mypy/test/data/check-dynamic-typing.test | 2 + mypy/test/data/check-expressions.test | 16 +++----- mypy/test/data/check-generics.test | 1 + mypy/test/data/check-tuples.test | 1 + mypy/test/data/fixtures/bool.py | 1 + mypy/test/data/fixtures/classmethod.py | 2 + mypy/test/data/fixtures/dict.py | 1 + mypy/test/data/fixtures/exception.py | 1 + mypy/test/data/fixtures/for.py | 6 +-- mypy/test/data/fixtures/isinstance.py | 5 ++- mypy/test/data/fixtures/isinstancelist.py | 1 + mypy/test/data/fixtures/list.py | 2 +- mypy/test/data/fixtures/module.py | 1 + mypy/test/data/fixtures/ops.py | 1 + mypy/test/data/fixtures/primitives.py | 1 + mypy/test/data/fixtures/property.py | 2 + mypy/test/data/fixtures/python2.py | 2 + mypy/test/data/fixtures/set.py | 3 +- mypy/test/data/fixtures/slice.py | 3 +- mypy/test/data/fixtures/staticmethod.py | 2 + mypy/test/data/fixtures/tuple.py | 4 +- mypy/test/data/fixtures/union.py | 1 + mypy/test/data/lib-stub/builtins.py | 1 + mypy/test/data/typexport-basic.test | 2 + mypy/test/testtypes.py | 26 ++++++------ mypy/treetransform.py | 8 ++-- mypy/typeanal.py | 4 +- mypy/typefixture.py | 8 ++-- mypy/types.py | 36 +++++++++-------- 39 files changed, 197 insertions(+), 148 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b17d7318c218..b522f94f6b23 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -430,8 +430,8 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: for i, item in enumerate(defn.items): for j, item2 in enumerate(defn.items[i + 1:]): # TODO overloads involving decorators - sig1 = function_type(item.func) - sig2 = function_type(item2.func) + sig1 = self.function_type(item.func) + sig2 = self.function_type(item2.func) if is_unsafe_overlapping_signatures(sig1, sig2): self.msg.overloaded_signatures_overlap(i + 1, j + 2, item.func) @@ -443,8 +443,8 @@ def visit_func_def(self, defn: FuncDef) -> Type: self.check_method_override(defn) self.check_inplace_operator_method(defn) if defn.original_def: - if not is_same_type(function_type(defn), - function_type(defn.original_def)): + if not is_same_type(self.function_type(defn), + self.function_type(defn.original_def)): self.msg.incompatible_conditional_function_def(defn) def check_func_item(self, defn: FuncItem, @@ -466,7 +466,7 @@ def check_func_item(self, defn: FuncItem, if fdef: self.errors.push_function(fdef.name()) - typ = function_type(defn) + typ = self.function_type(defn) if type_override: typ = type_override if isinstance(typ, Callable): @@ -654,21 +654,21 @@ def check_overlapping_op_methods(self, # Construct normalized function signatures corresponding to the # operator methods. The first argument is the left operand and the - # second operatnd is the right argument -- we switch the order of + # second operand is the right argument -- we switch the order of # the arguments of the reverse method. forward_tweaked = Callable([forward_base, forward_type.arg_types[0]], [nodes.ARG_POS] * 2, [None] * 2, forward_type.ret_type, - is_type_obj=False, + forward_type.fallback, name=forward_type.name) reverse_args = reverse_type.arg_types reverse_tweaked = Callable([reverse_args[1], reverse_args[0]], [nodes.ARG_POS] * 2, [None] * 2, reverse_type.ret_type, - is_type_obj=False, + fallback=self.named_type('builtins.function'), name=reverse_type.name) if is_unsafe_overlapping_signatures(forward_tweaked, @@ -693,7 +693,7 @@ def check_inplace_operator_method(self, defn: FuncBase) -> None: method = defn.name() if method not in nodes.inplace_operator_methods: return - typ = method_type(defn) + typ = self.method_type(defn) cls = defn.info other_method = '__' + method[3:] if cls.has_readable_member(other_method): @@ -764,14 +764,14 @@ def check_method_override_for_base_with_name( # The name of the method is defined in the base class. # Construct the type of the overriding method. - typ = method_type(defn) + typ = self.method_type(defn) # Map the overridden method type to subtype context so that # it can be checked for compatibility. original_type = base_attr.type if original_type is None and isinstance(base_attr.node, FuncDef): - original_type = function_type(cast(FuncDef, - base_attr.node)) + original_type = self.function_type(cast(FuncDef, + base_attr.node)) if isinstance(original_type, FunctionLike): original = map_type_from_supertype( method_type(original_type), @@ -1508,7 +1508,7 @@ def visit_del_stmt(self, s: DelStmt) -> Type: def visit_decorator(self, e: Decorator) -> Type: e.func.accept(self) - sig = function_type(e.func) # type: Type + sig = self.function_type(e.func) # type: Type # Process decorators from the inside out. for i in range(len(e.decorators)): n = len(e.decorators) - 1 - i @@ -1516,6 +1516,7 @@ def visit_decorator(self, e: Decorator) -> Type: temp = self.temp_node(sig) sig, t2 = self.expr_checker.check_call(dec, [temp], [nodes.ARG_POS], e) + sig = cast(FunctionLike, sig) sig = set_callable_name(sig, e.func) e.var.type = sig e.var.is_ready = True @@ -1670,8 +1671,8 @@ def named_type(self, name: str) -> Instance: sym = self.lookup_qualified(name) return Instance(cast(TypeInfo, sym.node), []) - def named_type_if_exists(self, name: str) -> Type: - """Return named instance type, or UnboundType if the type was + def named_type_if_exists(self, name: str) -> Instance: + """Return named instance type, or None if the type was not defined. This is used to simplify test cases by avoiding the need to @@ -1683,7 +1684,8 @@ def named_type_if_exists(self, name: str) -> Type: sym = self.lookup_qualified(name) return Instance(cast(TypeInfo, sym.node), []) except KeyError: - return UnboundType(name) + # This is an unsafe cast; we use UnboundType to make debugging easier. + return Any(UnboundType(name)) def named_generic_type(self, name: str, args: List[Type]) -> Instance: """Return an instance with the given name and type arguments. @@ -1805,6 +1807,12 @@ def iterable_item_type(self, instance: Instance) -> Type: self.lookup_typeinfo('typing.Iterable')) return iterable.args[0] + def function_type(self, func: FuncBase) -> FunctionLike: + return function_type(func, self.named_type('builtins.function')) + + def method_type(self, func: FuncBase) -> FunctionLike: + return method_type(func, self.named_type('builtins.function')) + def map_type_from_supertype(typ: Type, sub_info: TypeInfo, super_info: TypeInfo) -> Type: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 49651b52121c..0864753092d9 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -67,15 +67,15 @@ def analyse_ref_expr(self, e: RefExpr) -> Type: result = self.analyse_var_ref(node, e) elif isinstance(node, FuncDef): # Reference to a global function. - result = function_type(node) + result = function_type(node, self.named_type('builtins.function')) elif isinstance(node, OverloadedFuncDef): result = node.type elif isinstance(node, TypeInfo): # Reference to a type object. - result = type_object_type(node, self.chk.type_type) + result = type_object_type(node, self.named_type) elif isinstance(node, MypyFile): # Reference to a module object. - result = self.chk.named_type('builtins.module') + result = self.named_type('builtins.module') elif isinstance(node, Decorator): result = self.analyse_var_ref(node.var, e) else: @@ -680,7 +680,7 @@ def apply_generic_arguments(self, callable: Callable, types: List[Type], callable.arg_kinds, callable.arg_names, expand_type(callable.ret_type, id_to_type), - callable.is_type_obj(), + callable.fallback, callable.name, remaining_tvars, callable.bound_vars + bound_vars, @@ -713,7 +713,7 @@ def analyse_ordinary_member_access(self, e: MemberExpr, # This is a reference to a non-module attribute. return analyse_member_access(e.name, self.accept(e.expr), e, is_lvalue, False, - self.chk.basic_types(), self.msg) + self.named_type, self.msg) def analyse_external_member_access(self, member: str, base_type: Type, context: Context) -> Type: @@ -722,7 +722,7 @@ def analyse_external_member_access(self, member: str, base_type: Type, """ # TODO remove; no private definitions in mypy return analyse_member_access(member, base_type, context, False, False, - self.chk.basic_types(), self.msg) + self.named_type, self.msg) def visit_int_expr(self, e: IntExpr) -> Type: """Type check an integer literal (trivial).""" @@ -786,8 +786,11 @@ def visit_comparison_expr(self, e: ComparisonExpr) -> Type: # is_valid_var_arg is True for any Iterable self.is_valid_var_arg(right_type)): itertype = self.chk.analyse_iterable_item_type(right) - method_type = Callable([left_type], [nodes.ARG_POS], [None], - self.chk.bool_type(), False) + method_type = Callable([left_type], + [nodes.ARG_POS], + [None], + self.chk.bool_type(), + self.named_type('builtins.function')) sub_result = self.chk.bool_type() if not is_subtype(left_type, itertype): self.msg.unsupported_operand_types('in', left_type, right_type, e) @@ -832,7 +835,7 @@ def check_op_local(self, method: str, base_type: Type, arg: Node, Return tuple (result type, inferred operator method type). """ method_type = analyse_member_access(method, base_type, context, False, False, - self.chk.basic_types(), local_errors) + self.named_type, local_errors) return self.check_call(method_type, [arg], [nodes.ARG_POS], context, arg_messages=local_errors) @@ -917,7 +920,7 @@ def check_list_multiply(self, e: OpExpr) -> Type: Type inference is special-cased for this common construct. """ right_type = self.accept(e.right) - if is_subtype(right_type, self.chk.named_type('builtins.int')): + if is_subtype(right_type, self.named_type('builtins.int')): # Special case: [...] * . Use the type context of the # OpExpr, since the multiplication does not affect the type. left_type = self.accept(e.left, context=self.chk.type_context[-1]) @@ -1044,7 +1047,7 @@ def check_list_or_set_expr(self, items: List[Node], fullname: str, [None], self.chk.named_generic_type(fullname, [tv]), - False, + self.named_type('builtins.function'), tag, [TypeVarDef('T', -1, None)]) return self.check_call(constructor, @@ -1085,7 +1088,7 @@ def visit_dict_expr(self, e: DictExpr) -> Type: [None], self.chk.named_generic_type('builtins.dict', [tv1, tv2]), - False, + self.named_type('builtins.function'), '', [TypeVarDef('KT', -1, None), TypeVarDef('VT', -2, None)]) @@ -1105,7 +1108,7 @@ def visit_func_expr(self, e: FuncExpr) -> Type: ret_type = e.expr().accept(self.chk) if not e.args: # Form 'lambda: e'; just use the inferred return type. - return Callable([], [], [], ret_type, is_type_obj=False) + return Callable([], [], [], ret_type, self.named_type('builtins.function')) else: # TODO: Consider reporting an error. However, this is fine if # we are just doing the first pass in contextual type @@ -1154,7 +1157,7 @@ def analyse_super(self, e: SuperExpr, is_lvalue: bool) -> Type: # TODO fix multiple inheritance etc return analyse_member_access(e.name, self_type(e.info), e, is_lvalue, True, - self.chk.basic_types(), self.msg, + self.named_type, self.msg, e.info.mro[1]) else: # Invalid super. This has been reported by the semantic analyser. @@ -1201,7 +1204,7 @@ def check_generator_or_comprehension(self, gen: GeneratorExpr, [nodes.ARG_POS], [None], self.chk.named_generic_type(type_name, [tv]), - False, + self.chk.named_type('builtins.function'), id_for_messages, [TypeVarDef('T', -1, None)]) return self.check_call(constructor, @@ -1396,7 +1399,7 @@ def replace_callable_return_type(c: Callable, new_ret_type: Type) -> Callable: c.arg_kinds, c.arg_names, new_ret_type, - c.is_type_obj(), + c.fallback, c.name, c.variables, c.bound_vars, diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 68ea626bbbd4..35317c7a9e0b 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -18,7 +18,8 @@ def analyse_member_access(name: str, typ: Type, node: Context, is_lvalue: bool, - is_super: bool, basic_types: BasicTypes, + is_super: bool, + builtin_type: Function[[str], Instance], msg: MessageBuilder, override_info: TypeInfo = None, report_type: Type = None) -> Type: """Analyse attribute access. @@ -49,7 +50,8 @@ def analyse_member_access(name: str, typ: Type, node: Context, is_lvalue: bool, if is_lvalue: msg.cant_assign_to_method(node) typ = map_instance_to_supertype(typ, method.info) - return expand_type_by_instance(method_type(method), typ) + return expand_type_by_instance( + method_type(method, builtin_type('builtins.function')), typ) else: # Not a method. return analyse_member_var_access(name, typ, info, node, @@ -62,33 +64,30 @@ def analyse_member_access(name: str, typ: Type, node: Context, is_lvalue: bool, # The base object has dynamic type. msg.disable_type_names += 1 results = [analyse_member_access(name, subtype, node, is_lvalue, - is_super, basic_types, msg) + is_super, builtin_type, msg) for subtype in typ.items] msg.disable_type_names -= 1 return UnionType.make_simplified_union(results) elif isinstance(typ, TupleType): # Actually look up from the fallback instance type. return analyse_member_access(name, typ.fallback, node, is_lvalue, - is_super, basic_types, msg) + is_super, builtin_type, msg) elif (isinstance(typ, FunctionLike) and cast(FunctionLike, typ).is_type_obj()): # Class attribute. # TODO super? sig = cast(FunctionLike, typ) itype = cast(Instance, sig.items()[0].ret_type) - result = analyse_class_attribute_access(itype, name, node, is_lvalue, - msg) + result = analyse_class_attribute_access(itype, name, node, is_lvalue, builtin_type, msg) if result: return result # Look up from the 'type' type. - return analyse_member_access(name, basic_types.type_type, node, - is_lvalue, is_super, basic_types, msg, - report_type=report_type) + return analyse_member_access(name, sig.fallback, node, is_lvalue, is_super, + builtin_type, msg, report_type=report_type) elif isinstance(typ, FunctionLike): # Look up from the 'function' type. - return analyse_member_access(name, basic_types.function, node, - is_lvalue, is_super, basic_types, msg, - report_type=report_type) + return analyse_member_access(name, typ.fallback, node, is_lvalue, is_super, + builtin_type, msg, report_type=report_type) return msg.has_no_attr(report_type, name, node) @@ -176,8 +175,11 @@ def check_method_type(functype: FunctionLike, itype: Instance, msg.invalid_method_type(item, context) -def analyse_class_attribute_access(itype: Instance, name: str, - context: Context, is_lvalue: bool, +def analyse_class_attribute_access(itype: Instance, + name: str, + context: Context, + is_lvalue: bool, + builtin_type: Function[[str], Instance], msg: MessageBuilder) -> Type: node = itype.type.get(name) if not node: @@ -197,10 +199,9 @@ def analyse_class_attribute_access(itype: Instance, name: str, return add_class_tvars(t, itype.type, is_classmethod) if isinstance(node.node, TypeInfo): - # TODO add second argument - return type_object_type(cast(TypeInfo, node.node), None) + return type_object_type(cast(TypeInfo, node.node), builtin_type) - return function_type(cast(FuncBase, node.node)) + return function_type(cast(FuncBase, node.node), builtin_type('builtins.function')) def add_class_tvars(t: Type, info: TypeInfo, is_classmethod: bool) -> Type: @@ -218,7 +219,7 @@ def add_class_tvars(t: Type, info: TypeInfo, is_classmethod: bool) -> Type: arg_kinds, arg_names, t.ret_type, - t.is_type_obj(), + t.fallback, t.name, vars + t.variables, t.bound_vars, @@ -229,7 +230,7 @@ def add_class_tvars(t: Type, info: TypeInfo, is_classmethod: bool) -> Type: return t -def type_object_type(info: TypeInfo, type_type: Function[[], Type]) -> Type: +def type_object_type(info: TypeInfo, builtin_type: Function[[str], Instance]) -> Type: """Return the type of a type object. For a generic type G with type variables T and S the type is of form @@ -245,18 +246,18 @@ def [T, S](...) -> G[T, S], else: # Construct callable type based on signature of __init__. Adjust # return type and insert type arguments. - init_type = method_type(init_method) + init_type = method_type(init_method, builtin_type('builtins.function')) if isinstance(init_type, Callable): - return class_callable(init_type, info) + return class_callable(init_type, info, builtin_type('builtins.type')) else: # Overloaded __init__. items = [] # type: List[Callable] for it in cast(Overloaded, init_type).items(): - items.append(class_callable(it, info)) + items.append(class_callable(it, info, builtin_type('builtins.type'))) return Overloaded(items) -def class_callable(init_type: Callable, info: TypeInfo) -> Callable: +def class_callable(init_type: Callable, info: TypeInfo, type_type: Instance) -> Callable: """Create a type object type based on the signature of __init__.""" variables = [] # type: List[TypeVarDef] for i, tvar in enumerate(info.defn.type_vars): @@ -269,7 +270,7 @@ def class_callable(init_type: Callable, info: TypeInfo) -> Callable: init_type.arg_kinds, init_type.arg_names, self_type(info), - True, + type_type, None, variables).with_name('"{}"'.format(info.name())) return convert_class_tvars_to_func_tvars(c, len(initvars)) diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 8a340ecf3fd7..dd457a306874 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -57,9 +57,8 @@ def visit_type_var(self, t: TypeVar) -> Type: return AnyType() def visit_callable(self, t: Callable) -> Type: - # We must preserve the type object flag for overload resolution to - # work. - return Callable([], [], [], Void(), t.is_type_obj()) + # We must preserve the fallback type for overload resolution to work. + return Callable([], [], [], Void(), t.fallback) def visit_overloaded(self, t: Overloaded) -> Type: return t.items()[0].accept(self) diff --git a/mypy/expandtype.py b/mypy/expandtype.py index c33866e3711f..c4f36f2132a3 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -80,7 +80,7 @@ def visit_callable(self, t: Callable) -> Type: t.arg_kinds, t.arg_names, t.ret_type.accept(self), - t.is_type_obj(), + t.fallback, t.name, t.variables, self.expand_bound_vars(t.bound_vars), t.line, t.repr) @@ -118,7 +118,7 @@ def update_callable_implicit_bounds( t.arg_kinds, t.arg_names, t.ret_type, - t.is_type_obj(), + t.fallback, t.name, t.variables, arg_types, t.line, t.repr) diff --git a/mypy/join.py b/mypy/join.py index 1184f1efcf90..f05a2ae9d73c 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -120,7 +120,7 @@ def visit_type_var(self, t: TypeVar) -> Type: def visit_instance(self, t: Instance) -> Type: if isinstance(self.s, Instance): return join_instances(t, cast(Instance, self.s), self.basic) - elif t.type == self.basic.type_type.type and is_subtype(self.s, t): + elif t.type.fullname() == 'builtins.type' and is_subtype(self.s, t): return t else: return self.default(self.s) @@ -228,11 +228,17 @@ def combine_similar_callables(t: Callable, s: Callable, for i in range(len(t.arg_types)): arg_types.append(join_types(t.arg_types[i], s.arg_types[i], basic)) # TODO kinds and argument names + # The fallback type can be either 'function' or 'type'. The result should have 'type' as + # fallback only if both operands have it as 'type'. + if t.fallback.type.fullname() != 'builtins.type': + fallback = t.fallback + else: + fallback = s.fallback return Callable(arg_types, t.arg_kinds, t.arg_names, join_types(t.ret_type, s.ret_type, basic), - t.is_type_obj() and s.is_type_obj(), + fallback, None, t.variables) return s diff --git a/mypy/nodes.py b/mypy/nodes.py index ecfea8adaa90..370c050f6b1a 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -184,7 +184,8 @@ def accept(self, visitor: NodeVisitor[T]) -> T: class FuncBase(SymbolNode): """Abstract base class for function-like nodes""" - # Type signature (Callable or Overloaded) + # Type signature. This is usually Callable or Overloaded, but it can be something else for + # decorated functions/ type = None # type: mypy.types.Type # If method, reference to TypeInfo info = None # type: TypeInfo @@ -244,7 +245,7 @@ class FuncItem(FuncBase): def __init__(self, args: List['Var'], arg_kinds: List[int], init: List[Node], body: 'Block', - typ: 'mypy.types.Type' = None) -> None: + typ: 'mypy.types.FunctionLike' = None) -> None: self.args = args self.arg_kinds = arg_kinds self.max_pos = arg_kinds.count(ARG_POS) + arg_kinds.count(ARG_OPT) @@ -313,7 +314,7 @@ def __init__(self, arg_kinds: List[int], # Arguments kinds (nodes.ARG_*) init: List[Node], # Initializers (each may be None) body: 'Block', - typ: 'mypy.types.Type' = None) -> None: + typ: 'mypy.types.FunctionLike' = None) -> None: super().__init__(args, arg_kinds, init, body, typ) self._name = name @@ -1016,7 +1017,7 @@ def accept(self, visitor: NodeVisitor[T]) -> T: class OpExpr(Node): - """Binary operation (other than . or [] or comparison operators, + """Binary operation (other than . or [] or comparison operators, which have specific nodes).""" op = '' @@ -1049,13 +1050,13 @@ def __init__(self, operators: List[str], operands: List[Node]) -> None: self.operands = operands self.method_types = [] self.literal = min(o.literal for o in self.operands) - self.literal_hash = ( ('Comparison',) + tuple(operators) + + self.literal_hash = ( ('Comparison',) + tuple(operators) + tuple(o.literal_hash for o in operands) ) def accept(self, visitor: NodeVisitor[T]) -> T: return visitor.visit_comparison_expr(self) - - + + class SliceExpr(Node): """Slice expression (e.g. 'x:y', 'x:', '::2' or ':'). @@ -1625,13 +1626,13 @@ def clean_up(s: str) -> str: return re.sub('.*::', '', s) -def function_type(func: FuncBase) -> 'mypy.types.FunctionLike': +def function_type(func: FuncBase, fallback: 'mypy.types.Instance') -> 'mypy.types.FunctionLike': if func.type: + assert isinstance(func.type, mypy.types.FunctionLike) return cast(mypy.types.FunctionLike, func.type) else: # Implicit type signature with dynamic types. - # Overloaded functions always have a signature, so func must be an - # ordinary function. + # Overloaded functions always have a signature, so func must be an ordinary function. fdef = cast(FuncDef, func) name = func.name() if name: @@ -1643,21 +1644,20 @@ def function_type(func: FuncBase) -> 'mypy.types.FunctionLike': fdef.arg_kinds, names, mypy.types.AnyType(), - False, + fallback, name) @overload -def method_type(func: FuncBase) -> 'mypy.types.FunctionLike': +def method_type(func: FuncBase, fallback: 'mypy.types.Instance') -> 'mypy.types.FunctionLike': """Return the signature of a method (omit self).""" - return method_type(function_type(func)) + return method_type(function_type(func, fallback)) @overload def method_type(sig: 'mypy.types.FunctionLike') -> 'mypy.types.FunctionLike': if isinstance(sig, mypy.types.Callable): - csig = cast(mypy.types.Callable, sig) - return method_callable(csig) + return method_callable(sig) else: osig = cast(mypy.types.Overloaded, sig) items = List[mypy.types.Callable]() @@ -1671,7 +1671,7 @@ def method_callable(c: 'mypy.types.Callable') -> 'mypy.types.Callable': c.arg_kinds[1:], c.arg_names[1:], c.ret_type, - c.is_type_obj(), + c.fallback, c.name, c.variables, c.bound_vars) diff --git a/mypy/parse.py b/mypy/parse.py index 0f69c9988e39..9f3829f7a001 100755 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -357,7 +357,7 @@ def parse_function(self) -> FuncDef: kinds, [arg.name() for arg in args], sig.ret_type, - False) + None) else: self.check_argument_kinds(kinds, sig.arg_kinds, def_tok.line) @@ -365,7 +365,7 @@ def parse_function(self) -> FuncDef: kinds, [arg.name() for arg in args], sig.ret_type, - False) + None) # If there was a serious error, we really cannot build a parse tree # node. @@ -400,7 +400,7 @@ def check_argument_kinds(self, funckinds: List[int], sigkinds: List[int], "signature".format(token), line) def parse_function_header(self) -> Tuple[str, List[Var], List[Node], - List[int], Type, bool, + List[int], Callable, bool, Tuple[Token, Any]]: """Parse function header (a name followed by arguments) @@ -432,7 +432,7 @@ def parse_function_header(self) -> Tuple[str, List[Var], List[Node], return (name, args, init, kinds, typ, False, (name_tok, arg_repr)) - def parse_args(self) -> Tuple[List[Var], List[Node], List[int], Type, + def parse_args(self) -> Tuple[List[Var], List[Node], List[int], Callable, noderepr.FuncArgsRepr]: """Parse a function signature (...) [-> t].""" lparen = self.expect('(') @@ -466,7 +466,7 @@ def parse_args(self) -> Tuple[List[Var], List[Node], List[int], Type, def build_func_annotation(self, ret_type: Type, arg_types: List[Type], kinds: List[int], names: List[str], - line: int, is_default_ret: bool = False) -> Type: + line: int, is_default_ret: bool = False) -> Callable: # Are there any type annotations? if ((ret_type and not is_default_ret) or arg_types != [None] * len(arg_types)): @@ -595,7 +595,7 @@ def construct_function_type(self, arg_types: List[Type], kinds: List[int], arg_types[i] = AnyType() if ret_type is None: ret_type = AnyType() - return Callable(arg_types, kinds, names, ret_type, False, None, + return Callable(arg_types, kinds, names, ret_type, None, None, None, [], line, None) # Parsing statements @@ -1489,7 +1489,7 @@ def parse_comparison_expr(self, left: Node, prec: int) -> ComparisonExpr: operators_str.append(op_str) operators.append( (op, op2) ) - operand = self.parse_expression(prec) + operand = self.parse_expression(prec) operands.append(operand) # Continue if next token is a comparison operator diff --git a/mypy/parsetype.py b/mypy/parsetype.py index 428665586018..b76ac13fcd3c 100644 --- a/mypy/parsetype.py +++ b/mypy/parsetype.py @@ -177,7 +177,7 @@ def parse_str_as_type(typestr: str, line: int) -> Type: def parse_signature(tokens: List[Token]) -> Tuple[Callable, int]: - """Parse signature of form (...) -> ... + """Parse signature of form (argtype, ...) -> ... Return tuple (signature type, token index). """ @@ -211,4 +211,4 @@ def parse_signature(tokens: List[Token]) -> Tuple[Callable, int]: return Callable(arg_types, arg_kinds, [None] * len(arg_types), - ret_type, False), i + ret_type, None), i diff --git a/mypy/semanal.py b/mypy/semanal.py index 27e5bbe9b3fa..d1a266d8a16b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -257,7 +257,8 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: item.is_overload = True item.func.is_overload = True item.accept(self) - t.append(cast(Callable, function_type(item.func))) + t.append(cast(Callable, function_type(item.func, + self.builtin_type('builtins.function')))) if not [dec for dec in item.decorators if refers_to_fullname(dec, 'typing.overload')]: self.fail("'overload' decorator expected", item) @@ -1387,6 +1388,11 @@ def lookup_qualified(self, name: str, ctx: Context) -> SymbolTableNode: n = self.normalize_type_alias(n, ctx) return n + def builtin_type(self, fully_qualified_name: str) -> Instance: + node = self.lookup_fully_qualified(fully_qualified_name) + info = cast(TypeInfo, node.node) + return Instance(info, []) + def lookup_fully_qualified(self, name: str) -> SymbolTableNode: """Lookup a fully qualified name. diff --git a/mypy/test/data/check-dynamic-typing.test b/mypy/test/data/check-dynamic-typing.test index 61c551aa8d5f..c474756b3b62 100644 --- a/mypy/test/data/check-dynamic-typing.test +++ b/mypy/test/data/check-dynamic-typing.test @@ -109,6 +109,7 @@ class object: class bool: pass class int: pass class type: pass +class function: pass [case testBinaryOperationsWithDynamicAsRightOperand] from typing import Undefined, Any @@ -164,6 +165,7 @@ class object: class bool: pass class int: pass class type: pass +class function: pass [case testDynamicWithUnaryExpressions] from typing import Undefined, Any diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index 5166784b2322..3ffb639f1db5 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -38,10 +38,6 @@ b = 1 a = 1 class A: pass -[file builtins.py] -class object: - def __init__(self): pass -class int: pass [out] main, line 4: Incompatible types in assignment (expression has type "int", variable has type "A") @@ -55,10 +51,6 @@ a = r"x" a = """foo""" class A: pass -[file builtins.py] -class object: - def __init__(self): pass -class str: pass [out] main, line 4: Incompatible types in assignment (expression has type "str", variable has type "A") @@ -72,7 +64,9 @@ class A: pass [file builtins.py] class object: - def __init__(self): pass + def __init__(self): pass +class type: pass +class function: pass class float: pass [out] main, line 4: Incompatible types in assignment (expression has type "float", variable has type "A") @@ -88,8 +82,10 @@ class A: pass [file builtins.py] class object: def __init__(self): pass -class bytes: pass +class type: pass class tuple: pass +class function: pass +class bytes: pass [case testUnicodeLiteralInPython3] from typing import Undefined diff --git a/mypy/test/data/check-generics.test b/mypy/test/data/check-generics.test index 6cd5f0430bf8..a7c1354340ec 100644 --- a/mypy/test/data/check-generics.test +++ b/mypy/test/data/check-generics.test @@ -637,6 +637,7 @@ class list(Iterable[T], Generic[T]): class int: pass class type: pass class tuple: pass +class function: pass [case testMultipleAsssignmentWithIterable] from typing import Iterable, Undefined, typevar diff --git a/mypy/test/data/check-tuples.test b/mypy/test/data/check-tuples.test index 12475816b3e1..edbe82cd9fe4 100644 --- a/mypy/test/data/check-tuples.test +++ b/mypy/test/data/check-tuples.test @@ -422,6 +422,7 @@ class int: pass class str: pass class bool: pass class type: pass +class function: pass -- For loop over tuple diff --git a/mypy/test/data/fixtures/bool.py b/mypy/test/data/fixtures/bool.py index 09098e221794..eac62168b813 100644 --- a/mypy/test/data/fixtures/bool.py +++ b/mypy/test/data/fixtures/bool.py @@ -8,5 +8,6 @@ def __init__(self) -> None: pass class type: pass class tuple: pass +class function: pass class bool: pass class int: pass diff --git a/mypy/test/data/fixtures/classmethod.py b/mypy/test/data/fixtures/classmethod.py index e0fdfcf55d8e..634e1e0c30e8 100644 --- a/mypy/test/data/fixtures/classmethod.py +++ b/mypy/test/data/fixtures/classmethod.py @@ -6,6 +6,8 @@ def __init__(self) -> None: pass class type: def __init__(self, x) -> None: pass +class function: pass + classmethod = object() # Dummy definition. class int: diff --git a/mypy/test/data/fixtures/dict.py b/mypy/test/data/fixtures/dict.py index c849383bf9a4..181d25182d06 100644 --- a/mypy/test/data/fixtures/dict.py +++ b/mypy/test/data/fixtures/dict.py @@ -18,3 +18,4 @@ def __iter__(self) -> Iterator[T]: pass def __mul__(self, x: int) -> list[T]: pass class tuple: pass +class function: pass diff --git a/mypy/test/data/fixtures/exception.py b/mypy/test/data/fixtures/exception.py index af92b3373635..8177156ccc4d 100644 --- a/mypy/test/data/fixtures/exception.py +++ b/mypy/test/data/fixtures/exception.py @@ -4,5 +4,6 @@ def __init__(self): pass class type: pass class tuple: pass +class function: pass class BaseException: pass diff --git a/mypy/test/data/fixtures/for.py b/mypy/test/data/fixtures/for.py index 2bd05824381c..c7de135b6023 100644 --- a/mypy/test/data/fixtures/for.py +++ b/mypy/test/data/fixtures/for.py @@ -7,13 +7,13 @@ class object: def __init__(self) -> None: pass - + class type: pass +class tuple: pass +class function: pass class bool: pass class int: pass # for convenience class str: pass # for convenience class list(Iterable[t], Generic[t]): def __iter__(self) -> Iterator[t]: pass - -class tuple: pass diff --git a/mypy/test/data/fixtures/isinstance.py b/mypy/test/data/fixtures/isinstance.py index 86ea733e1f45..293e8de2df6f 100644 --- a/mypy/test/data/fixtures/isinstance.py +++ b/mypy/test/data/fixtures/isinstance.py @@ -8,6 +8,9 @@ def __init__(self) -> None: pass class type: def __init__(self, x) -> None: pass +class tuple: pass +class function: pass + def isinstance(x: object, t: type) -> bool: pass @builtinclass @@ -16,5 +19,3 @@ class int: pass class bool(int): pass @builtinclass class str: pass - -class tuple: pass diff --git a/mypy/test/data/fixtures/isinstancelist.py b/mypy/test/data/fixtures/isinstancelist.py index 86b71f6a9b07..209fa195f2cf 100644 --- a/mypy/test/data/fixtures/isinstancelist.py +++ b/mypy/test/data/fixtures/isinstancelist.py @@ -9,6 +9,7 @@ class type: def __init__(self, x) -> None: pass class tuple: pass +class function: pass def isinstance(x: object, t: type) -> bool: pass diff --git a/mypy/test/data/fixtures/list.py b/mypy/test/data/fixtures/list.py index 228977e9fffc..8908dee19761 100644 --- a/mypy/test/data/fixtures/list.py +++ b/mypy/test/data/fixtures/list.py @@ -15,6 +15,6 @@ def __iter__(self) -> Iterator[T]: pass def __mul__(self, x: int) -> list[T]: pass class tuple: pass - +class function: pass class int: pass class str: pass diff --git a/mypy/test/data/fixtures/module.py b/mypy/test/data/fixtures/module.py index ab0f40428e1a..5b7614f226f2 100644 --- a/mypy/test/data/fixtures/module.py +++ b/mypy/test/data/fixtures/module.py @@ -2,3 +2,4 @@ class object: def __init__(self) -> None: pass class module: pass class type: pass +class function: pass diff --git a/mypy/test/data/fixtures/ops.py b/mypy/test/data/fixtures/ops.py index 46e7bd892f17..b527090f865e 100644 --- a/mypy/test/data/fixtures/ops.py +++ b/mypy/test/data/fixtures/ops.py @@ -10,6 +10,7 @@ def __ne__(self, o: 'object') -> 'bool': pass class type: pass class tuple: pass +class function: pass class bool: pass diff --git a/mypy/test/data/fixtures/primitives.py b/mypy/test/data/fixtures/primitives.py index 523f105fc539..e3600e55bd77 100644 --- a/mypy/test/data/fixtures/primitives.py +++ b/mypy/test/data/fixtures/primitives.py @@ -12,3 +12,4 @@ class float: pass class bool: pass class bytes: pass class tuple: pass +class function: pass diff --git a/mypy/test/data/fixtures/property.py b/mypy/test/data/fixtures/property.py index efde438a8049..98c0e176974f 100644 --- a/mypy/test/data/fixtures/property.py +++ b/mypy/test/data/fixtures/property.py @@ -6,6 +6,8 @@ def __init__(self) -> None: pass class type: def __init__(self, x) -> None: pass +class function: pass + property = object() # Dummy definition. class int: pass diff --git a/mypy/test/data/fixtures/python2.py b/mypy/test/data/fixtures/python2.py index bab7d204828b..76245b717e55 100644 --- a/mypy/test/data/fixtures/python2.py +++ b/mypy/test/data/fixtures/python2.py @@ -4,6 +4,8 @@ def __init__(self) -> None: pass class type: def __init__(self, x) -> None: pass +class function: pass + class int: pass class str: pass class unicode: pass diff --git a/mypy/test/data/fixtures/set.py b/mypy/test/data/fixtures/set.py index b56e74598cc0..7d970b59a271 100644 --- a/mypy/test/data/fixtures/set.py +++ b/mypy/test/data/fixtures/set.py @@ -8,9 +8,10 @@ class object: def __init__(self) -> None: pass class type: pass +class tuple: pass +class function: pass class int: pass class str: pass -class tuple: pass class set(Generic[T]): pass diff --git a/mypy/test/data/fixtures/slice.py b/mypy/test/data/fixtures/slice.py index 8b5b39130609..bd1076849714 100644 --- a/mypy/test/data/fixtures/slice.py +++ b/mypy/test/data/fixtures/slice.py @@ -4,8 +4,9 @@ class object: def __init__(self): pass class type: pass +class tuple: pass +class function: pass class int: pass -class tuple: pass class slice: pass diff --git a/mypy/test/data/fixtures/staticmethod.py b/mypy/test/data/fixtures/staticmethod.py index 0fc879e3c456..139aceedbacc 100644 --- a/mypy/test/data/fixtures/staticmethod.py +++ b/mypy/test/data/fixtures/staticmethod.py @@ -6,6 +6,8 @@ def __init__(self) -> None: pass class type: def __init__(self, x) -> None: pass +class function: pass + staticmethod = object() # Dummy definition. class int: diff --git a/mypy/test/data/fixtures/tuple.py b/mypy/test/data/fixtures/tuple.py index b860e6daec42..3051291381f7 100644 --- a/mypy/test/data/fixtures/tuple.py +++ b/mypy/test/data/fixtures/tuple.py @@ -6,10 +6,8 @@ class object: def __init__(self): pass class type: pass - -# Current tuple types get special treatment in the type checker, thus there -# is no need for type arguments here. class tuple: pass +class function: pass # We need int for indexing tuples. class int: pass diff --git a/mypy/test/data/fixtures/union.py b/mypy/test/data/fixtures/union.py index a1c6a0fefefd..5502082138e2 100644 --- a/mypy/test/data/fixtures/union.py +++ b/mypy/test/data/fixtures/union.py @@ -7,6 +7,7 @@ class object: def __init__(self): pass class type: pass +class function: pass # Current tuple types get special treatment in the type checker, thus there # is no need for type arguments here. diff --git a/mypy/test/data/lib-stub/builtins.py b/mypy/test/data/lib-stub/builtins.py index 54513409ec81..49b9743b9a5e 100644 --- a/mypy/test/data/lib-stub/builtins.py +++ b/mypy/test/data/lib-stub/builtins.py @@ -15,5 +15,6 @@ class int: pass class str: pass class tuple: pass +class function: pass # Definition of None is implicit diff --git a/mypy/test/data/typexport-basic.test b/mypy/test/data/typexport-basic.test index 80c00d3c765e..ccd624f7f3c6 100644 --- a/mypy/test/data/typexport-basic.test +++ b/mypy/test/data/typexport-basic.test @@ -107,6 +107,7 @@ a = 1 + 2 [file builtins.py] class object: def __init__(self) -> None: pass +class function: pass class int: def __add__(self, x: int) -> int: pass def __truediv__(self, x: int) -> float: pass @@ -140,6 +141,7 @@ class int: def __gt__(self, x: int) -> int: pass class bool: pass class type: pass +class function: pass [out] ComparisonExpr(3) : builtins.bool ComparisonExpr(4) : builtins.bool diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index 2d9d59a92f53..b757d79bb55c 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -24,6 +24,8 @@ def __init__(self): super().__init__() self.x = UnboundType('X') # Helpers self.y = UnboundType('Y') + self.fx = TypeFixture() + self.function = self.fx.std_function def test_any(self): assert_equal(str(AnyType()), 'Any') @@ -43,7 +45,7 @@ def test_callable_type(self): c = Callable([self.x, self.y], [ARG_POS, ARG_POS], [None, None], - AnyType(), False) + AnyType(), self.function) assert_equal(str(c), 'def (X?, Y?) -> Any') c2 = Callable([], [], [], Void(None), False) @@ -51,23 +53,23 @@ def test_callable_type(self): def test_callable_type_with_default_args(self): c = Callable([self.x, self.y], [ARG_POS, ARG_OPT], [None, None], - AnyType(), False) + AnyType(), self.function) assert_equal(str(c), 'def (X?, Y? =) -> Any') c2 = Callable([self.x, self.y], [ARG_OPT, ARG_OPT], [None, None], - AnyType(), False) + AnyType(), self.function) assert_equal(str(c2), 'def (X? =, Y? =) -> Any') def test_callable_type_with_var_args(self): - c = Callable([self.x], [ARG_STAR], [None], AnyType(), False) + c = Callable([self.x], [ARG_STAR], [None], AnyType(), self.function) assert_equal(str(c), 'def (*X?) -> Any') c2 = Callable([self.x, self.y], [ARG_POS, ARG_STAR], - [None, None], AnyType(), False) + [None, None], AnyType(), self.function) assert_equal(str(c2), 'def (X?, *Y?) -> Any') c3 = Callable([self.x, self.y], [ARG_OPT, ARG_STAR], [None, None], - AnyType(), False) + AnyType(), self.function) assert_equal(str(c3), 'def (X? =, *Y?) -> Any') def test_tuple_type(self): @@ -82,12 +84,12 @@ def test_type_variable_binding(self): def test_generic_function_type(self): c = Callable([self.x, self.y], [ARG_POS, ARG_POS], [None, None], - self.y, False, None, + self.y, self.function, None, [TypeVarDef('X', -1, None)]) assert_equal(str(c), 'def [X] (X?, Y?) -> Y?') v = [TypeVarDef('Y', -1, None), TypeVarDef('X', -2, None)] - c2 = Callable([], [], [], Void(None), False, None, v) + c2 = Callable([], [], [], Void(None), self.function, None, v) assert_equal(str(c2), 'def [Y, X] ()') @@ -238,7 +240,7 @@ def callable(self, vars, *a) -> Callable: [ARG_POS] * (len(a) - 1), [None] * (len(a) - 1), a[-1], - False, + self.fx.std_function, None, tv) @@ -462,7 +464,7 @@ def callable(self, *a): """ n = len(a) - 1 return Callable(a[:-1], [ARG_POS] * n, [None] * n, - a[-1], False) + a[-1], self.fx.std_function) def type_callable(self, *a): """type_callable(a1, ..., an, r) constructs a callable with @@ -471,7 +473,7 @@ def type_callable(self, *a): """ n = len(a) - 1 return Callable(a[:-1], [ARG_POS] * n, [None] * n, - a[-1], True) + a[-1], self.fx.type_type) class MeetSuite(Suite): @@ -667,7 +669,7 @@ def callable(self, *a): n = len(a) - 1 return Callable(a[:-1], [ARG_POS] * n, [None] * n, - a[-1], False) + a[-1], self.fx.std_function) class CombinedTypesSuite(Suite): diff --git a/mypy/treetransform.py b/mypy/treetransform.py index b16b6d2e7ca6..66d7bb6f4cb5 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -3,7 +3,7 @@ Subclass TransformVisitor to perform non-trivial transformations. """ -from typing import List, Dict +from typing import List, Dict, cast from mypy.nodes import ( MypyFile, Import, Node, ImportAll, ImportFrom, FuncItem, FuncDef, @@ -19,7 +19,7 @@ DisjointclassExpr, CoerceExpr, TypeExpr, ComparisonExpr, JavaCast, TempNode ) -from mypy.types import Type +from mypy.types import Type, FunctionLike from mypy.visitor import NodeVisitor @@ -72,7 +72,7 @@ def visit_func_def(self, node: FuncDef) -> FuncDef: node.arg_kinds[:], [None] * len(node.init), self.block(node.body), - self.optional_type(node.type)) + cast(FunctionLike, self.optional_type(node.type))) self.copy_function_attributes(new, node) @@ -91,7 +91,7 @@ def visit_func_expr(self, node: FuncExpr) -> Node: node.arg_kinds[:], [None] * len(node.init), self.block(node.body), - self.optional_type(node.type)) + cast(FunctionLike, self.optional_type(node.type))) self.copy_function_attributes(new, node) return new diff --git a/mypy/typeanal.py b/mypy/typeanal.py index a1f7b5ab9e9f..d94cfd943583 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -144,7 +144,7 @@ def visit_callable(self, t: Callable) -> Type: t.arg_kinds, t.arg_names, t.ret_type.accept(self), - t.is_type_obj(), + self.builtin_type('builtins.function'), t.name, self.anal_var_defs(t.variables), self.anal_bound_vars(t.bound_vars), t.line, t.repr) @@ -169,7 +169,7 @@ def analyze_function_type(self, t: UnboundType) -> Type: return Callable(self.anal_array(args), [nodes.ARG_POS] * len(args), [None] * len(args), ret_type=t.args[1].accept(self), - is_type_obj=False) + fallback=self.builtin_type('builtins.function')) def anal_array(self, a: List[Type]) -> List[Type]: res = List[Type]() diff --git a/mypy/typefixture.py b/mypy/typefixture.py index 1733230cad49..87e4a907bf50 100644 --- a/mypy/typefixture.py +++ b/mypy/typefixture.py @@ -145,7 +145,7 @@ def callable(self, *a): a1, ... an and return type r. """ return Callable(a[:-1], [ARG_POS] * (len(a) - 1), - [None] * (len(a) - 1), a[-1], False) + [None] * (len(a) - 1), a[-1], self.std_function) def callable_type(self, *a): """callable_type(a1, ..., an, r) constructs a callable with @@ -153,7 +153,7 @@ def callable_type(self, *a): represents a type. """ return Callable(a[:-1], [ARG_POS] * (len(a) - 1), - [None] * (len(a) - 1), a[-1], True) + [None] * (len(a) - 1), a[-1], self.type_type) def callable_default(self, min_args, *a): """callable_default(min_args, a1, ..., an, r) constructs a @@ -164,7 +164,7 @@ def callable_default(self, min_args, *a): return Callable(a[:-1], [ARG_POS] * min_args + [ARG_OPT] * (n - min_args), [None] * n, - a[-1], False) + a[-1], self.std_function) def callable_var_arg(self, min_args, *a): """callable_var_arg(min_args, a1, ..., an, r) constructs a callable @@ -175,7 +175,7 @@ def callable_var_arg(self, min_args, *a): [ARG_POS] * min_args + [ARG_OPT] * (n - 1 - min_args) + [ARG_STAR], [None] * n, - a[-1], False) + a[-1], self.std_function) class InterfaceTypeFixture(TypeFixture): diff --git a/mypy/types.py b/mypy/types.py index 2d2f86fd795c..530afffe5637 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -229,7 +229,10 @@ def type_object(self) -> mypy.nodes.TypeInfo: pass def items(self) -> List['Callable']: pass @abstractmethod - def with_name(self, name: str) -> Type: pass + def with_name(self, name: str) -> 'FunctionLike': pass + + # Corresponding instance type (e.g. builtins.type) + fallback = Undefined(Instance) class Callable(FunctionLike): @@ -238,10 +241,10 @@ class Callable(FunctionLike): arg_types = Undefined(List[Type]) # Types of function arguments arg_kinds = Undefined(List[int]) # mypy.nodes.ARG_ constants arg_names = Undefined(List[str]) # None if not a keyword argument - min_args = 0 # Minimum number of arguments - is_var_arg = False # Is it a varargs function? - ret_type = Undefined(Type) # Return value type - name = '' # Name (may be None; for error messages) + min_args = 0 # Minimum number of arguments + is_var_arg = False # Is it a varargs function? + ret_type = Undefined(Type) # Return value type + name = '' # Name (may be None; for error messages) # Type variables for a generic function variables = Undefined(List[TypeVarDef]) @@ -266,7 +269,7 @@ def __init__(self, arg_types: List[Type], arg_kinds: List[int], arg_names: List[str], ret_type: Type, - is_type_obj: bool, + fallback: Instance, name: str = None, variables: List[TypeVarDef] = None, bound_vars: List[Tuple[int, Type]] = None, line: int = -1, repr: Any = None) -> None: @@ -280,7 +283,7 @@ def __init__(self, arg_types: List[Type], self.min_args = arg_kinds.count(mypy.nodes.ARG_POS) self.is_var_arg = mypy.nodes.ARG_STAR in arg_kinds self.ret_type = ret_type - self._is_type_obj = is_type_obj + self.fallback = fallback assert not name or ' bool: - return self._is_type_obj + return self.fallback.type.fullname() == 'builtins.type' def type_object(self) -> mypy.nodes.TypeInfo: - assert self._is_type_obj - return (cast(Instance, self.ret_type)).type + assert self.is_type_obj() + return cast(Instance, self.ret_type).type def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_callable(self) @@ -306,7 +309,7 @@ def with_name(self, name: str) -> 'Callable': self.arg_kinds, self.arg_names, ret, - self.is_type_obj(), + self.fallback, name, self.variables, self.bound_vars, @@ -342,6 +345,7 @@ class Overloaded(FunctionLike): def __init__(self, items: List[Callable]) -> None: self._items = items + self.fallback = items[0].fallback super().__init__(items[0].line, None) def items(self) -> List[Callable]: @@ -564,7 +568,7 @@ def visit_callable(self, t: Callable) -> Type: t.arg_kinds, t.arg_names, t.ret_type.accept(self), - t.is_type_obj(), + t.fallback, t.name, self.translate_variables(t.variables), self.translate_bound_vars(t.bound_vars), @@ -811,8 +815,8 @@ def query_types(self, types: List[Type]) -> bool: class BasicTypes: """Collection of Instance types of basic types (object, type, etc.).""" - def __init__(self, object: Instance, type_type: Instance, tuple: Type, - function: Type) -> None: + def __init__(self, object: Instance, type_type: Instance, tuple: Instance, + function: Instance) -> None: self.object = object self.type_type = type_type self.function = function @@ -826,7 +830,7 @@ def strip_type(typ: Type) -> Type: typ.arg_kinds, typ.arg_names, typ.ret_type, - typ.is_type_obj(), + typ.fallback, None, typ.variables) elif isinstance(typ, Overloaded): @@ -845,7 +849,7 @@ def replace_leading_arg_type(t: Callable, self_type: Type) -> Callable: t.arg_kinds, t.arg_names, t.ret_type, - t.is_type_obj(), + t.fallback, t.name, t.variables, t.bound_vars, From b1c1df09f75a80fdcf015d055b3b6e2ff5e3dee3 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 14:55:49 -0700 Subject: [PATCH 016/144] Refactoring --- mypy/checker.py | 27 +-------------------------- mypy/join.py | 11 +++++------ mypy/typefixture.py | 3 +-- mypy/types.py | 5 +---- 4 files changed, 8 insertions(+), 38 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b522f94f6b23..d1de060b674f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1671,22 +1671,6 @@ def named_type(self, name: str) -> Instance: sym = self.lookup_qualified(name) return Instance(cast(TypeInfo, sym.node), []) - def named_type_if_exists(self, name: str) -> Instance: - """Return named instance type, or None if the type was - not defined. - - This is used to simplify test cases by avoiding the need to - define basic types not needed in specific test cases (tuple - etc.). - """ - try: - # Assume that the name refers to a type. - sym = self.lookup_qualified(name) - return Instance(cast(TypeInfo, sym.node), []) - except KeyError: - # This is an unsafe cast; we use UnboundType to make debugging easier. - return Any(UnboundType(name)) - def named_generic_type(self, name: str, args: List[Type]) -> Instance: """Return an instance with the given name and type arguments. @@ -1716,13 +1700,6 @@ def str_type(self) -> Instance: """Return instance type 'str'.""" return self.named_type('builtins.str') - def tuple_type(self) -> Type: - """Return instance type 'tuple'.""" - # We need the tuple for analysing member access. We want to be able to - # do this even if tuple type is not available (useful in test cases), - # so we return an unbound type if there is no tuple type. - return self.named_type_if_exists('builtins.tuple') - def check_type_equivalency(self, t1: Type, t2: Type, node: Context, msg: str = messages.INCOMPATIBLE_TYPES) -> None: """Generate an error if the types are not equivalent. The @@ -1774,9 +1751,7 @@ def basic_types(self) -> BasicTypes: """Return a BasicTypes instance that contains primitive types that are needed for certain type operations (joins, for example). """ - return BasicTypes(self.object_type(), self.named_type('builtins.type'), - self.named_type_if_exists('builtins.tuple'), - self.named_type_if_exists('builtins.function')) + return BasicTypes(self.object_type()) def is_within_function(self) -> bool: """Are we currently type checking within a function? diff --git a/mypy/join.py b/mypy/join.py index f05a2ae9d73c..33f9c5e68335 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -130,12 +130,11 @@ def visit_callable(self, t: Callable) -> Type: t, cast(Callable, self.s)): return combine_similar_callables(t, cast(Callable, self.s), self.basic) - elif t.is_type_obj() and is_subtype(self.s, self.basic.type_type): - return self.basic.type_type - elif (isinstance(self.s, Instance) and - cast(Instance, self.s).type == self.basic.type_type.type and - t.is_type_obj()): - return self.basic.type_type + elif t.is_type_obj() and is_subtype(self.s, t.fallback): + return t.fallback + elif (t.is_type_obj() and isinstance(self.s, Instance) and + cast(Instance, self.s).type == t.fallback): + return t.fallback else: return self.default(self.s) diff --git a/mypy/typefixture.py b/mypy/typefixture.py index 87e4a907bf50..4b1e707bcdb3 100644 --- a/mypy/typefixture.py +++ b/mypy/typefixture.py @@ -135,8 +135,7 @@ def __init__(self): self.lstb = Instance(self.std_listi, [self.b]) # List[B] # Basic types - self.basic = BasicTypes(self.o, self.type_type, self.std_tuple, - self.std_function) + self.basic = BasicTypes(self.o) # Helper methods diff --git a/mypy/types.py b/mypy/types.py index 530afffe5637..739e63568424 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -815,11 +815,8 @@ def query_types(self, types: List[Type]) -> bool: class BasicTypes: """Collection of Instance types of basic types (object, type, etc.).""" - def __init__(self, object: Instance, type_type: Instance, tuple: Instance, - function: Instance) -> None: + def __init__(self, object: Instance) -> None: self.object = object - self.type_type = type_type - self.function = function def strip_type(typ: Type) -> Type: From 3706079f3aad5adc65e143113c1361ccf3806223 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 16:11:45 -0700 Subject: [PATCH 017/144] Add upper bound type to type variables --- mypy/checkexpr.py | 16 ++--- mypy/checkmember.py | 16 +++-- mypy/join.py | 24 +++++-- mypy/nodes.py | 10 +-- mypy/semanal.py | 14 ++-- mypy/test/testtypes.py | 11 +-- mypy/typeanal.py | 4 +- mypy/typefixture.py | 154 ++++++++++++++++++++--------------------- mypy/types.py | 12 ++-- 9 files changed, 140 insertions(+), 121 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0864753092d9..4c8fa9dea145 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1041,7 +1041,7 @@ def visit_set_expr(self, e: SetExpr) -> Type: def check_list_or_set_expr(self, items: List[Node], fullname: str, tag: str, context: Context) -> Type: # Translate into type checking a generic function call. - tv = TypeVar('T', -1, []) + tv = TypeVar('T', -1, [], self.chk.object_type()) constructor = Callable([tv], [nodes.ARG_STAR], [None], @@ -1049,7 +1049,7 @@ def check_list_or_set_expr(self, items: List[Node], fullname: str, [tv]), self.named_type('builtins.function'), tag, - [TypeVarDef('T', -1, None)]) + [TypeVarDef('T', -1, None, self.chk.object_type())]) return self.check_call(constructor, items, [nodes.ARG_POS] * len(items), context)[0] @@ -1077,8 +1077,8 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type: def visit_dict_expr(self, e: DictExpr) -> Type: # Translate into type checking a generic function call. - tv1 = TypeVar('KT', -1, []) - tv2 = TypeVar('VT', -2, []) + tv1 = TypeVar('KT', -1, [], self.chk.object_type()) + tv2 = TypeVar('VT', -2, [], self.chk.object_type()) constructor = Undefined(Callable) # The callable type represents a function like this: # @@ -1090,8 +1090,8 @@ def visit_dict_expr(self, e: DictExpr) -> Type: [tv1, tv2]), self.named_type('builtins.function'), '', - [TypeVarDef('KT', -1, None), - TypeVarDef('VT', -2, None)]) + [TypeVarDef('KT', -1, None, self.chk.object_type()), + TypeVarDef('VT', -2, None, self.chk.object_type())]) # Synthesize function arguments. args = List[Node]() for key, value in e.items: @@ -1199,14 +1199,14 @@ def check_generator_or_comprehension(self, gen: GeneratorExpr, # Infer the type of the list comprehension by using a synthetic generic # callable type. - tv = TypeVar('T', -1, []) + tv = TypeVar('T', -1, [], self.chk.object_type()) constructor = Callable([tv], [nodes.ARG_POS], [None], self.chk.named_generic_type(type_name, [tv]), self.chk.named_type('builtins.function'), id_for_messages, - [TypeVarDef('T', -1, None)]) + [TypeVarDef('T', -1, None, self.chk.object_type())]) return self.check_call(constructor, [gen.left_expr], [nodes.ARG_POS], gen)[0] diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 35317c7a9e0b..85508aea5196 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -196,7 +196,7 @@ def analyse_class_attribute_access(itype: Instance, t = node.type if t: is_classmethod = is_decorated and cast(Decorator, node.node).func.is_class - return add_class_tvars(t, itype.type, is_classmethod) + return add_class_tvars(t, itype.type, is_classmethod, builtin_type) if isinstance(node.node, TypeInfo): return type_object_type(cast(TypeInfo, node.node), builtin_type) @@ -204,9 +204,11 @@ def analyse_class_attribute_access(itype: Instance, return function_type(cast(FuncBase, node.node), builtin_type('builtins.function')) -def add_class_tvars(t: Type, info: TypeInfo, is_classmethod: bool) -> Type: +def add_class_tvars(t: Type, info: TypeInfo, is_classmethod: bool, + builtin_type: Function[[str], Instance]) -> Type: if isinstance(t, Callable): - vars = [TypeVarDef(n, i + 1, None) + # TODO: Should we propagate type variable values? + vars = [TypeVarDef(n, i + 1, None, builtin_type('builtins.object')) for i, n in enumerate(info.type_vars)] arg_types = t.arg_types arg_kinds = t.arg_kinds @@ -225,7 +227,7 @@ def add_class_tvars(t: Type, info: TypeInfo, is_classmethod: bool) -> Type: t.bound_vars, t.line, None) elif isinstance(t, Overloaded): - return Overloaded([cast(Callable, add_class_tvars(i, info, is_classmethod)) + return Overloaded([cast(Callable, add_class_tvars(i, info, is_classmethod, builtin_type)) for i in t.items()]) return t @@ -261,7 +263,7 @@ def class_callable(init_type: Callable, info: TypeInfo, type_type: Instance) -> """Create a type object type based on the signature of __init__.""" variables = [] # type: List[TypeVarDef] for i, tvar in enumerate(info.defn.type_vars): - variables.append(TypeVarDef(tvar.name, i + 1, tvar.values)) + variables.append(TypeVarDef(tvar.name, i + 1, tvar.values, tvar.upper_bound)) initvars = init_type.variables variables.extend(initvars) @@ -290,7 +292,7 @@ def visit_type_var(self, t: TypeVar) -> Type: if t.id < 0: return t else: - return TypeVar(t.name, -t.id - self.num_func_tvars, t.values) + return TypeVar(t.name, -t.id - self.num_func_tvars, t.values, t.upper_bound) def translate_variables(self, variables: List[TypeVarDef]) -> List[TypeVarDef]: @@ -300,7 +302,7 @@ def translate_variables(self, for v in variables: if v.id > 0: items.append(TypeVarDef(v.name, -v.id - self.num_func_tvars, - v.values)) + v.values, v.upper_bound)) else: items.append(v) return items diff --git a/mypy/join.py b/mypy/join.py index 33f9c5e68335..2400f55319fe 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -5,7 +5,7 @@ from mypy.types import ( Type, AnyType, NoneTyp, Void, TypeVisitor, Instance, UnboundType, ErrorType, TypeVar, Callable, TupleType, ErasedType, BasicTypes, TypeList, - UnionType + UnionType, FunctionLike ) from mypy.subtypes import is_subtype, is_equivalent, map_instance_to_supertype @@ -73,7 +73,6 @@ class TypeJoinVisitor(TypeVisitor[Type]): def __init__(self, s: Type, basic: BasicTypes) -> None: self.s = s self.basic = basic - self.object = basic.object def visit_unbound_type(self, t: UnboundType) -> Type: if isinstance(self.s, Void) or isinstance(self.s, ErrorType): @@ -154,12 +153,20 @@ def join(self, s: Type, t: Type) -> Type: return join_types(s, t, self.basic) def default(self, typ: Type) -> Type: - if isinstance(typ, UnboundType): + if isinstance(typ, Instance): + return object_from_instance(typ) + elif isinstance(typ, UnboundType): return AnyType() elif isinstance(typ, Void) or isinstance(typ, ErrorType): return ErrorType() + elif isinstance(typ, TupleType): + return self.default(typ.fallback) + elif isinstance(typ, FunctionLike): + return self.default(typ.fallback) + elif isinstance(typ, TypeVar): + return self.default(typ.upper_bound) else: - return self.object + return AnyType() def join_instances(t: Instance, s: Instance, basic: BasicTypes) -> Type: @@ -182,7 +189,7 @@ def join_instances(t: Instance, s: Instance, basic: BasicTypes) -> Type: return Instance(t.type, args) else: # Incompatible; return trivial result object. - return basic.object + return object_from_instance(t) elif t.type.bases and is_subtype(t, s): return join_instances_via_supertype(t, s, basic) else: @@ -241,3 +248,10 @@ def combine_similar_callables(t: Callable, s: Callable, None, t.variables) return s + + +def object_from_instance(instance: Instance) -> Instance: + """Construct the type 'builtins.object' from an instance type.""" + # Use the fact that 'object' is always the last class in the mro. + res = Instance(instance.type.mro[-1], []) + return res diff --git a/mypy/nodes.py b/mypy/nodes.py index 370c050f6b1a..e3b6f2d1bc4b 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1386,14 +1386,14 @@ class TypeInfo(SymbolNode): the class. """ - _fullname = None # type: str # Fully qualified name - defn = Undefined(ClassDef) # Corresponding ClassDef + _fullname = None # type: str # Fully qualified name + defn = Undefined(ClassDef) # Corresponding ClassDef # Method Resolution Order: the order of looking up attributes. The first - # value always to refers to self. + # value always to refers to this class. mro = Undefined(List['TypeInfo']) - subtypes = Undefined(Set['TypeInfo']) # Direct subclasses + subtypes = Undefined(Set['TypeInfo']) # Direct subclasses encountered so far names = Undefined('SymbolTable') # Names defined directly in this type - is_abstract = False # Does the class have any abstract attributes? + is_abstract = False # Does the class have any abstract attributes? abstract_attributes = Undefined(List[str]) # All classes in this build unit that are disjoint with this class. disjoint_classes = Undefined(List['TypeInfo']) diff --git a/mypy/semanal.py b/mypy/semanal.py index d1a266d8a16b..c354cfc823cf 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -208,7 +208,7 @@ def update_function_type_variables(self, defn: FuncDef) -> None: typevars = [(tvar, values) for tvar, values in typevars if not self.is_defined_type_var(tvar, defn)] if typevars: - defs = [TypeVarDef(tvar[0], -i - 1, tvar[1]) + defs = [TypeVarDef(tvar[0], -i - 1, tvar[1], self.object_type()) for i, tvar in enumerate(typevars)] functype.variables = defs @@ -419,10 +419,10 @@ def clean_up_bases_and_infer_type_variables(self, defn: ClassDef) -> None: For example, consider this class: - . class Foo(Bar, Generic[t]): ... + class Foo(Bar, Generic[T]): ... - Now we will remove Generic[t] from bases of Foo and infer that the - type variable 't' is a type argument of Foo. + Now we will remove Generic[T] from bases of Foo and infer that the + type variable 'T' is a type argument of Foo. """ removed = List[int]() type_vars = List[TypeVarDef]() @@ -435,7 +435,8 @@ def clean_up_bases_and_infer_type_variables(self, defn: ClassDef) -> None: removed.append(i) for j, tvar in enumerate(tvars): name, values = tvar - type_vars.append(TypeVarDef(name, j + 1, values)) + type_vars.append(TypeVarDef(name, j + 1, values, + self.object_type())) if type_vars: defn.type_vars = type_vars if defn.info: @@ -1658,7 +1659,8 @@ def self_type(typ: TypeInfo) -> Instance: tv = List[Type]() for i in range(len(typ.type_vars)): tv.append(TypeVar(typ.type_vars[i], i + 1, - typ.defn.type_vars[i].values)) + typ.defn.type_vars[i].values, + typ.defn.type_vars[i].upper_bound)) return Instance(typ, tv) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index b757d79bb55c..b4b26cc2dbb3 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -78,17 +78,18 @@ def test_tuple_type(self): assert_equal(str(TupleType([self.x, AnyType()], None)), 'Tuple[X?, Any]') def test_type_variable_binding(self): - assert_equal(str(TypeVarDef('X', 1, None)), 'X') - assert_equal(str(TypeVarDef('X', 1, [self.x, self.y])), + assert_equal(str(TypeVarDef('X', 1, None, self.fx.o)), 'X') + assert_equal(str(TypeVarDef('X', 1, [self.x, self.y], self.fx.o)), 'X in (X?, Y?)') def test_generic_function_type(self): c = Callable([self.x, self.y], [ARG_POS, ARG_POS], [None, None], self.y, self.function, None, - [TypeVarDef('X', -1, None)]) + [TypeVarDef('X', -1, None, self.fx.o)]) assert_equal(str(c), 'def [X] (X?, Y?) -> Y?') - v = [TypeVarDef('Y', -1, None), TypeVarDef('X', -2, None)] + v = [TypeVarDef('Y', -1, None, self.fx.o), + TypeVarDef('X', -2, None, self.fx.o)] c2 = Callable([], [], [], Void(None), self.function, None, v) assert_equal(str(c2), 'def [Y, X] ()') @@ -234,7 +235,7 @@ def callable(self, vars, *a) -> Callable: tv = [] # type: List[TypeVarDef] n = -1 for v in vars: - tv.append(TypeVarDef(v, n, None)) + tv.append(TypeVarDef(v, n, None, self.fx.o)) n -= 1 return Callable(a[:-1], [ARG_POS] * (len(a) - 1), diff --git a/mypy/typeanal.py b/mypy/typeanal.py index d94cfd943583..9834da2240bb 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -86,7 +86,8 @@ def visit_unbound_type(self, t: UnboundType) -> Type: else: rep = None values = cast(TypeVarExpr, sym.node).values - return TypeVar(t.name, sym.tvar_id, values, False, t.line, rep) + return TypeVar(t.name, sym.tvar_id, values, self.builtin_type('builtins.object'), + False, t.line, rep) elif sym.node.fullname() == 'builtins.None': return Void() elif sym.node.fullname() == 'typing.Any': @@ -188,6 +189,7 @@ def anal_var_defs(self, var_defs: List[TypeVarDef]) -> List[TypeVarDef]: a = List[TypeVarDef]() for vd in var_defs: a.append(TypeVarDef(vd.name, vd.id, self.anal_array(vd.values), + vd.upper_bound.accept(self), vd.line, vd.repr)) return a diff --git a/mypy/typefixture.py b/mypy/typefixture.py index 4b1e707bcdb3..9356640a19dc 100644 --- a/mypy/typefixture.py +++ b/mypy/typefixture.py @@ -21,18 +21,20 @@ class TypeFixture: """ def __init__(self): - # Type variables + # The 'object' class + self.oi = self.make_type_info('builtins.object') # class object + self.o = Instance(self.oi, []) # object - self.t = TypeVar('T', 1, []) # T`1 (type variable) - self.tf = TypeVar('T', -1, []) # T`-1 (type variable) - self.tf2 = TypeVar('T', -2, []) # T`-2 (type variable) - self.s = TypeVar('S', 2, []) # S`2 (type variable) - self.s1 = TypeVar('S', 1, []) # S`1 (type variable) - self.sf = TypeVar('S', -2, []) # S`-2 (type variable) - self.sf1 = TypeVar('S', -1, []) # S`-1 (type variable) + # Type variables + self.t = TypeVar('T', 1, [], self.o) # T`1 (type variable) + self.tf = TypeVar('T', -1, [], self.o) # T`-1 (type variable) + self.tf2 = TypeVar('T', -2, [], self.o) # T`-2 (type variable) + self.s = TypeVar('S', 2, [], self.o) # S`2 (type variable) + self.s1 = TypeVar('S', 1, [], self.o) # S`1 (type variable) + self.sf = TypeVar('S', -2, [], self.o) # S`-2 (type variable) + self.sf1 = TypeVar('S', -1, [], self.o) # S`-1 (type variable) # Simple types - self.anyt = AnyType() self.void = Void() self.err = ErrorType() @@ -41,57 +43,49 @@ def __init__(self): # Abstract class TypeInfos # class F - self.fi = make_type_info('F', is_abstract=True) + self.fi = self.make_type_info('F', is_abstract=True) # class F2 - self.f2i = make_type_info('F2', is_abstract=True) + self.f2i = self.make_type_info('F2', is_abstract=True) # class F3(F) - self.f3i = make_type_info('F3', is_abstract=True, mro=[self.fi]) + self.f3i = self.make_type_info('F3', is_abstract=True, mro=[self.fi]) # Class TypeInfos - - self.oi = make_type_info('builtins.object') # class object - self.std_tuplei = make_type_info('builtins.tuple') # class tuple - self.type_typei = make_type_info('builtins.type') # class type - self.std_functioni = make_type_info('std::Function') # Function TODO - self.ai = make_type_info('A', mro=[self.oi]) # class A - self.bi = make_type_info('B', mro=[self.ai, self.oi]) # class B(A) - self.ci = make_type_info('C', mro=[self.ai, self.oi]) # class C(A) - self.di = make_type_info('D', mro=[self.oi]) # class D - + self.std_tuplei = self.make_type_info('builtins.tuple') # class tuple + self.type_typei = self.make_type_info('builtins.type') # class type + self.std_functioni = self.make_type_info('builtins.function') # function TODO + self.ai = self.make_type_info('A', mro=[self.oi]) # class A + self.bi = self.make_type_info('B', mro=[self.ai, self.oi]) # class B(A) + self.ci = self.make_type_info('C', mro=[self.ai, self.oi]) # class C(A) + self.di = self.make_type_info('D', mro=[self.oi]) # class D # class E(F) - self.ei = make_type_info('E', mro=[self.fi, self.oi]) - + self.ei = self.make_type_info('E', mro=[self.fi, self.oi]) # class E2(F2, F) - self.e2i = make_type_info('E2', mro=[self.f2i, self.fi, self.oi]) - + self.e2i = self.make_type_info('E2', mro=[self.f2i, self.fi, self.oi]) # class E3(F, F2) - self.e3i = make_type_info('E3', mro=[self.fi, self.f2i, self.oi]) + self.e3i = self.make_type_info('E3', mro=[self.fi, self.f2i, self.oi]) # Generic class TypeInfos - # G[T] - self.gi = make_type_info('G', mro=[self.oi], typevars=['T']) + self.gi = self.make_type_info('G', mro=[self.oi], typevars=['T']) # G2[T] - self.g2i = make_type_info('G2', mro=[self.oi], typevars=['T']) + self.g2i = self.make_type_info('G2', mro=[self.oi], typevars=['T']) # H[S, T] - self.hi = make_type_info('H', mro=[self.oi], typevars=['S', 'T']) + self.hi = self.make_type_info('H', mro=[self.oi], typevars=['S', 'T']) # GS[T, S] <: G[S] - self.gsi = make_type_info('GS', mro=[self.gi, self.oi], - typevars=['T', 'S'], - bases=[Instance(self.gi, [self.s])]) + self.gsi = self.make_type_info('GS', mro=[self.gi, self.oi], + typevars=['T', 'S'], + bases=[Instance(self.gi, [self.s])]) # GS2[S] <: G[S] - self.gs2i = make_type_info('GS2', mro=[self.gi, self.oi], - typevars=['S'], - bases=[Instance(self.gi, [self.s1])]) + self.gs2i = self.make_type_info('GS2', mro=[self.gi, self.oi], + typevars=['S'], + bases=[Instance(self.gi, [self.s1])]) # list[T] - self.std_listi = make_type_info('builtins.list', mro=[self.oi], - typevars=['T']) + self.std_listi = self.make_type_info('builtins.list', mro=[self.oi], + typevars=['T']) # Instance types - - self.o = Instance(self.oi, []) # object self.std_tuple = Instance(self.std_tuplei, []) # tuple self.type_type = Instance(self.type_typei, []) # type self.std_function = Instance(self.std_functioni, []) # function TODO @@ -109,7 +103,6 @@ def __init__(self): self.f3 = Instance(self.f3i, []) # F3 # Generic instance types - self.ga = Instance(self.gi, [self.a]) # G[A] self.gb = Instance(self.gi, [self.b]) # G[B] self.go = Instance(self.gi, [self.o]) # G[object] @@ -176,6 +169,40 @@ def callable_var_arg(self, min_args, *a): [ARG_STAR], [None] * n, a[-1], self.std_function) + def make_type_info(self, name: str, + is_abstract: bool = False, + mro: List[TypeInfo] = None, + bases: List[Instance] = None, + typevars: List[str] = None) -> TypeInfo: + """Make a TypeInfo suitable for use in unit tests.""" + + class_def = ClassDef(name, Block([]), None, []) + class_def.fullname = name + + if typevars: + v = [] # type: List[TypeVarDef] + id = 1 + for n in typevars: + v.append(TypeVarDef(n, id, None, self.oi)) + id += 1 + class_def.type_vars = v + + info = TypeInfo(SymbolTable(), class_def) + if mro is None: + mro = [] + if name != 'builtins.object': + mro.append(self.oi) + info.mro = [info] + mro + if bases is None: + if mro: + # By default, assume that there is a single non-generic base. + bases = [Instance(mro[0], [])] + else: + bases = [] + info.bases = bases + + return info + class InterfaceTypeFixture(TypeFixture): """Extension of TypeFixture that contains additional generic @@ -184,48 +211,15 @@ class InterfaceTypeFixture(TypeFixture): def __init__(self): super().__init__() # GF[T] - self.gfi = make_type_info('GF', typevars=['T'], is_abstract=True) + self.gfi = self.make_type_info('GF', typevars=['T'], is_abstract=True) # M1 <: GF[A] - self.m1i = make_type_info('M1', - is_abstract=True, - mro=[self.gfi, self.oi], - bases=[Instance(self.gfi, [self.a])]) + self.m1i = self.make_type_info('M1', + is_abstract=True, + mro=[self.gfi, self.oi], + bases=[Instance(self.gfi, [self.a])]) self.gfa = Instance(self.gfi, [self.a]) # GF[A] self.gfb = Instance(self.gfi, [self.b]) # GF[B] self.m1 = Instance(self.m1i, []) # M1 - - -def make_type_info(name: str, - is_abstract: bool = False, - mro: List[TypeInfo] = None, - bases: List[Instance] = None, - typevars: List[str] = None) -> TypeInfo: - """Make a TypeInfo suitable for use in unit tests.""" - - class_def = ClassDef(name, Block([]), None, []) - class_def.fullname = name - - if typevars: - v = [] # type: List[TypeVarDef] - id = 1 - for n in typevars: - v.append(TypeVarDef(n, id, None)) - id += 1 - class_def.type_vars = v - - info = TypeInfo(SymbolTable(), class_def) - if mro is None: - mro = [] - info.mro = [info] + mro - if bases is None: - if mro: - # By default, assume that there is a single non-generic base. - bases = [Instance(mro[0], [])] - else: - bases = [] - info.bases = bases - - return info diff --git a/mypy/types.py b/mypy/types.py index 739e63568424..df05e1ee2e49 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -35,14 +35,16 @@ class TypeVarDef(mypy.nodes.Context): name = '' id = 0 values = Undefined(List[Type]) + upper_bound = Undefined(Type) line = 0 repr = Undefined(Any) - def __init__(self, name: str, id: int, values: List[Type], line: int = -1, + def __init__(self, name: str, id: int, values: List[Type], upper_bound: Type, line: int = -1, repr: Any = None) -> None: self.name = name self.id = id self.values = values + self.upper_bound = upper_bound self.line = line self.repr = repr @@ -193,7 +195,8 @@ class TypeVar(Type): name = '' # Name of the type variable (for messages and debugging) id = 0 # 1, 2, ... for type-related, -1, ... for function-related - values = Undefined(List[Type]) # Value restriction + values = Undefined(List[Type]) # Value restriction, empty list if no restriction + upper_bound = Undefined(Type) # Upper bound for values (currently always 'object') # True if refers to the value of the type variable stored in a generic # instance wrapper. This is only relevant for generic class wrappers. If @@ -203,12 +206,13 @@ class TypeVar(Type): # Can also be BoundVar/ObjectVar TODO better representation is_wrapper_var = Undefined(Any) - def __init__(self, name: str, id: int, values: List[Type], + def __init__(self, name: str, id: int, values: List[Type], upper_bound: Type, is_wrapper_var: Any = False, line: int = -1, repr: Any = None) -> None: self.name = name self.id = id self.values = values + self.upper_bound = upper_bound self.is_wrapper_var = is_wrapper_var super().__init__(line, repr) @@ -816,7 +820,7 @@ class BasicTypes: """Collection of Instance types of basic types (object, type, etc.).""" def __init__(self, object: Instance) -> None: - self.object = object + pass def strip_type(typ: Type) -> Type: From 83db45da27e037af694b832039a40d340d7fa698 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 16:20:48 -0700 Subject: [PATCH 018/144] Remove mypy.types.BasicTypes --- mypy/checker.py | 36 +++++++++++++--------------------- mypy/checkexpr.py | 18 +++++++---------- mypy/checkmember.py | 2 +- mypy/erasetype.py | 9 +++------ mypy/infer.py | 13 +++++-------- mypy/join.py | 44 +++++++++++++++++++----------------------- mypy/meet.py | 24 +++++++++++------------ mypy/solve.py | 9 ++++----- mypy/test/testsolve.py | 2 +- mypy/test/testtypes.py | 8 ++++---- mypy/typefixture.py | 6 +----- mypy/types.py | 7 ------- 12 files changed, 70 insertions(+), 108 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index d1de060b674f..4d6e0999f68f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -22,8 +22,7 @@ from mypy import nodes from mypy.types import ( Type, AnyType, Callable, Void, FunctionLike, Overloaded, TupleType, - Instance, NoneTyp, UnboundType, ErrorType, TypeTranslator, BasicTypes, - strip_type, UnionType + Instance, NoneTyp, UnboundType, ErrorType, TypeTranslator, strip_type, UnionType ) from mypy.sametypes import is_same_type from mypy.messages import MessageBuilder @@ -68,14 +67,13 @@ class Key(AnyType): class ConditionalTypeBinder: """Keep track of conditional types of variables.""" - def __init__(self, basic_types_fn) -> None: + def __init__(self) -> None: self.frames = List[Frame]() # The first frame is special: it's the declared types of variables. self.frames.append(Frame()) self.dependencies = Dict[Key, Set[Key]]() # Set of other keys to invalidate if a key # is changed self._added_dependencies = Set[Key]() # Set of keys with dependencies added already - self.basic_types_fn = basic_types_fn self.frames_on_escape = Dict[int, List[Frame]]() @@ -138,8 +136,7 @@ def update_from_options(self, frames: List[Frame]) -> bool: type = resulting_values[0] for other in resulting_values[1:]: - type = join_simple(self.frames[0][key], type, - other, self.basic_types_fn()) + type = join_simple(self.frames[0][key], type, other) if not is_same_type(type, current_value): self._push(key, type) changed = True @@ -156,8 +153,7 @@ def update_expand(self, frame: Frame, index: int = -1) -> bool: old_type = self._get(key, index) if old_type is None: continue - replacement = join_simple(self.frames[0][key], old_type, frame[key], - self.basic_types_fn()) + replacement = join_simple(self.frames[0][key], old_type, frame[key]) if not is_same_type(replacement, old_type): self._push(key, replacement, index) @@ -269,12 +265,12 @@ def pop_loop_frame(self): self.loop_frames.pop() -def meet_frames(basic_types: BasicTypes, *frames: Frame) -> Frame: +def meet_frames(*frames: Frame) -> Frame: answer = Frame() for f in frames: for key in f: if key in answer: - answer[key] = meet_simple(answer[key], f[key], basic_types) + answer[key] = meet_simple(answer[key], f[key]) else: answer[key] = f[key] return answer @@ -330,7 +326,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile], self.pyversion = pyversion self.msg = MessageBuilder(errors) self.type_map = {} - self.binder = ConditionalTypeBinder(self.basic_types) + self.binder = ConditionalTypeBinder() self.binder.push_frame() self.expr_checker = mypy.checkexpr.ExpressionChecker(self, self.msg) self.return_types = [] @@ -485,7 +481,7 @@ def check_func_def(self, defn: FuncItem, typ: Callable, name: str) -> None: # Expand type variables with value restrictions to ordinary types. for item, typ in self.expand_typevars(defn, typ): old_binder = self.binder - self.binder = ConditionalTypeBinder(self.basic_types) + self.binder = ConditionalTypeBinder() self.binder.push_frame() defn.expanded.append(item) @@ -844,7 +840,7 @@ def visit_class_def(self, defn: ClassDef) -> Type: typ = defn.info self.errors.push_type(defn.name) old_binder = self.binder - self.binder = ConditionalTypeBinder(self.basic_types) + self.binder = ConditionalTypeBinder() self.binder.push_frame() self.accept(defn.defs) self.binder = old_binder @@ -1089,7 +1085,7 @@ def narrow_type_from_binder(self, expr: Node, known_type: Type) -> Type: if expr.literal >= LITERAL_TYPE: restriction = self.binder.get(expr) if restriction: - ans = meet_simple(known_type, restriction, self.basic_types()) + ans = meet_simple(known_type, restriction) return ans return known_type @@ -1264,7 +1260,7 @@ def visit_if_stmt(self, s: IfStmt) -> Type: self.binder.allow_jump(len(self.binder.frames) - 1) if not self.breaking_out: broken = False - ending_frames.append(meet_frames(self.basic_types(), clauses_frame, frame)) + ending_frames.append(meet_frames(clauses_frame, frame)) self.breaking_out = False @@ -1388,7 +1384,7 @@ def exception_type(self, n: Node) -> Type: for item in unwrapped.items: tt = self.exception_type(item) if t: - t = join_types(t, tt, self.basic_types()) + t = join_types(t, tt) else: t = tt return t @@ -1440,7 +1436,7 @@ def analyse_iterable_item_type(self, expr: Node) -> Type: if isinstance(iterable, TupleType): joined = NoneTyp() # type: Type for item in iterable.items: - joined = join_types(joined, item, self.basic_types()) + joined = join_types(joined, item) if isinstance(joined, ErrorType): self.fail(messages.CANNOT_INFER_ITEM_TYPE, expr) return AnyType() @@ -1747,12 +1743,6 @@ def enter(self) -> None: def leave(self) -> None: self.locals = None - def basic_types(self) -> BasicTypes: - """Return a BasicTypes instance that contains primitive types that are - needed for certain type operations (joins, for example). - """ - return BasicTypes(self.object_type()) - def is_within_function(self) -> bool: """Are we currently type checking within a function? diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4c8fa9dea145..14378c70e562 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -282,8 +282,7 @@ def infer_function_type_arguments_using_context( # type inference to conform to the valid values. Give up and just use function # arguments for type inference. ret_type = NoneTyp() - args = infer_type_arguments(callable.type_var_ids(), ret_type, - erased_ctx, self.chk.basic_types()) + args = infer_type_arguments(callable.type_var_ids(), ret_type, erased_ctx) # Only substite non-None and non-erased types. new_args = [] # type: List[Type] for arg in args: @@ -329,8 +328,7 @@ def infer_function_type_arguments(self, callee_type: Callable, pass1_args.append(arg) inferred_args = infer_function_type_arguments( - callee_type, pass1_args, arg_kinds, formal_to_actual, - self.chk.basic_types()) # type: List[Type] + callee_type, pass1_args, arg_kinds, formal_to_actual) # type: List[Type] if 2 in arg_pass_nums: # Second pass of type inference. @@ -376,8 +374,7 @@ def infer_function_type_arguments_pass2( callee_type, args, arg_kinds, formal_to_actual) inferred_args = infer_function_type_arguments( - callee_type, arg_types, arg_kinds, formal_to_actual, - self.chk.basic_types()) + callee_type, arg_types, arg_kinds, formal_to_actual) return callee_type, inferred_args @@ -817,7 +814,7 @@ def visit_comparison_expr(self, e: ComparisonExpr) -> Type: else: # TODO: check on void needed? self.check_not_void(sub_result, e) - result = join.join_types(result, sub_result, self.chk.basic_types()) + result = join.join_types(result, sub_result) return result @@ -911,8 +908,7 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type: self.check_not_void(left_type, context) self.check_not_void(right_type, context) - return join.join_types(left_type, right_type, - self.chk.basic_types()) + return join.join_types(left_type, right_type) def check_list_multiply(self, e: OpExpr) -> Type: """Type check an expression of form '[...] * e'. @@ -1218,7 +1214,7 @@ def visit_conditional_expr(self, e: ConditionalExpr) -> Type: self.check_not_void(cond_type, e) if_type = self.accept(e.if_expr) else_type = self.accept(e.else_expr, context=if_type) - return join.join_types(if_type, else_type, self.chk.basic_types()) + return join.join_types(if_type, else_type) # # Helpers @@ -1291,7 +1287,7 @@ def unwrap_list(self, a: List[Node]) -> List[Node]: def erase(self, type: Type) -> Type: """Replace type variable types in type with Any.""" - return erasetype.erase_type(type, self.chk.basic_types()) + return erasetype.erase_type(type) def is_valid_argc(nargs: int, is_var_arg: bool, callable: Callable) -> bool: diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 85508aea5196..fe7de03a3589 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -4,7 +4,7 @@ from mypy.types import ( Type, Instance, AnyType, TupleType, Callable, FunctionLike, TypeVarDef, - Overloaded, TypeVar, TypeTranslator, BasicTypes, UnionType + Overloaded, TypeVar, TypeTranslator, UnionType ) from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context from mypy.nodes import ARG_POS, function_type, Decorator diff --git a/mypy/erasetype.py b/mypy/erasetype.py index dd457a306874..1f0a2b7ba67f 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -3,11 +3,11 @@ from mypy.types import ( Type, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp, Instance, TypeVar, Callable, TupleType, UnionType, Overloaded, ErasedType, - TypeTranslator, BasicTypes, TypeList + TypeTranslator, TypeList ) -def erase_type(typ: Type, basic: BasicTypes) -> Type: +def erase_type(typ: Type) -> Type: """Erase any type variables from a type. Also replace tuple types with the corresponding concrete types. Replace @@ -20,13 +20,10 @@ def erase_type(typ: Type, basic: BasicTypes) -> Type: Function[...] -> Function[[], None] """ - return typ.accept(EraseTypeVisitor(basic)) + return typ.accept(EraseTypeVisitor()) class EraseTypeVisitor(TypeVisitor[Type]): - def __init__(self, basic: BasicTypes) -> None: - self.basic = basic - def visit_unbound_type(self, t: UnboundType) -> Type: assert False, 'Not supported' diff --git a/mypy/infer.py b/mypy/infer.py index e6eef423e918..70289ca68829 100644 --- a/mypy/infer.py +++ b/mypy/infer.py @@ -3,7 +3,7 @@ from typing import List from mypy.constraints import infer_constraints, infer_constraints_for_callable -from mypy.types import Type, Callable, BasicTypes +from mypy.types import Type, Callable from mypy.solve import solve_constraints from mypy.constraints import SUBTYPE_OF @@ -11,8 +11,7 @@ def infer_function_type_arguments(callee_type: Callable, arg_types: List[Type], arg_kinds: List[int], - formal_to_actual: List[List[int]], - basic: BasicTypes) -> List[Type]: + formal_to_actual: List[List[int]]) -> List[Type]: """Infer the type arguments of a generic function. Return an array of lower bound types for the type variables -1 (at @@ -24,7 +23,6 @@ def infer_function_type_arguments(callee_type: Callable, arg_types: argument types at the call site arg_kinds: nodes.ARG_* values for arg_types formal_to_actual: mapping from formal to actual variable indices - basic: references to basic types which are needed during inference """ # Infer constraints. constraints = infer_constraints_for_callable( @@ -32,13 +30,12 @@ def infer_function_type_arguments(callee_type: Callable, # Solve constraints. type_vars = callee_type.type_var_ids() - return solve_constraints(type_vars, constraints, basic) + return solve_constraints(type_vars, constraints) def infer_type_arguments(type_var_ids: List[int], - template: Type, actual: Type, - basic: BasicTypes) -> List[Type]: + template: Type, actual: Type) -> List[Type]: # Like infer_function_type_arguments, but only match a single type # against a generic type. constraints = infer_constraints(template, actual, SUBTYPE_OF) - return solve_constraints(type_var_ids, constraints, basic) + return solve_constraints(type_var_ids, constraints) diff --git a/mypy/join.py b/mypy/join.py index 2400f55319fe..48e06f0b4cb2 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -4,13 +4,13 @@ from mypy.types import ( Type, AnyType, NoneTyp, Void, TypeVisitor, Instance, UnboundType, - ErrorType, TypeVar, Callable, TupleType, ErasedType, BasicTypes, TypeList, + ErrorType, TypeVar, Callable, TupleType, ErasedType, TypeList, UnionType, FunctionLike ) from mypy.subtypes import is_subtype, is_equivalent, map_instance_to_supertype -def join_simple(declaration: Type, s: Type, t: Type, basic: BasicTypes) -> Type: +def join_simple(declaration: Type, s: Type, t: Type) -> Type: """Return a simple least upper bound given the declared type.""" if isinstance(s, AnyType): @@ -31,7 +31,7 @@ def join_simple(declaration: Type, s: Type, t: Type, basic: BasicTypes) -> Type: if isinstance(declaration, UnionType): return UnionType.make_simplified_union([s, t]) - value = t.accept(TypeJoinVisitor(s, basic)) + value = t.accept(TypeJoinVisitor(s)) if value is None: # XXX this code path probably should be avoided. @@ -46,7 +46,7 @@ def join_simple(declaration: Type, s: Type, t: Type, basic: BasicTypes) -> Type: return declaration -def join_types(s: Type, t: Type, basic: BasicTypes) -> Type: +def join_types(s: Type, t: Type) -> Type: """Return the least upper bound of s and t. For example, the join of 'int' and 'object' is 'object'. @@ -64,15 +64,14 @@ def join_types(s: Type, t: Type, basic: BasicTypes) -> Type: return t # Use a visitor to handle non-trivial cases. - return t.accept(TypeJoinVisitor(s, basic)) + return t.accept(TypeJoinVisitor(s)) class TypeJoinVisitor(TypeVisitor[Type]): """Implementation of the least upper bound algorithm.""" - def __init__(self, s: Type, basic: BasicTypes) -> None: + def __init__(self, s: Type) -> None: self.s = s - self.basic = basic def visit_unbound_type(self, t: UnboundType) -> Type: if isinstance(self.s, Void) or isinstance(self.s, ErrorType): @@ -118,7 +117,7 @@ def visit_type_var(self, t: TypeVar) -> Type: def visit_instance(self, t: Instance) -> Type: if isinstance(self.s, Instance): - return join_instances(t, cast(Instance, self.s), self.basic) + return join_instances(t, cast(Instance, self.s)) elif t.type.fullname() == 'builtins.type' and is_subtype(self.s, t): return t else: @@ -127,8 +126,7 @@ def visit_instance(self, t: Instance) -> Type: def visit_callable(self, t: Callable) -> Type: if isinstance(self.s, Callable) and is_similar_callables( t, cast(Callable, self.s)): - return combine_similar_callables(t, cast(Callable, self.s), - self.basic) + return combine_similar_callables(t, cast(Callable, self.s)) elif t.is_type_obj() and is_subtype(self.s, t.fallback): return t.fallback elif (t.is_type_obj() and isinstance(self.s, Instance) and @@ -150,7 +148,7 @@ def visit_tuple_type(self, t: TupleType) -> Type: return self.default(self.s) def join(self, s: Type, t: Type) -> Type: - return join_types(s, t, self.basic) + return join_types(s, t) def default(self, typ: Type) -> Type: if isinstance(typ, Instance): @@ -169,7 +167,7 @@ def default(self, typ: Type) -> Type: return AnyType() -def join_instances(t: Instance, s: Instance, basic: BasicTypes) -> Type: +def join_instances(t: Instance, s: Instance) -> Type: """Calculate the join of two instance types. If allow_interfaces is True, also consider interface-type results for @@ -185,31 +183,30 @@ def join_instances(t: Instance, s: Instance, basic: BasicTypes) -> Type: # Compatible; combine type arguments. args = [] # type: List[Type] for i in range(len(t.args)): - args.append(join_types(t.args[i], s.args[i], basic)) + args.append(join_types(t.args[i], s.args[i])) return Instance(t.type, args) else: # Incompatible; return trivial result object. return object_from_instance(t) elif t.type.bases and is_subtype(t, s): - return join_instances_via_supertype(t, s, basic) + return join_instances_via_supertype(t, s) else: # Now t is not a subtype of s, and t != s. Now s could be a subtype # of t; alternatively, we need to find a common supertype. This works # in of the both cases. - return join_instances_via_supertype(s, t, basic) + return join_instances_via_supertype(s, t) -def join_instances_via_supertype(t: Instance, s: Instance, - basic: BasicTypes) -> Type: +def join_instances_via_supertype(t: Instance, s: Instance) -> Type: # Give preference to joins via duck typing relationship, so that # join(int, float) == float, for example. if t.type.ducktype and is_subtype(t.type.ducktype, s): - return join_types(t.type.ducktype, s, basic) + return join_types(t.type.ducktype, s) elif s.type.ducktype and is_subtype(s.type.ducktype, t): - return join_types(t, s.type.ducktype, basic) + return join_types(t, s.type.ducktype) res = s mapped = map_instance_to_supertype(t, t.type.bases[0].type) - join = join_instances(mapped, res, basic) + join = join_instances(mapped, res) # If the join failed, fail. This is a defensive measure (this might # never happen). if isinstance(join, ErrorType): @@ -228,11 +225,10 @@ def is_similar_callables(t: Callable, s: Callable) -> bool: and t.is_var_arg == s.is_var_arg and is_equivalent(t, s)) -def combine_similar_callables(t: Callable, s: Callable, - basic: BasicTypes) -> Callable: +def combine_similar_callables(t: Callable, s: Callable) -> Callable: arg_types = [] # type: List[Type] for i in range(len(t.arg_types)): - arg_types.append(join_types(t.arg_types[i], s.arg_types[i], basic)) + arg_types.append(join_types(t.arg_types[i], s.arg_types[i])) # TODO kinds and argument names # The fallback type can be either 'function' or 'type'. The result should have 'type' as # fallback only if both operands have it as 'type'. @@ -243,7 +239,7 @@ def combine_similar_callables(t: Callable, s: Callable, return Callable(arg_types, t.arg_kinds, t.arg_names, - join_types(t.ret_type, s.ret_type, basic), + join_types(t.ret_type, s.ret_type), fallback, None, t.variables) diff --git a/mypy/meet.py b/mypy/meet.py index 325d1051b6c3..98d0c814919d 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -3,7 +3,7 @@ from mypy.join import is_similar_callables, combine_similar_callables from mypy.types import ( Type, AnyType, TypeVisitor, UnboundType, Void, ErrorType, NoneTyp, TypeVar, - Instance, Callable, TupleType, ErasedType, BasicTypes, TypeList, UnionType + Instance, Callable, TupleType, ErasedType, TypeList, UnionType ) from mypy.sametypes import is_same_type from mypy.subtypes import is_subtype @@ -12,7 +12,7 @@ # TODO Describe this module. -def meet_types(s: Type, t: Type, basic: BasicTypes) -> Type: +def meet_types(s: Type, t: Type) -> Type: """Return the greatest lower bound of two types.""" if isinstance(s, ErasedType): return s @@ -20,14 +20,14 @@ def meet_types(s: Type, t: Type, basic: BasicTypes) -> Type: return t if isinstance(s, UnionType) and not isinstance(t, UnionType): s, t = t, s - return t.accept(TypeMeetVisitor(s, basic)) + return t.accept(TypeMeetVisitor(s)) -def meet_simple(s: Type, t: Type, basic: BasicTypes, default_right: bool = True) -> Type: +def meet_simple(s: Type, t: Type, default_right: bool = True) -> Type: if s == t: return s if isinstance(s, UnionType): - return UnionType.make_simplified_union([meet_types(x, t, basic) for x in s.items]) + return UnionType.make_simplified_union([meet_types(x, t) for x in s.items]) elif not is_overlapping_types(s, t): return Void() else: @@ -37,7 +37,7 @@ def meet_simple(s: Type, t: Type, basic: BasicTypes, default_right: bool = True) return s -def meet_simple_away(s: Type, t: Type, basic: BasicTypes) -> Type: +def meet_simple_away(s: Type, t: Type) -> Type: if isinstance(s, UnionType): return UnionType.make_simplified_union([x for x in s.items if not is_subtype(x, t)]) @@ -86,9 +86,8 @@ def nearest_builtin_ancestor(type: TypeInfo) -> TypeInfo: class TypeMeetVisitor(TypeVisitor[Type]): - def __init__(self, s: Type, basic: BasicTypes) -> None: + def __init__(self, s: Type) -> None: self.s = s - self.basic = basic def visit_unbound_type(self, t: UnboundType) -> Type: if isinstance(self.s, Void) or isinstance(self.s, ErrorType): @@ -112,9 +111,9 @@ def visit_union_type(self, t: UnionType) -> Type: meets = List[Type]() for x in t.items: for y in self.s.items: - meets.append(meet_types(x, y, self.basic)) + meets.append(meet_types(x, y)) else: - meets = [meet_types(x, self.s, self.basic) + meets = [meet_types(x, self.s) for x in t.items] return UnionType.make_simplified_union(meets) @@ -166,8 +165,7 @@ def visit_instance(self, t: Instance) -> Type: def visit_callable(self, t: Callable) -> Type: if isinstance(self.s, Callable) and is_similar_callables( t, cast(Callable, self.s)): - return combine_similar_callables(t, cast(Callable, self.s), - self.basic) + return combine_similar_callables(t, cast(Callable, self.s)) else: return self.default(self.s) @@ -192,7 +190,7 @@ def visit_intersection(self, t): return self.default(self.s) def meet(self, s, t): - return meet_types(s, t, self.basic) + return meet_types(s, t) def default(self, typ): if isinstance(typ, UnboundType): diff --git a/mypy/solve.py b/mypy/solve.py index 7c43c335c76e..af635bcaf02b 100644 --- a/mypy/solve.py +++ b/mypy/solve.py @@ -2,15 +2,14 @@ from typing import List, Dict -from mypy.types import Type, Void, NoneTyp, AnyType, ErrorType, BasicTypes +from mypy.types import Type, Void, NoneTyp, AnyType, ErrorType from mypy.constraints import Constraint, SUPERTYPE_OF from mypy.join import join_types from mypy.meet import meet_types from mypy.subtypes import is_subtype -def solve_constraints(vars: List[int], constraints: List[Constraint], - basic: BasicTypes) -> List[Type]: +def solve_constraints(vars: List[int], constraints: List[Constraint]) -> List[Type]: """Solve type constraints. Return the best type(s) for type variables; each type can be None if the value of the variable @@ -39,12 +38,12 @@ def solve_constraints(vars: List[int], constraints: List[Constraint], if bottom is None: bottom = c.target else: - bottom = join_types(bottom, c.target, basic) + bottom = join_types(bottom, c.target) else: if top is None: top = c.target else: - top = meet_types(top, c.target, basic) + top = meet_types(top, c.target) if isinstance(top, AnyType) or isinstance(bottom, AnyType): res.append(AnyType()) diff --git a/mypy/test/testsolve.py b/mypy/test/testsolve.py index 04b3ab3ae252..0b470421280b 100644 --- a/mypy/test/testsolve.py +++ b/mypy/test/testsolve.py @@ -141,7 +141,7 @@ def assert_solve(self, vars, constraints, results): res.append(r[0]) else: res.append(r) - actual = solve_constraints(vars, constraints, self.fx.basic) + actual = solve_constraints(vars, constraints) assert_equal(str(actual), str(res)) def supc(self, type_var, bound): diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index b4b26cc2dbb3..1687cfb341e3 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -176,7 +176,7 @@ def test_erase_with_type_object(self): self.fx.callable_type(self.fx.void)) def assert_erase(self, orig, result): - assert_equal(str(erase_type(orig, self.fx.basic)), str(result)) + assert_equal(str(erase_type(orig)), str(result)) # is_more_precise @@ -423,7 +423,7 @@ def test_simple_type_objects(self): t2 = self.type_callable(self.fx.b, self.fx.b) self.assert_join(t1, t1, t1) - assert_true(join_types(t1, t1, self.fx.basic).is_type_obj()) + assert_true(join_types(t1, t1).is_type_obj()) self.assert_join(t1, t2, self.fx.type_type) self.assert_join(t1, self.fx.type_type, self.fx.type_type) @@ -444,7 +444,7 @@ def assert_join(self, s, t, join): self.assert_simple_join(t, s, join) def assert_simple_join(self, s, t, join): - result = join_types(s, t, self.fx.basic) + result = join_types(s, t) actual = str(result) expected = str(join) assert_equal(actual, expected, @@ -648,7 +648,7 @@ def assert_meet(self, s, t, meet): self.assert_simple_meet(t, s, meet) def assert_simple_meet(self, s, t, meet): - result = meet_types(s, t, self.fx.basic) + result = meet_types(s, t) actual = str(result) expected = str(meet) assert_equal(actual, expected, diff --git a/mypy/typefixture.py b/mypy/typefixture.py index 9356640a19dc..baf3f785c3be 100644 --- a/mypy/typefixture.py +++ b/mypy/typefixture.py @@ -6,8 +6,7 @@ from typing import List from mypy.types import ( - TypeVar, AnyType, Void, ErrorType, NoneTyp, Instance, Callable, TypeVarDef, - BasicTypes + TypeVar, AnyType, Void, ErrorType, NoneTyp, Instance, Callable, TypeVarDef ) from mypy.nodes import ( TypeInfo, ClassDef, Block, ARG_POS, ARG_OPT, ARG_STAR, SymbolTable @@ -127,9 +126,6 @@ def __init__(self): self.lsta = Instance(self.std_listi, [self.a]) # List[A] self.lstb = Instance(self.std_listi, [self.b]) # List[B] - # Basic types - self.basic = BasicTypes(self.o) - # Helper methods def callable(self, *a): diff --git a/mypy/types.py b/mypy/types.py index df05e1ee2e49..c5d8d663e7e6 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -816,13 +816,6 @@ def query_types(self, types: List[Type]) -> bool: return res -class BasicTypes: - """Collection of Instance types of basic types (object, type, etc.).""" - - def __init__(self, object: Instance) -> None: - pass - - def strip_type(typ: Type) -> Type: """Make a copy of type without 'debugging info' (function name).""" From 81b154db84110421128064de0a2f13ec938204d5 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 16:29:41 -0700 Subject: [PATCH 019/144] Remove obsolete AST node classes --- mypy/nodes.py | 53 ------------------------------------------- mypy/pprinter.py | 17 -------------- mypy/strconv.py | 12 ---------- mypy/treetransform.py | 12 +--------- mypy/visitor.py | 9 -------- 5 files changed, 1 insertion(+), 102 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index e3b6f2d1bc4b..eea550d6198f 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1309,59 +1309,6 @@ def accept(self, visitor: NodeVisitor[T]) -> T: return visitor.visit_disjointclass_expr(self) -class CoerceExpr(Node): - """Implicit coercion expression. - - This is used only when compiling/transforming. These are inserted - after type checking. - """ - - expr = Undefined(Node) - target_type = Undefined('mypy.types.Type') - source_type = Undefined('mypy.types.Type') - is_wrapper_class = False - - def __init__(self, expr: Node, target_type: 'mypy.types.Type', - source_type: 'mypy.types.Type', - is_wrapper_class: bool) -> None: - self.expr = expr - self.target_type = target_type - self.source_type = source_type - self.is_wrapper_class = is_wrapper_class - - def accept(self, visitor: NodeVisitor[T]) -> T: - return visitor.visit_coerce_expr(self) - - -class JavaCast(Node): - # TODO obsolete; remove - expr = Undefined(Node) - target = Undefined('mypy.types.Type') - - def __init__(self, expr: Node, target: 'mypy.types.Type') -> None: - self.expr = expr - self.target = target - - def accept(self, visitor: NodeVisitor[T]) -> T: - return visitor.visit_java_cast(self) - - -class TypeExpr(Node): - """Expression that evaluates to a runtime representation of a type. - - This is used only for runtime type checking. This node is always generated - only after type checking. - """ - - type = Undefined('mypy.types.Type') - - def __init__(self, typ: 'mypy.types.Type') -> None: - self.type = typ - - def accept(self, visitor: NodeVisitor[T]) -> T: - return visitor.visit_type_expr(self) - - class TempNode(Node): """Temporary dummy node used during type checking. diff --git a/mypy/pprinter.py b/mypy/pprinter.py index 43456c000517..508a64043fc7 100644 --- a/mypy/pprinter.py +++ b/mypy/pprinter.py @@ -166,23 +166,6 @@ def visit_member_expr(self, o): def visit_name_expr(self, o): self.string(o.name) - def visit_coerce_expr(self, o: CoerceExpr) -> None: - self.string('{') - self.full_type(o.target_type) - if coerce.is_special_primitive(o.source_type): - self.string(' <= ') - self.type(o.source_type) - self.string(' ') - self.node(o.expr) - self.string('}') - - def visit_type_expr(self, o: TypeExpr) -> None: - # Type expressions are only generated during transformation, so we must - # use automatic formatting. - self.string('<') - self.full_type(o.type) - self.string('>') - def visit_index_expr(self, o): if o.analyzed: o.analyzed.accept(self) diff --git a/mypy/strconv.py b/mypy/strconv.py index 22ed8bfc7cb1..08102a09c0ac 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -412,15 +412,3 @@ def visit_slice_expr(self, o): if not a[1]: a[1] = '' return self.dump(a, o) - - def visit_coerce_expr(self, o): - return self.dump([o.expr, ('Types', [o.target_type, o.source_type])], - o) - - def visit_type_expr(self, o): - return self.dump([str(o.type)], o) - - def visit_filter_node(self, o): - # These are for convenience. These node types are not defined in the - # parser module. - pass diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 66d7bb6f4cb5..793a3f1c4d33 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -16,8 +16,7 @@ UnicodeExpr, FloatExpr, CallExpr, SuperExpr, MemberExpr, IndexExpr, SliceExpr, OpExpr, UnaryExpr, FuncExpr, TypeApplication, PrintStmt, SymbolTable, RefExpr, UndefinedExpr, TypeVarExpr, DucktypeExpr, - DisjointclassExpr, CoerceExpr, TypeExpr, ComparisonExpr, - JavaCast, TempNode + DisjointclassExpr, ComparisonExpr, TempNode ) from mypy.types import Type, FunctionLike from mypy.visitor import NodeVisitor @@ -399,15 +398,6 @@ def visit_ducktype_expr(self, node: DucktypeExpr) -> Node: def visit_disjointclass_expr(self, node: DisjointclassExpr) -> Node: return DisjointclassExpr(node.cls) - def visit_coerce_expr(self, node: CoerceExpr) -> Node: - raise RuntimeError('Not supported') - - def visit_type_expr(self, node: TypeExpr) -> Node: - raise RuntimeError('Not supported') - - def visit_java_cast(self, node: JavaCast) -> Node: - raise RuntimeError('Not supported') - def visit_temp_node(self, node: TempNode) -> Node: return TempNode(self.type(node.type)) diff --git a/mypy/visitor.py b/mypy/visitor.py index 0793e9c23c09..52f47167d37e 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -202,14 +202,5 @@ def visit_ducktype_expr(self, o: 'mypy.nodes.DucktypeExpr') -> T: def visit_disjointclass_expr(self, o: 'mypy.nodes.DisjointclassExpr') -> T: pass - def visit_coerce_expr(self, o: 'mypy.nodes.CoerceExpr') -> T: - pass - - def visit_type_expr(self, o: 'mypy.nodes.TypeExpr') -> T: - pass - - def visit_java_cast(self, o: 'mypy.nodes.JavaCast') -> T: - pass - def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> T: pass From 41319ba3c4b0bc889430af9dc79cee1e22c33f3c Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 16:32:52 -0700 Subject: [PATCH 020/144] Clean up obsolete code --- mypy/sametypes.py | 4 +--- mypy/subtypes.py | 3 +-- mypy/typeanal.py | 2 +- mypy/types.py | 25 ++----------------------- 4 files changed, 5 insertions(+), 29 deletions(-) diff --git a/mypy/sametypes.py b/mypy/sametypes.py index e3770e9c76bc..0438be08247e 100644 --- a/mypy/sametypes.py +++ b/mypy/sametypes.py @@ -64,9 +64,7 @@ def visit_instance(self, left: Instance) -> bool: def visit_type_var(self, left: TypeVar) -> bool: return (isinstance(self.right, TypeVar) and - left.id == (cast(TypeVar, self.right)).id and - left.is_wrapper_var == - cast(TypeVar, self.right).is_wrapper_var) + left.id == (cast(TypeVar, self.right)).id) def visit_callable(self, left: Callable) -> bool: # FIX generics diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 9358901e0bb0..f6a495ba55b7 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -92,8 +92,7 @@ def visit_instance(self, left: Instance) -> bool: def visit_type_var(self, left: TypeVar) -> bool: right = self.right if isinstance(right, TypeVar): - return (left.name == right.name and - left.is_wrapper_var == right.is_wrapper_var) + return left.name == right.name else: return is_named_instance(self.right, 'builtins.object') diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 9834da2240bb..d01d1a10bc89 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -87,7 +87,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: rep = None values = cast(TypeVarExpr, sym.node).values return TypeVar(t.name, sym.tvar_id, values, self.builtin_type('builtins.object'), - False, t.line, rep) + t.line, rep) elif sym.node.fullname() == 'builtins.None': return Void() elif sym.node.fullname() == 'typing.Any': diff --git a/mypy/types.py b/mypy/types.py index c5d8d663e7e6..418eaf4672ef 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -182,10 +182,6 @@ def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_instance(self) -BOUND_VAR = 2 -OBJECT_VAR = 3 - - class TypeVar(Type): """A type variable type. @@ -198,22 +194,12 @@ class TypeVar(Type): values = Undefined(List[Type]) # Value restriction, empty list if no restriction upper_bound = Undefined(Type) # Upper bound for values (currently always 'object') - # True if refers to the value of the type variable stored in a generic - # instance wrapper. This is only relevant for generic class wrappers. If - # False (default), this refers to the type variable value(s) given as the - # implicit type variable argument. - # - # Can also be BoundVar/ObjectVar TODO better representation - is_wrapper_var = Undefined(Any) - def __init__(self, name: str, id: int, values: List[Type], upper_bound: Type, - is_wrapper_var: Any = False, line: int = -1, - repr: Any = None) -> None: + line: int = -1, repr: Any = None) -> None: self.name = name self.id = id self.values = values self.upper_bound = upper_bound - self.is_wrapper_var = is_wrapper_var super().__init__(line, repr) def accept(self, visitor: 'TypeVisitor[T]') -> T: @@ -648,14 +634,7 @@ def visit_type_var(self, t): return '`{}'.format(t.id) else: # Named type variable type. - s = '{}`{}'.format(t.name, t.id) - if t.is_wrapper_var == BOUND_VAR: - s += '!B' - elif t.is_wrapper_var is True: - s += '!W' - elif t.is_wrapper_var == OBJECT_VAR: - s += '!O' - return s + return '{}`{}'.format(t.name, t.id) def visit_callable(self, t): s = '' From cbca1a5d9c8a9d875b64694bcb878a05efa0828e Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 16:35:16 -0700 Subject: [PATCH 021/144] Remove more obsolete code Closes #449. --- mypy/coerce.py | 69 ----------- mypy/pprinter.py | 317 ----------------------------------------------- 2 files changed, 386 deletions(-) delete mode 100644 mypy/coerce.py delete mode 100644 mypy/pprinter.py diff --git a/mypy/coerce.py b/mypy/coerce.py deleted file mode 100644 index b406caaa10ec..000000000000 --- a/mypy/coerce.py +++ /dev/null @@ -1,69 +0,0 @@ -from typing import cast - -from mypy.nodes import Node, TypeInfo, CoerceExpr, JavaCast -from mypy.types import ( - Type, Instance, Void, NoneTyp, AnyType -) -from mypy.sametypes import is_same_type -from mypy.subtypes import is_proper_subtype -from mypy.rttypevars import translate_runtime_type_vars_in_context - - -def coerce(expr: Node, target_type: Type, source_type: Type, context: TypeInfo, - is_wrapper_class: bool = False, is_java: bool = False) -> Node: - """Build an expression that coerces expr from source_type to target_type. - - Return bare expr if the coercion is trivial (always a no-op). - """ - if is_trivial_coercion(target_type, source_type, is_java): - res = expr - else: - # Translate type variables to expressions that fetch the value of a - # runtime type variable. - target = translate_runtime_type_vars_in_context(target_type, context, - is_java) - source = translate_runtime_type_vars_in_context(source_type, context, - is_java) - res = CoerceExpr(expr, target, source, is_wrapper_class) - - if is_java and ((isinstance(source_type, Instance) and - (cast(Instance, source_type)).erased) - or (isinstance(res, CoerceExpr) and - isinstance(target_type, Instance))): - res = JavaCast(res, target_type) - - return res - - -def is_trivial_coercion(target_type: Type, source_type: Type, - is_java: bool) -> bool: - """Is an implicit coercion from source_type to target_type a no-op? - - Note that we omit coercions of form any <= C, unless C is a primitive that - may have a special representation. - """ - # FIX: Replace type vars in source type with any? - if isinstance(source_type, Void) or is_same_type(target_type, source_type): - return True - - # Coercions from a primitive type to any other type are non-trivial, since - # we may have to change the representation. - if not is_java and is_special_primitive(source_type): - return False - - return (is_proper_subtype(source_type, target_type) - or isinstance(source_type, NoneTyp) - or isinstance(target_type, AnyType)) - - -def is_special_primitive(type: Type) -> bool: - """Is type a primitive with a special runtime representation? - - There needs to be explicit corcions to/from special primitive types. For - example, floats need to boxed/unboxed. The special primitive types include - int, float and bool. - """ - return (isinstance(type, Instance) - and (cast(Instance, type)).type.fullname() in ['builtins.int', - 'builtins.float', - 'builtins.bool']) diff --git a/mypy/pprinter.py b/mypy/pprinter.py deleted file mode 100644 index 508a64043fc7..000000000000 --- a/mypy/pprinter.py +++ /dev/null @@ -1,317 +0,0 @@ -from typing import List, cast - -from mypy.output import TypeOutputVisitor -from mypy.nodes import ( - Node, VarDef, ClassDef, FuncDef, MypyFile, CoerceExpr, TypeExpr, CallExpr, - TypeVarExpr -) -from mypy.visitor import NodeVisitor -from mypy.types import Void, TypeVisitor, Callable, Instance, Type, UnboundType -from mypy.maptypevar import num_slots -from mypy import coerce -from mypy import nodes - - -class PrettyPrintVisitor(NodeVisitor): - """Convert transformed parse trees into formatted source code. - - Use automatic formatting (i.e. omit original formatting). - """ - - def __init__(self) -> None: - super().__init__() - self.result = [] # type: List[str] - self.indent = 0 - - def output(self) -> str: - return ''.join(self.result) - - # - # Definitions - # - - def visit_mypy_file(self, file: MypyFile) -> None: - for d in file.defs: - d.accept(self) - - def visit_class_def(self, tdef: ClassDef) -> None: - self.string('class ') - self.string(tdef.name) - if tdef.base_types: - b = [] # type: List[str] - for bt in tdef.base_types: - if not bt: - continue - elif isinstance(bt, UnboundType): - b.append(bt.name) - elif (cast(Instance, bt)).type.fullname() != 'builtins.object': - typestr = bt.accept(TypeErasedPrettyPrintVisitor()) - b.append(typestr) - if b: - self.string('({})'.format(', '.join(b))) - self.string(':\n') - for d in tdef.defs.body: - d.accept(self) - self.dedent() - - def visit_func_def(self, fdef: FuncDef) -> None: - # FIX varargs, default args, keyword args etc. - ftyp = cast(Callable, fdef.type) - self.string('def ') - self.string(fdef.name()) - self.string('(') - for i in range(len(fdef.args)): - a = fdef.args[i] - self.string(a.name()) - if i < len(ftyp.arg_types): - self.string(': ') - self.type(ftyp.arg_types[i]) - else: - self.string('xxx ') - if i < len(fdef.args) - 1: - self.string(', ') - self.string(') -> ') - self.type(ftyp.ret_type) - fdef.body.accept(self) - - def visit_var_def(self, vdef: VarDef) -> None: - if vdef.items[0].name() not in nodes.implicit_module_attrs: - self.string(vdef.items[0].name()) - self.string(': ') - self.type(vdef.items[0].type) - if vdef.init: - self.string(' = ') - self.node(vdef.init) - self.string('\n') - - # - # Statements - # - - def visit_block(self, b): - self.string(':\n') - for s in b.body: - s.accept(self) - self.dedent() - - def visit_pass_stmt(self, o): - self.string('pass\n') - - def visit_return_stmt(self, o): - self.string('return ') - if o.expr: - self.node(o.expr) - self.string('\n') - - def visit_expression_stmt(self, o): - self.node(o.expr) - self.string('\n') - - def visit_assignment_stmt(self, o): - if isinstance(o.rvalue, CallExpr) and isinstance(o.rvalue.analyzed, - TypeVarExpr): - # Skip type variable definition 'x = typevar(...)'. - return - self.node(o.lvalues[0]) # FIX multiple lvalues - if o.type: - self.string(': ') - self.type(o.type) - self.string(' = ') - self.node(o.rvalue) - self.string('\n') - - def visit_if_stmt(self, o): - self.string('if ') - self.node(o.expr[0]) - self.node(o.body[0]) - for e, b in zip(o.expr[1:], o.body[1:]): - self.string('elif ') - self.node(e) - self.node(b) - if o.else_body: - self.string('else') - self.node(o.else_body) - - def visit_while_stmt(self, o): - self.string('while ') - self.node(o.expr) - self.node(o.body) - if o.else_body: - self.string('else') - self.node(o.else_body) - - # - # Expressions - # - - def visit_call_expr(self, o): - if o.analyzed: - o.analyzed.accept(self) - return - self.node(o.callee) - self.string('(') - self.omit_next_space = True - for i in range(len(o.args)): - self.node(o.args[i]) - if i < len(o.args) - 1: - self.string(', ') - self.string(')') - - def visit_member_expr(self, o): - self.node(o.expr) - self.string('.' + o.name) - if o.direct: - self.string('!') - - def visit_name_expr(self, o): - self.string(o.name) - - def visit_index_expr(self, o): - if o.analyzed: - o.analyzed.accept(self) - return - self.node(o.base) - self.string('[') - self.node(o.index) - self.string(']') - - def visit_int_expr(self, o): - self.string(str(o.value)) - - def visit_str_expr(self, o): - self.string(repr(o.value)) - - def visit_op_expr(self, o): - self.node(o.left) - self.string(' %s ' % o.op) - self.node(o.right) - - def visit_comparison_expr(self, o): - self.node(o.operands[0]) - for operator, operand in zip(o.operators, o.operands[1:]): - self.string(' %s ' % operator) - self.node(operand) - - def visit_unary_expr(self, o): - self.string(o.op) - if o.op == 'not': - self.string(' ') - self.node(o.expr) - - def visit_paren_expr(self, o): - self.string('(') - self.node(o.expr) - self.string(')') - - def visit_super_expr(self, o): - self.string('super().') - self.string(o.name) - - def visit_cast_expr(self, o): - self.string('cast(') - self.type(o.type) - self.string(', ') - self.node(o.expr) - self.string(')') - - def visit_type_application(self, o): - # Type arguments are erased in transformation. - self.node(o.expr) - - def visit_undefined_expr(self, o): - # Omit declared type as redundant. - self.string('Undefined') - - # - # Helpers - # - - def string(self, s: str) -> None: - if not s: - return - if self.last_output_char() == '\n': - self.result.append(' ' * self.indent) - self.result.append(s) - if s.endswith(':\n'): - self.indent += 4 - - def dedent(self) -> None: - self.indent -= 4 - - def node(self, n: Node) -> None: - n.accept(self) - - def last_output_char(self) -> str: - if self.result: - return self.result[-1][-1] - return '' - - def type(self, t): - """Pretty-print a type with erased type arguments.""" - if t: - v = TypeErasedPrettyPrintVisitor() - self.string(t.accept(v)) - - def full_type(self, t): - """Pretty-print a type, includingn type arguments.""" - if t: - v = TypePrettyPrintVisitor() - self.string(t.accept(v)) - - -class TypeErasedPrettyPrintVisitor(TypeVisitor[str]): - """Pretty-print types. - - Omit type variables (e.g. C instead of C[int]). - - Note that the translation does not preserve all information about the - types, but this is fine since this is only used in test case output. - """ - - def visit_any(self, t): - return 'Any' - - def visit_void(self, t): - return 'None' - - def visit_instance(self, t): - return t.type.name() - - def visit_type_var(self, t): - return 'Any*' - - def visit_runtime_type_var(self, t): - v = PrettyPrintVisitor() - t.node.accept(v) - return v.output() - - -class TypePrettyPrintVisitor(TypeVisitor[str]): - """Pretty-print types. - - Include type variables. - - Note that the translation does not preserve all information about the - types, but this is fine since this is only used in test case output. - """ - - def visit_any(self, t): - return 'Any' - - def visit_void(self, t): - return 'None' - - def visit_instance(self, t): - s = t.type.name() - if t.args: - argstr = ', '.join([a.accept(self) for a in t.args]) - s += '[%s]' % argstr - return s - - def visit_type_var(self, t): - return 'Any*' - - def visit_runtime_type_var(self, t): - v = PrettyPrintVisitor() - t.node.accept(v) - return v.output() From b983ea7df488839f5082af64a4d5d27bbc3a06e1 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 20:40:53 -0700 Subject: [PATCH 022/144] Add test case for class Foo() --- mypy/test/data/parse.test | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mypy/test/data/parse.test b/mypy/test/data/parse.test index 54474b9c50a2..0320ec6546c8 100644 --- a/mypy/test/data/parse.test +++ b/mypy/test/data/parse.test @@ -2660,3 +2660,12 @@ MypyFile:1( y Block:4( PassStmt:5()))) + +[case testEmptySuperClass] +class A(): + pass +[out] +MypyFile:1( + ClassDef:1( + A + PassStmt:2())) From 39769640d8b2702f82957bc24622408c7a0fdb3c Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 22:14:41 -0700 Subject: [PATCH 023/144] Remove whitespace at end of lines --- CREDITS | 2 +- lib-python/3.2/incomplete/logging/__init__.py | 2 +- lib-python/3.2/subprocess.py | 4 ++-- lib-python/3.2/test/test_random.py | 2 +- lib-python/3.2/test/test_tempfile.py | 4 ++-- mypy/noderepr.py | 4 ++-- mypy/test/testoutput.py | 6 +++--- stubs/3.2/os/__init__.py | 20 +++++++++---------- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CREDITS b/CREDITS index 548bc8f2c853..05c66b83b69e 100644 --- a/CREDITS +++ b/CREDITS @@ -20,7 +20,7 @@ Contributors (in alphabetical order): Jeff Walden Additional thanks to: - + Max Bolingbroke Peter Calvert Kannan Goundan diff --git a/lib-python/3.2/incomplete/logging/__init__.py b/lib-python/3.2/incomplete/logging/__init__.py index 6468f68d1946..aa861eb956f2 100644 --- a/lib-python/3.2/incomplete/logging/__init__.py +++ b/lib-python/3.2/incomplete/logging/__init__.py @@ -1187,7 +1187,7 @@ class Logger(Filterer): any root any manager - + def __init__(self, name, level=NOTSET): """ Initialize the logger with a name and an optional level. diff --git a/lib-python/3.2/subprocess.py b/lib-python/3.2/subprocess.py index 5657d8e6fccf..d2529dba68f0 100644 --- a/lib-python/3.2/subprocess.py +++ b/lib-python/3.2/subprocess.py @@ -1019,7 +1019,7 @@ def _internal_poll(self, _deadstate: int = None) -> int: return self._internal_poll_win(_deadstate) from _subprocess import Handle - + def _internal_poll_win(self, _deadstate: int = None, _WaitForSingleObject: Function[[Handle, int], int] = _subprocess.WaitForSingleObject, @@ -1424,7 +1424,7 @@ def _internal_poll(self, _deadstate: int = None) -> int: """ return self._internal_poll_posix(_deadstate) - + def _internal_poll_posix(self, _deadstate: int = None, _waitpid: Function[[int, int], Tuple[int, int]] = os.waitpid, diff --git a/lib-python/3.2/test/test_random.py b/lib-python/3.2/test/test_random.py index 5b26576dfe54..d2ed7d6eb586 100644 --- a/lib-python/3.2/test/test_random.py +++ b/lib-python/3.2/test/test_random.py @@ -17,7 +17,7 @@ class TestBasicOps(unittest.TestCase, Generic[RT]): # Subclasses must arrange for self.gen to retrieve the Random instance # to be tested. - gen = Undefined(RT) # Either Random or SystemRandom + gen = Undefined(RT) # Either Random or SystemRandom def randomlist(self, n: int) -> List[float]: """Helper function to make a list of random numbers""" diff --git a/lib-python/3.2/test/test_tempfile.py b/lib-python/3.2/test/test_tempfile.py index 925e9068ed54..2b3926623bea 100644 --- a/lib-python/3.2/test/test_tempfile.py +++ b/lib-python/3.2/test/test_tempfile.py @@ -247,7 +247,7 @@ def __init__(self, dir: str, pre: str, suf: str, bin: int) -> None: else: flags = self._tflags (self.fd, self.name) = tempfile._mkstemp_inner(dir, pre, suf, flags) - + self._close = os.close self._unlink = os.unlink @@ -553,7 +553,7 @@ def tearDown(self) -> None: class mktemped: def _unlink(self, path: str) -> None: os.unlink(path) - + _bflags = tempfile._bin_openflags def __init__(self, dir: str, pre: str, suf: str) -> None: diff --git a/mypy/noderepr.py b/mypy/noderepr.py index 1cd24a4be5cd..102263cc839c 100644 --- a/mypy/noderepr.py +++ b/mypy/noderepr.py @@ -225,9 +225,9 @@ def __init__(self, dot: Any, name: Any) -> None: class ComparisonExprRepr: def __init__(self, operators: List[Any]) -> None: - # List of tupples of (op, op2). + # List of tupples of (op, op2). # Note: op2 may be empty; it is used for "is not" and "not in". - self.operators = operators + self.operators = operators class CallExprRepr: def __init__(self, lparen: Any, commas: List[Token], star: Any, star2: Any, diff --git a/mypy/test/testoutput.py b/mypy/test/testoutput.py index 818e56826eb6..b19ecf61bad2 100644 --- a/mypy/test/testoutput.py +++ b/mypy/test/testoutput.py @@ -39,7 +39,7 @@ def test_output(testcase): try: src = '\n'.join(testcase.input) # Parse and semantically analyze the source program. - + # Test case names with a special suffix get semantically analyzed. This # lets us test that semantic analysis does not break source code pretty # printing. @@ -54,7 +54,7 @@ def test_output(testcase): files = {'main': parse(src, 'main')} a = [] first = True - + # Produce an output containing the pretty-printed forms (with original # formatting) of all the relevant source files. for fnam in sorted(files.keys()): @@ -66,7 +66,7 @@ def test_output(testcase): if not first: a.append('{}:'.format(fix_path(remove_prefix( f.path, test_temp_dir)))) - + v = OutputVisitor() f.accept(v) s = v.output() diff --git a/stubs/3.2/os/__init__.py b/stubs/3.2/os/__init__.py index 00d879509ddd..4ada661d1fdc 100644 --- a/stubs/3.2/os/__init__.py +++ b/stubs/3.2/os/__init__.py @@ -98,10 +98,10 @@ # ----- os classes (structures) ----- class stat_result: - # For backward compatibility, the return value of stat() is also - # accessible as a tuple of at least 10 integers giving the most important - # (and portable) members of the stat structure, in the order st_mode, - # st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, st_atime, st_mtime, + # For backward compatibility, the return value of stat() is also + # accessible as a tuple of at least 10 integers giving the most important + # (and portable) members of the stat structure, in the order st_mode, + # st_ino, st_dev, st_nlink, st_uid, st_gid, st_size, st_atime, st_mtime, # st_ctime. More items may be added at the end by some implementations. st_mode = 0 # protection bits, @@ -118,7 +118,7 @@ class stat_result: def __init__(self, tuple) -> None: pass - # On some Unix systems (such as Linux), the following attributes may also + # On some Unix systems (such as Linux), the following attributes may also # be available: st_blocks = 0 # number of blocks allocated for file st_blksize = 0 # filesystem blocksize @@ -358,11 +358,11 @@ def utime(path: bytes, times: Tuple[float, float] = None) -> None: pass # TODO onerror: function from OSError to void @overload -def walk(top: str, topdown: bool = True, onerror: Any = None, +def walk(top: str, topdown: bool = True, onerror: Any = None, followlinks: bool = False) -> Iterator[Tuple[str, List[str], List[str]]]: pass @overload -def walk(top: bytes, topdown: bool = True, onerror: Any = None, +def walk(top: bytes, topdown: bool = True, onerror: Any = None, followlinks: bool = False) -> Iterator[Tuple[bytes, List[bytes], List[bytes]]]: pass # walk(): "By default errors from the os.listdir() call are ignored. If @@ -440,12 +440,12 @@ def spawnle(mode: int, path: bytes, arg0: bytes, *args: Any) -> int: pass # Imprecise sig @overload def spawnlp(mode: int, file: str, arg0: str, - *args: str) -> int: pass # Unix only TODO + *args: str) -> int: pass # Unix only TODO @overload def spawnlp(mode: int, file: bytes, arg0: bytes, *args: bytes) -> int: pass @overload def spawnlpe(mode: int, file: str, arg0: str, *args: Any) -> int: - pass # Imprecise signature; Unix only TODO + pass # Imprecise signature; Unix only TODO @overload def spawnlpe(mode: int, file: bytes, arg0: bytes, *args: Any) -> int: pass # Imprecise signature @@ -465,7 +465,7 @@ def spawnvp(mode: int, file: str, args: List[str]) -> int: pass # Unix only def spawnvp(mode: int, file: bytes, args: List[bytes]) -> int: pass @overload def spawnvpe(mode: int, file: str, args: List[str], - env: Mapping[str, str]) -> int: + env: Mapping[str, str]) -> int: pass # Unix only @overload def spawnvpe(mode: int, file: bytes, args: List[bytes], From c15ba23c6d1eeaa4accf2ba9b1afbf6d40352936 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 22:18:24 -0700 Subject: [PATCH 024/144] Add script for removing trailing whitespace This solution was suggested by @akaihola, and it is from a gist by @dpaluy. Originally from stack exchange (see the script for a link to the gist and the discussion). Closes #408. --- misc/remove-eol-whitespace.sh | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 misc/remove-eol-whitespace.sh diff --git a/misc/remove-eol-whitespace.sh b/misc/remove-eol-whitespace.sh new file mode 100755 index 000000000000..3da6b9de64a5 --- /dev/null +++ b/misc/remove-eol-whitespace.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +# Remove trailing whitespace from all non-binary files in a git repo. + +# From https://gist.github.com/dpaluy/3690668; originally from here: +# http://unix.stackexchange.com/questions/36233/how-to-skip-file-in-sed-if-it-contains-regex/36240#36240 + +git grep -I --name-only -z -e '' | xargs -0 sed -i -e 's/[ \t]\+\(\r\?\)$/\1/' From e662fddbf4815674799197cd67cb846e8211f105 Mon Sep 17 00:00:00 2001 From: YuanchaoZhu Date: Mon, 22 Sep 2014 01:48:16 -0400 Subject: [PATCH 025/144] Implement type checking raise from --- mypy/checker.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 4d6e0999f68f..885da982a058 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1338,8 +1338,12 @@ def visit_raise_stmt(self, s: RaiseStmt) -> Type: # Good! return None # Else fall back to the check below (which will fail). + super_type = self.named_type('builtins.BaseException') + # Type checking "raise from" + if s.from_expr: + super_type = self.accept(s.from_expr) self.check_subtype(typ, - self.named_type('builtins.BaseException'), s, + super_type, s, messages.INVALID_EXCEPTION) def visit_try_stmt(self, s: TryStmt) -> Type: From be8cc1c2f406cd6709f6dab436c3e93ec5b9c1a7 Mon Sep 17 00:00:00 2001 From: YuanchaoZhu Date: Mon, 22 Sep 2014 03:42:56 -0400 Subject: [PATCH 026/144] New test cases for raise from type checking --- mypy/test/data/check-statements.test | 8 ++++++++ mypy/test/data/fixtures/exception.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/mypy/test/data/check-statements.test b/mypy/test/data/check-statements.test index 185769a1c872..6cc3d4b6fb63 100644 --- a/mypy/test/data/check-statements.test +++ b/mypy/test/data/check-statements.test @@ -313,6 +313,14 @@ raise object # E: Exception must be derived from BaseException raise f # E: Exception must be derived from BaseException [builtins fixtures/exception.py] +[case testRaiseFromStatement] +from typing import Undefined +e = Undefined # type: Exception +a = Undefined # type: BaseException +raise e from a +raise e from 1 # E: Exception must be derived from BaseException +[builtins fixtures/exception.py] + [case testTryFinallyStatement] import typing try: diff --git a/mypy/test/data/fixtures/exception.py b/mypy/test/data/fixtures/exception.py index 8177156ccc4d..928272f64d2d 100644 --- a/mypy/test/data/fixtures/exception.py +++ b/mypy/test/data/fixtures/exception.py @@ -7,3 +7,5 @@ class tuple: pass class function: pass class BaseException: pass +class Exception: pass +class int: pass From ef13fc5d71f35fb3bec5063d551c58d2e2d05e4e Mon Sep 17 00:00:00 2001 From: YuanchaoZhu Date: Mon, 22 Sep 2014 10:37:15 -0400 Subject: [PATCH 027/144] Cast super_type from Instance down to Type --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 885da982a058..a2aa9f33e118 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1338,7 +1338,7 @@ def visit_raise_stmt(self, s: RaiseStmt) -> Type: # Good! return None # Else fall back to the check below (which will fail). - super_type = self.named_type('builtins.BaseException') + super_type = cast(Type, self.named_type('builtins.BaseException')) # Type checking "raise from" if s.from_expr: super_type = self.accept(s.from_expr) From 505f291789bb0a19d763c3d76c02118aeeb91a7b Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Tue, 23 Sep 2014 20:39:37 +0200 Subject: [PATCH 028/144] Added context to temp_nodes. Small fixes. --- mypy/checker.py | 30 ++++++++++++++--------------- mypy/test/data/check-inference.test | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 035a55de51ca..8fbc625cb98d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -934,7 +934,7 @@ def check_assignment(self, lvalue: Node, rvalue: Node, force_rvalue_type: Type=N self.check_assignment(lvalue.expr, rvalue) elif isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr): - assert not force_rvalue_type or isinstance(force_rvalue_type, TuppleType) + assert not force_rvalue_type or isinstance(force_rvalue_type, TupleType) rvalue = self.remove_parens(rvalue) @@ -1033,7 +1033,7 @@ def check_multi_assignment(self, lvalues: List[Node], lvalue_type)) for lv, rv_type in zip(lvalues, rvalue_type.items): - self.check_assignment(lv, self.temp_node(rv_type), rv_type) + self.check_assignment(lv, self.temp_node(rv_type, context), rv_type) elif (is_subtype(rvalue_type, self.named_generic_type('typing.Iterable', @@ -1042,7 +1042,7 @@ def check_multi_assignment(self, lvalues: List[Node], # Rvalue is iterable. item_type = self.iterable_item_type(cast(Instance, rvalue_type)) for lv in lvalues: - self.check_assignment(lv, self.temp_node(item_type), item_type) + self.check_assignment(lv, self.temp_node(item_type, context), item_type) else: self.fail(msg, context) @@ -1449,30 +1449,30 @@ def analyse_index_variables(self, index: List[NameExpr], item_type: Type, context: Context) -> None: """Type check or infer for loop or list comprehension index vars.""" if not is_annotated: - - # TODO FIX temp: - - return - - - # Create a temporary copy of variables with Node item type. # TODO this is ugly node_index = [] # type: List[Node] for i in index: node_index.append(i) - self.check_assignments(node_index, - self.temp_node(item_type, context)) + # TODO SK fix + + if len(node_index) == 1: + self.check_assignment(node_index[0], self.temp_node(item_type, context), context) + else: + self.check_multi_assignment(node_index, + self.temp_node(item_type, context), + context) elif len(index) == 1: v = cast(Var, index[0].node) if v.type: self.check_single_assignment(v.type, - self.temp_node(item_type), context, + self.temp_node(item_type, context), + context, messages.INCOMPATIBLE_TYPES_IN_FOR) else: - # TODO FIX temp: + # TODO SK FIX temp: return @@ -1486,7 +1486,7 @@ def analyse_index_variables(self, index: List[NameExpr], else: t.append(AnyType()) self.check_multi_assignment(t, [None] * len(index), - self.temp_node(item_type), context, + self.temp_node(item_type, context), context, messages.INCOMPATIBLE_TYPES_IN_FOR) def visit_del_stmt(self, s: DelStmt) -> Type: diff --git a/mypy/test/data/check-inference.test b/mypy/test/data/check-inference.test index e320b8fe5308..3f1716e7df4a 100644 --- a/mypy/test/data/check-inference.test +++ b/mypy/test/data/check-inference.test @@ -711,7 +711,7 @@ class A: pass [case testMultipleAssignmentWithPartialDefinition2] from typing import Undefined a = Undefined # type: A -a, x = [a] +a, x = [a, a] x = a a = x x = object() # E: Incompatible types in assignment (expression has type "object", variable has type "A") From 96e90a34a5d3eaf8c3c55e98eead2f723ac4ce27 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 24 Sep 2014 17:52:44 +0200 Subject: [PATCH 029/144] Removed is_annotated and types-field from ForStmt node --- mypy/checker.py | 56 ++++++++++++------------------------------- mypy/checkexpr.py | 2 +- mypy/nodes.py | 13 +--------- mypy/output.py | 1 - mypy/parse.py | 2 +- mypy/semanal.py | 13 ---------- mypy/strconv.py | 2 -- mypy/treetransform.py | 3 +-- 8 files changed, 19 insertions(+), 73 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 8fbc625cb98d..7ff607d6fd33 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1403,7 +1403,7 @@ def check_exception_type(self, type: Type, context: Context) -> Type: def visit_for_stmt(self, s: ForStmt) -> Type: """Type check a for statement.""" item_type = self.analyse_iterable_item_type(s.expr) - self.analyse_index_variables(s.index, s.is_annotated(), item_type, s) + self.analyse_index_variables(s.index, item_type, s) self.binder.push_frame() self.binder.push_loop_frame() self.accept_in_frame(s.body, repeat_till_fixed=True) @@ -1445,49 +1445,23 @@ def analyse_iterable_item_type(self, expr: Node) -> Type: return echk.check_call(method, [], [], expr)[0] def analyse_index_variables(self, index: List[NameExpr], - is_annotated: bool, item_type: Type, context: Context) -> None: """Type check or infer for loop or list comprehension index vars.""" - if not is_annotated: - # Create a temporary copy of variables with Node item type. - # TODO this is ugly - node_index = [] # type: List[Node] - for i in index: - node_index.append(i) - - # TODO SK fix - - if len(node_index) == 1: - self.check_assignment(node_index[0], self.temp_node(item_type, context), context) - else: - self.check_multi_assignment(node_index, - self.temp_node(item_type, context), - context) - elif len(index) == 1: - v = cast(Var, index[0].node) - if v.type: - self.check_single_assignment(v.type, - self.temp_node(item_type, context), - context, - messages.INCOMPATIBLE_TYPES_IN_FOR) - else: - - # TODO SK FIX temp: + # Create a temporary copy of variables with Node item type. + # TODO this is ugly + node_index = [] # type: List[Node] + for i in index: + node_index.append(i) - return - - - - t = [] # type: List[Type] - for ii in index: - v = cast(Var, ii.node) - if v.type: - t.append(v.type) - else: - t.append(AnyType()) - self.check_multi_assignment(t, [None] * len(index), - self.temp_node(item_type, context), context, - messages.INCOMPATIBLE_TYPES_IN_FOR) + # TODO SK fix + + if len(node_index) == 1: + self.check_assignment(node_index[0], self.temp_node(item_type, context), context) + else: + self.check_multi_assignment(node_index, + self.temp_node(item_type, context), + context) + def visit_del_stmt(self, s: DelStmt) -> Type: if isinstance(s.expr, IndexExpr): diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 5663cfe60545..576318857f7f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1189,7 +1189,7 @@ def check_generator_or_comprehension(self, gen: GeneratorExpr, for index, sequence, conditions in zip(gen.indices, gen.sequences, gen.condlists): sequence_type = self.chk.analyse_iterable_item_type(sequence) - self.chk.analyse_index_variables(index, False, sequence_type, gen) + self.chk.analyse_index_variables(index, sequence_type, gen) for condition in conditions: self.accept(condition) self.chk.binder.pop_frame() diff --git a/mypy/nodes.py b/mypy/nodes.py index ecfea8adaa90..ba403157a537 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -568,32 +568,21 @@ def accept(self, visitor: NodeVisitor[T]) -> T: class ForStmt(Node): # Index variables index = Undefined(List['NameExpr']) - # Index variable types (each may be None) - types = Undefined(List['mypy.types.Type']) # Expression to iterate expr = Undefined(Node) body = Undefined(Block) else_body = Undefined(Block) def __init__(self, index: List['NameExpr'], expr: Node, body: Block, - else_body: Block, - types: List['mypy.types.Type'] = None) -> None: + else_body: Block) -> None: self.index = index self.expr = expr self.body = body self.else_body = else_body - self.types = types def accept(self, visitor: NodeVisitor[T]) -> T: return visitor.visit_for_stmt(self) - def is_annotated(self) -> bool: - ann = False - for t in self.types: - if t is not None: - ann = True - return ann - class ReturnStmt(Node): expr = Undefined(Node) # Expression or None diff --git a/mypy/output.py b/mypy/output.py index 020677f8d0f8..1f3302927dd0 100644 --- a/mypy/output.py +++ b/mypy/output.py @@ -263,7 +263,6 @@ def visit_for_stmt(self, o): r = o.repr self.token(r.for_tok) for i in range(len(o.index)): - self.type(o.types[i]) self.node(o.index[i]) self.token(r.commas[i]) self.token(r.in_tok) diff --git a/mypy/parse.py b/mypy/parse.py index 0f69c9988e39..6ce46b917e6b 100755 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -862,7 +862,7 @@ def parse_for_stmt(self) -> ForStmt: else_body = None else_tok = none - node = ForStmt(index, expr, body, else_body, types) + node = ForStmt(index, expr, body, else_body) self.set_repr(node, noderepr.ForStmtRepr(for_tok, commas, in_tok, else_tok)) return node diff --git a/mypy/semanal.py b/mypy/semanal.py index 3b3727e07b36..2a7c735ed358 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1022,19 +1022,6 @@ def visit_for_stmt(self, s: ForStmt) -> None: for n in s.index: self.analyse_lvalue(n) - # Analyze index variable types. - for i in range(len(s.types)): - t = s.types[i] - if t: - s.types[i] = self.anal_type(t) - v = cast(Var, s.index[i].node) - # TODO check if redefinition - v.type = s.types[i] - - # Report error if only some of the loop variables have annotations. - if s.types != [None] * len(s.types) and None in s.types: - self.fail('Cannot mix unannotated and annotated loop variables', s) - self.loop_depth += 1 self.visit_block(s.body) self.loop_depth -= 1 diff --git a/mypy/strconv.py b/mypy/strconv.py index 22ed8bfc7cb1..771674c26626 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -197,8 +197,6 @@ def visit_while_stmt(self, o): def visit_for_stmt(self, o): a = [o.index] - if o.types != [None] * len(o.types): - a += o.types a.extend([o.expr, o.body]) if o.else_body: a.append(('Else', o.else_body.body)) diff --git a/mypy/treetransform.py b/mypy/treetransform.py index b16b6d2e7ca6..b794b1356908 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -207,8 +207,7 @@ def visit_for_stmt(self, node: ForStmt) -> Node: return ForStmt(self.names(node.index), self.node(node.expr), self.block(node.body), - self.optional_block(node.else_body), - self.optional_types(node.types)) + self.optional_block(node.else_body)) def visit_return_stmt(self, node: ReturnStmt) -> Node: return ReturnStmt(self.optional_node(node.expr)) From 7ac849e1be2ddb06fc4bdcc75f62aaaf844c532d Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 24 Sep 2014 18:53:50 +0200 Subject: [PATCH 030/144] Fixed type inference for assignment of Any to tuples --- mypy/checker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 7ff607d6fd33..ad2082c55098 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1004,7 +1004,8 @@ def check_multi_assignment(self, lvalues: List[Node], undefined_rvalue = False if isinstance(rvalue_type, AnyType): - pass + for lv in lvalues: + self.check_assignment(lv, self.temp_node(AnyType(), context)) elif isinstance(rvalue_type, TupleType): # Rvalue with tuple type. if len(rvalue_type.items) != len(lvalues): From bc0b29066aa0411045409afb1f00cdeacdba13dc Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 24 Sep 2014 20:03:09 +0200 Subject: [PATCH 031/144] Fixed/extended failing testcases --- mypy/test/data/semanal-errors.test | 9 +++++++-- mypy/test/data/typexport-basic.test | 1 - 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mypy/test/data/semanal-errors.test b/mypy/test/data/semanal-errors.test index 0d7407355339..af0a89b05e66 100644 --- a/mypy/test/data/semanal-errors.test +++ b/mypy/test/data/semanal-errors.test @@ -361,12 +361,17 @@ main, line 4: Invalid assignment target [case testInvalidLvalues2] x = y = z = 1 -x, (y, z) = 1 -x, [y, z] = 1 +x, (y, 1) = 1 +x, [y, 1] = 1 +x, [y, [z, 1]] = 1 +x, (y, (z, 1)) = 1 x, (y) = 1 # ok +x, (y, (z, z)) = 1 # ok [out] main, line 2: Invalid assignment target main, line 3: Invalid assignment target +main, line 4: Invalid assignment target +main, line 5: Invalid assignment target [case testInvalidLvalues3] x = 1 diff --git a/mypy/test/data/typexport-basic.test b/mypy/test/data/typexport-basic.test index 80c00d3c765e..c093691a53e7 100644 --- a/mypy/test/data/typexport-basic.test +++ b/mypy/test/data/typexport-basic.test @@ -661,7 +661,6 @@ NameExpr(6) : A[B] NameExpr(6) : A[C] NameExpr(6) : B NameExpr(6) : C -TupleExpr(6) : Tuple[A[B], A[C]] NameExpr(7) : A[B] NameExpr(8) : A[C] From 7d72577b100e8e34953be60005a379ad0be79097 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 24 Sep 2014 21:12:59 +0200 Subject: [PATCH 032/144] Changed error message when there is a mismatch between expected and provided values to unpack in assignment to match the python error message --- mypy/checker.py | 5 +++-- mypy/messages.py | 9 +++++++++ mypy/test/data/check-inference.test | 6 +++--- mypy/test/data/check-tuples.test | 4 ++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index ad2082c55098..a9348ec1e746 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1009,8 +1009,9 @@ def check_multi_assignment(self, lvalues: List[Node], elif isinstance(rvalue_type, TupleType): # Rvalue with tuple type. if len(rvalue_type.items) != len(lvalues): - self.msg.incompatible_value_count_in_assignment( - len(lvalues), len(rvalue_type.items), context) + #self.msg.incompatible_value_count_in_assignment( + # len(lvalues), len(rvalue_type.items), context) + self.msg.wrong_number_values_to_unpack(len(rvalue_type.items), len(lvalues), context) else: if not undefined_rvalue: # Create lvalue_type for type inference diff --git a/mypy/messages.py b/mypy/messages.py index f388f21bd0a1..e24f363671f3 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -454,6 +454,15 @@ def invalid_cast(self, target_type: Type, source_type: Type, self.fail('Cannot cast from {} to {}'.format( self.format(source_type), self.format(target_type)), context) + def wrong_number_values_to_unpack(self, provided: int, expected: int, context: Context) -> None: + if provided < expected: + if provided == 1: + self.fail('Need more than 1 value to unpack ({} expected)'.format(expected), context) + else: + self.fail('Need more than {} values to unpack ({} expected)'.format(provided, expected), context) + elif provided > expected: + self.fail('Too many values to unpack ({} expected, {} provided)'.format(expected, provided), context) + def incompatible_operator_assignment(self, op: str, context: Context) -> None: self.fail('Result type of {} incompatible in assignment'.format(op), diff --git a/mypy/test/data/check-inference.test b/mypy/test/data/check-inference.test index 3f1716e7df4a..8f7afa7f87fc 100644 --- a/mypy/test/data/check-inference.test +++ b/mypy/test/data/check-inference.test @@ -215,8 +215,8 @@ def f() -> None: [builtins fixtures/tuple.py] [out] main: In function "f": -main, line 5: Too many values to assign -main, line 6: Need 4 values to assign +main, line 5: Too many values to unpack (2 expected, 3 provided) +main, line 6: Need more than 3 values to unpack (4 expected) [case testInvalidRvalueTypeInInferredMultipleLvarDefinition] import typing @@ -639,7 +639,7 @@ class B: pass [out] main, line 4: Incompatible types in assignment (expression has type "A", variable has type "B") main, line 5: Incompatible types in assignment (expression has type "B", variable has type "A") -main, line 8: Need 3 values to assign +main, line 8: Need more than 2 values to unpack (3 expected) main, line 10: Need type annotation for variable [case testInferenceOfFor3] diff --git a/mypy/test/data/check-tuples.test b/mypy/test/data/check-tuples.test index 12475816b3e1..46339f99757c 100644 --- a/mypy/test/data/check-tuples.test +++ b/mypy/test/data/check-tuples.test @@ -219,8 +219,8 @@ from typing import Undefined, Tuple t1 = Undefined # type: Tuple[A, A, A] a = Undefined # type: A -a, a = t1 # E: Too many values to assign -a, a, a, a = t1 # E: Need 4 values to assign +a, a = t1 # E: Too many values to unpack (2 expected, 3 provided) +a, a, a, a = t1 # E: Need more than 3 values to unpack (4 expected) a, a, a = t1 From a3e23fa09604cceb7d3b96032f98e60927cdec14 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 24 Sep 2014 21:22:19 +0200 Subject: [PATCH 033/144] Added tests for nested tuple assignments --- mypy/test/data/check-tuples.test | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/mypy/test/data/check-tuples.test b/mypy/test/data/check-tuples.test index 46339f99757c..7dc7a1244053 100644 --- a/mypy/test/data/check-tuples.test +++ b/mypy/test/data/check-tuples.test @@ -360,6 +360,54 @@ a = '' # E: Incompatible types in assignment (expression has type "str", variabl b = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "str") +-- Nested tuple assignment +-- ---------------------------- + +[case testNestedTupleAssignment1] +from typing import Undefined +a1, b1, c1 = Undefined, Undefined, Undefined # type: (A, B, C) +a2, b2, c2 = Undefined, Undefined, Undefined # type: (A, B, C) + +a1, (b1, c1) = a2, (b2, c2) +a1, (a1, (b1, c1)) = a2, (a2, (b2, c2)) +a1, (a1, (a1, b1)) = a1, (a1, (a1, c1)) # Fail + +class A: pass +class B: pass +class C: pass +[out] +main, line 7: Incompatible types in assignment (expression has type "C", variable has type "B") + +[case testNestedTupleAssignment2] +from typing import Undefined +a1, b1, c1 = Undefined, Undefined, Undefined # type: (A, B, C) +a2, b2, c2 = Undefined, Undefined, Undefined # type: (A, B, C) +t = a1, b1 + +a2, b2 = t +(a2, b2), c2 = t, c1 +(a2, c2), c2 = t, c1 # Fail +t, c2 = (a2, b2), c2 +t, c2 = (a2, a2), c2 # Fail +t = a1, a1, a1 # Fail +t = a1 # Fail +a2, a2, a2 = t # Fail +a2, = t # Fail +a2 = t # Fail + +class A: pass +class B: pass +class C: pass +[out] +main, line 8: Incompatible types in assignment (expression has type "B", variable has type "C") +main, line 10: Incompatible types in assignment (expression has type "Tuple[A, A]", variable has type "Tuple[A, B]") +main, line 11: Incompatible types in assignment (expression has type "Tuple[A, A, A]", variable has type "Tuple[A, B]") +main, line 12: Incompatible types in assignment (expression has type "A", variable has type "Tuple[A, B]") +main, line 13: Need more than 2 values to unpack (3 expected) +main, line 14: Too many values to unpack (1 expected, 2 provided) +main, line 15: Incompatible types in assignment (expression has type "Tuple[A, B]", variable has type "A") + + -- Error messages -- -------------- From 2fed4cef880ce34b2350af0336fe9efd0c57d2ef Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Fri, 26 Sep 2014 19:49:10 +0200 Subject: [PATCH 034/144] Added inference tests for (nested) tuple assignments --- mypy/test/data/check-inference.test | 99 +++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/mypy/test/data/check-inference.test b/mypy/test/data/check-inference.test index 8f7afa7f87fc..8803b686c002 100644 --- a/mypy/test/data/check-inference.test +++ b/mypy/test/data/check-inference.test @@ -194,6 +194,59 @@ class B: pass [out] main: In function "f": +[case testInferringLvarTypesInTupleAssignment] +from typing import Undefined, Tuple +def f() -> None: + t = Undefined # type: Tuple[A, B] + a, b = t + a = b # E: Incompatible types in assignment (expression has type "B", variable has type "A") + a = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") + b = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") + + a = A() + b = B() + +class A: pass +class B: pass +[out] +main: In function "f": + +[case testInferringLvarTypesInNestedTupleAssignment] +from typing import Undefined, Tuple +def f() -> None: + t = Undefined # type: Tuple[A, B] + a1, (a, b) = A(), t + a = b # E: Incompatible types in assignment (expression has type "B", variable has type "A") + a = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") + b = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") + + a = A() + b = B() + +class A: pass +class B: pass +[out] +main: In function "f": + +[case testInferringLvarTypesInNestedTupleAssignment] +import typing +def f() -> None: + a, (b, c) = A(), (B(), C()) + a = b # E: Incompatible types in assignment (expression has type "B", variable has type "A") + a = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") + b = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") + c = A() # E: Incompatible types in assignment (expression has type "A", variable has type "C") + + a = A() + b = B() + c = C() + +class A: pass +class B: pass +class C: pass +[out] +main: In function "f": + [case testInferringLvarTypesInMultiDefWithNoneTypes] import typing def f() -> None: @@ -204,6 +257,15 @@ class A: pass [out] main: In function "f": +[case testInferringLvarTypesInNestedTupleAssignmentWithNoneTypes] +import typing +def f() -> None: + a1, (a2, b) = A(), (A(), None) # E: Need type annotation for variable + +class A: pass +[out] +main: In function "f": + [case testInferringLvarTypesInMultiDefWithInvalidTuple] from typing import Undefined, Tuple t = Undefined # type: Tuple[object, object, object] @@ -228,6 +290,16 @@ class A: pass [out] main: In function "f": +[case testInvalidRvalueTypeInInferredNestedTupleAssignment] +import typing +def f() -> None: + a1, (a2, b) = A(), f # E: Incompatible types in assignment + a3, (c, d) = A(), A() # E: Incompatible types in assignment +class A: pass +[builtins fixtures/for.py] +[out] +main: In function "f": + [case testInferringMultipleLvarDefinitionWithListRvalue] from typing import List @@ -255,6 +327,33 @@ def f() -> None: [out] main: In function "f": +[case testInferringNestedTupleAssignmentWithListRvalue] +from typing import List + +class C: pass +class D: pass + +def f() -> None: + c1, (a, b) = C(), List[C]() + c2, (c, d, e) = C(), List[D]() + a = D() # E: Incompatible types in assignment (expression has type "D", variable has type "C") + b = D() # E: Incompatible types in assignment (expression has type "D", variable has type "C") + c = C() # E: Incompatible types in assignment (expression has type "C", variable has type "D") + b = c # E: Incompatible types in assignment (expression has type "D", variable has type "C") + + a = C() + b = C() + c = D() + d = D() + e = D() + + a = b + c = d + d = e +[builtins fixtures/for.py] +[out] +main: In function "f": + [case testInferringMultipleLvarDefinitionWithImplicitDynamicRvalue] import typing def f() -> None: From a6dd66c991dac7ea574f8c90c89a5fb94ddaefbe Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Fri, 26 Sep 2014 20:58:04 +0200 Subject: [PATCH 035/144] Fixed (type) errors --- mypy/checker.py | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index a9348ec1e746..9d3a91b76122 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -390,12 +390,10 @@ def visit_var_def(self, defn: VarDef) -> Type: defn.init, defn.init) else: # Multiple assignment. - lvt = List[Type]() + lv = List[Node]() for v in defn.items: - lvt.append(v.type) - self.check_multi_assignment( - lvt, [None] * len(lvt), - defn.init, defn.init) + lv.append(self.temp_node(v.type, v)) + self.check_multi_assignment(lv, defn.init, defn.init) else: init_type = self.accept(defn.init) if defn.kind == LDEF and not defn.is_top_level: @@ -929,13 +927,11 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> Type: self.check_assignment(lv, rvalue, s.type) def check_assignment(self, lvalue: Node, rvalue: Node, force_rvalue_type: Type=None) -> None: - if isinstance(lvalue, ParenExpr): self.check_assignment(lvalue.expr, rvalue) elif isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr): - assert not force_rvalue_type or isinstance(force_rvalue_type, TupleType) - + ltuple = cast(Union[TupleExpr, ListExpr], lvalue) rvalue = self.remove_parens(rvalue) if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr): @@ -945,25 +941,26 @@ def check_assignment(self, lvalue: Node, rvalue: Node, force_rvalue_type: Type=N # type List[object] rtuple = cast(Union[TupleExpr, ListExpr], rvalue) - ltuple = cast(Union[TupleExpr, ListExpr], lvalue) if len(rtuple.items) != len(ltuple.items): self.msg.incompatible_value_count_in_assignment( len(ltuple.items), len(rtuple.items), lvalue) else: - force_rvalue_types = None - if force_rvalue_type: - assert len(rtuple.items) == len(force_rvalue_type.items) - force_rvalue_types = force_rvalue_type.items + assert not force_rvalue_type or isinstance(force_rvalue_type, TupleType) + force_rvalue_tuple_type = cast(TupleType, force_rvalue_type) + + force_rvalue_types = None # type: List[Type] + if force_rvalue_tuple_type: + assert len(rtuple.items) == len(force_rvalue_tuple_type.items) + force_rvalue_types = force_rvalue_tuple_type.items else: force_rvalue_types = [None] * len(rtuple.items) for lv, rv, forced_type in zip(ltuple.items, rtuple.items, force_rvalue_types): self.check_assignment(lv, rv, forced_type) else: - lvalues = lvalue.items # unpack tuple or list expr + lvalues = ltuple.items # unpack tuple or list expr self.check_multi_assignment(lvalues, rvalue, lvalue) - else: lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) @@ -1009,8 +1006,6 @@ def check_multi_assignment(self, lvalues: List[Node], elif isinstance(rvalue_type, TupleType): # Rvalue with tuple type. if len(rvalue_type.items) != len(lvalues): - #self.msg.incompatible_value_count_in_assignment( - # len(lvalues), len(rvalue_type.items), context) self.msg.wrong_number_values_to_unpack(len(rvalue_type.items), len(lvalues), context) else: if not undefined_rvalue: @@ -1019,7 +1014,7 @@ def check_multi_assignment(self, lvalues: List[Node], type_parameters = [] # type: List[Type] for i in range(len(lvalues)): - sub_lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalues[i]) + sub_lvalue_type, index_expr, inferred = self.check_lvalue(lvalues[i]) if sub_lvalue_type: type_parameters.append(sub_lvalue_type) @@ -1049,10 +1044,10 @@ def check_multi_assignment(self, lvalues: List[Node], self.fail(msg, context) - def check_lvalue(self, lvalue: Node) -> None: - lvalue_type = None - index_lvalue = None - inferred = None + def check_lvalue(self, lvalue: Node) -> Tuple[Type, IndexExpr, Var]: + lvalue_type = None # type: Type + index_lvalue = None # type: IndexExpr + inferred = None # type: Var if self.is_definition(lvalue): is_inferred = True @@ -1455,16 +1450,13 @@ def analyse_index_variables(self, index: List[NameExpr], for i in index: node_index.append(i) - # TODO SK fix - if len(node_index) == 1: - self.check_assignment(node_index[0], self.temp_node(item_type, context), context) + self.check_assignment(node_index[0], self.temp_node(item_type, context)) else: self.check_multi_assignment(node_index, self.temp_node(item_type, context), context) - def visit_del_stmt(self, s: DelStmt) -> Type: if isinstance(s.expr, IndexExpr): e = cast(IndexExpr, s.expr) # Cast From 340f07c232e5d6adf76fddf137490052d6255f6b Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Fri, 26 Sep 2014 21:38:33 +0200 Subject: [PATCH 036/144] Refactoring --- mypy/checker.py | 159 ++++++++++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 73 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 9d3a91b76122..2f66463793cb 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -386,7 +386,7 @@ def visit_var_def(self, defn: VarDef) -> Type: if defn.items[0].type: # Explicit types. if len(defn.items) == 1: - self.check_single_assignment(defn.items[0].type, + self.check_simple_assignment(defn.items[0].type, defn.init, defn.init) else: # Multiple assignment. @@ -927,45 +927,20 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> Type: self.check_assignment(lv, rvalue, s.type) def check_assignment(self, lvalue: Node, rvalue: Node, force_rvalue_type: Type=None) -> None: + """Type check a single assignment: lvalue = rvalue + """ if isinstance(lvalue, ParenExpr): - self.check_assignment(lvalue.expr, rvalue) - + self.check_assignment(lvalue.expr, rvalue, force_rvalue_type) elif isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr): ltuple = cast(Union[TupleExpr, ListExpr], lvalue) rvalue = self.remove_parens(rvalue) - if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr): - # Recursively go into Tuple or List expression rhs instead of - # using the type of rhs, because this allowed more fine grained - # control in cases like: a, b = [int, str] where rhs would get - # type List[object] - - rtuple = cast(Union[TupleExpr, ListExpr], rvalue) - - if len(rtuple.items) != len(ltuple.items): - self.msg.incompatible_value_count_in_assignment( - len(ltuple.items), len(rtuple.items), lvalue) - else: - assert not force_rvalue_type or isinstance(force_rvalue_type, TupleType) - force_rvalue_tuple_type = cast(TupleType, force_rvalue_type) - - force_rvalue_types = None # type: List[Type] - if force_rvalue_tuple_type: - assert len(rtuple.items) == len(force_rvalue_tuple_type.items) - force_rvalue_types = force_rvalue_tuple_type.items - else: - force_rvalue_types = [None] * len(rtuple.items) - - for lv, rv, forced_type in zip(ltuple.items, rtuple.items, force_rvalue_types): - self.check_assignment(lv, rv, forced_type) - else: - lvalues = ltuple.items # unpack tuple or list expr - self.check_multi_assignment(lvalues, rvalue, lvalue) + self.check_assignment_to_multiple_lvalues(ltuple.items, rvalue, lvalue, force_rvalue_type) else: lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) if lvalue_type: - rvalue_type = self.check_single_assignment(lvalue_type, rvalue, rvalue) + rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, rvalue) if rvalue_type and not force_rvalue_type: self.binder.assign_type(lvalue, rvalue_type) @@ -975,6 +950,35 @@ def check_assignment(self, lvalue: Node, rvalue: Node, force_rvalue_type: Type=N if inferred: self.infer_variable_type(inferred, lvalue, self.accept(rvalue), rvalue) + + def check_assignment_to_multiple_lvalues(self, lvalues: List[Node], rvalue: Node, context: Context, + force_rvalue_type: Type=None) -> None: + if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr): + # Recursively go into Tuple or List expression rhs instead of + # using the type of rhs, because this allowed more fine grained + # control in cases like: a, b = [int, str] where rhs would get + # type List[object] + + rtuple = cast(Union[TupleExpr, ListExpr], rvalue) + + if len(rtuple.items) != len(lvalues): + self.msg.incompatible_value_count_in_assignment( + len(lvalues), len(rtuple.items), context) + else: + assert not force_rvalue_type or isinstance(force_rvalue_type, TupleType) + force_rvalue_tuple_type = cast(TupleType, force_rvalue_type) + + force_rvalue_types = None # type: List[Type] + if force_rvalue_tuple_type: + assert len(rtuple.items) == len(force_rvalue_tuple_type.items) + force_rvalue_types = force_rvalue_tuple_type.items + else: + force_rvalue_types = [None] * len(rtuple.items) + + for lv, rv, forced_type in zip(lvalues, rtuple.items, force_rvalue_types): + self.check_assignment(lv, rv, forced_type) + else: + self.check_multi_assignment(lvalues, rvalue, context) def remove_parens(self, node: Node) -> Node: if isinstance(node, ParenExpr): @@ -985,9 +989,10 @@ def remove_parens(self, node: Node) -> Node: def check_multi_assignment(self, lvalues: List[Node], rvalue: Node, context: Context, - msg: str = None) -> List[Type]: - '''Check the assignment of one rvalue to a number of lvalues - for example from a ListExpr or TupleExpr''' + msg: str = None) -> None: + """Check the assignment of one rvalue to a number of lvalues + for example from a ListExpr or TupleExpr + """ if not msg: msg = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT @@ -1004,45 +1009,53 @@ def check_multi_assignment(self, lvalues: List[Node], for lv in lvalues: self.check_assignment(lv, self.temp_node(AnyType(), context)) elif isinstance(rvalue_type, TupleType): - # Rvalue with tuple type. - if len(rvalue_type.items) != len(lvalues): - self.msg.wrong_number_values_to_unpack(len(rvalue_type.items), len(lvalues), context) - else: - if not undefined_rvalue: - # Create lvalue_type for type inference - # TODO do this better - - type_parameters = [] # type: List[Type] - for i in range(len(lvalues)): - sub_lvalue_type, index_expr, inferred = self.check_lvalue(lvalues[i]) - - if sub_lvalue_type: - type_parameters.append(sub_lvalue_type) - else: # index lvalue - # TODO Figure out more precise type context, probably - # based on the type signature of the _set method. - type_parameters.append(rvalue_type.items[i]) - - lvalue_type = TupleType(type_parameters) - - # Infer rvalue again, now in the correct type context. - rvalue_type = cast(TupleType, self.accept(rvalue, - lvalue_type)) - - for lv, rv_type in zip(lvalues, rvalue_type.items): - self.check_assignment(lv, self.temp_node(rv_type, context), rv_type) - - elif (is_subtype(rvalue_type, - self.named_generic_type('typing.Iterable', - [AnyType()])) and - isinstance(rvalue_type, Instance)): - # Rvalue is iterable. - item_type = self.iterable_item_type(cast(Instance, rvalue_type)) - for lv in lvalues: - self.check_assignment(lv, self.temp_node(item_type, context), item_type) + self.check_multi_assignment_from_tuple(lvalues, rvalue, cast(TupleType, rvalue_type), + context, undefined_rvalue) + elif self.type_is_iterable(rvalue_type): + self.check_multi_assignment_from_iterable(lvalues, cast(Instance, rvalue_type), context) else: self.fail(msg, context) - + + def check_multi_assignment_from_tuple(self, lvalues: List[Node], rvalue: Node, + rvalue_type: TupleType, context: Context, + undefined_rvalue: bool) -> None: + if len(rvalue_type.items) != len(lvalues): + self.msg.wrong_number_values_to_unpack(len(rvalue_type.items), len(lvalues), context) + else: + if not undefined_rvalue: + # Create lvalue_type for type inference + # TODO do this better + + type_parameters = [] # type: List[Type] + for i in range(len(lvalues)): + sub_lvalue_type, index_expr, inferred = self.check_lvalue(lvalues[i]) + + if sub_lvalue_type: + type_parameters.append(sub_lvalue_type) + else: # index lvalue + # TODO Figure out more precise type context, probably + # based on the type signature of the _set method. + type_parameters.append(rvalue_type.items[i]) + + lvalue_type = TupleType(type_parameters) + + # Infer rvalue again, now in the correct type context. + rvalue_type = cast(TupleType, self.accept(rvalue, + lvalue_type)) + + for lv, rv_type in zip(lvalues, rvalue_type.items): + self.check_assignment(lv, self.temp_node(rv_type, context), rv_type) + + def type_is_iterable(self, type: Type) -> bool: + return (is_subtype(type, self.named_generic_type('typing.Iterable', + [AnyType()])) and + isinstance(type, Instance)) + + def check_multi_assignment_from_iterable(self, lvalues: List[Node], rvalue_type: Instance, + context: Context) -> None: + item_type = self.iterable_item_type(rvalue_type) + for lv in lvalues: + self.check_assignment(lv, self.temp_node(item_type, context), item_type) def check_lvalue(self, lvalue: Node) -> Tuple[Type, IndexExpr, Var]: lvalue_type = None # type: Type @@ -1050,7 +1063,6 @@ def check_lvalue(self, lvalue: Node) -> Tuple[Type, IndexExpr, Var]: inferred = None # type: Var if self.is_definition(lvalue): - is_inferred = True if isinstance(lvalue, NameExpr): inferred = cast(Var, lvalue.node) else: @@ -1140,9 +1152,10 @@ def narrow_type_from_binder(self, expr: Node, known_type: Type) -> Type: return ans return known_type - def check_single_assignment(self, lvalue_type: Type, rvalue: Node, + def check_simple_assignment(self, lvalue_type: Type, rvalue: Node, context: Node, msg: str = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT) -> Type: + """Checks the assignment of rvalue to a lvalue of type lvalue_type""" if refers_to_fullname(rvalue, 'typing.Undefined'): # The rvalue is just 'Undefined'; this is always valid. # Infer the type of 'Undefined' from the lvalue type. From 8afc166e793c6c59e099d89589a1d005226deb10 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sat, 27 Sep 2014 16:42:31 +0200 Subject: [PATCH 037/144] Replaced force_rvalue_type with (semantically clearer) infer_lvalue_type parameter and propagate it to all check_assignmnent* functions --- mypy/checker.py | 53 ++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 2f66463793cb..85774738c4cd 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -917,32 +917,32 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> Type: Handle all kinds of assignment statements (simple, indexed, multiple). """ - self.check_assignment(s.lvalues[-1], s.rvalue, s.type) + self.check_assignment(s.lvalues[-1], s.rvalue, s.type == None) if len(s.lvalues) > 1: # Chained assignment (e.g. x = y = ...). # Make sure that rvalue type will not be reinferred. rvalue = self.temp_node(self.type_map[s.rvalue], s) for lv in s.lvalues[:-1]: - self.check_assignment(lv, rvalue, s.type) + self.check_assignment(lv, rvalue, s.type == None) - def check_assignment(self, lvalue: Node, rvalue: Node, force_rvalue_type: Type=None) -> None: + def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool=True) -> None: """Type check a single assignment: lvalue = rvalue """ if isinstance(lvalue, ParenExpr): - self.check_assignment(lvalue.expr, rvalue, force_rvalue_type) + self.check_assignment(lvalue.expr, rvalue, infer_lvalue_type) elif isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr): ltuple = cast(Union[TupleExpr, ListExpr], lvalue) rvalue = self.remove_parens(rvalue) - self.check_assignment_to_multiple_lvalues(ltuple.items, rvalue, lvalue, force_rvalue_type) + self.check_assignment_to_multiple_lvalues(ltuple.items, rvalue, lvalue, infer_lvalue_type) else: lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) if lvalue_type: - rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, rvalue) + rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, lvalue) - if rvalue_type and not force_rvalue_type: + if rvalue_type and infer_lvalue_type: self.binder.assign_type(lvalue, rvalue_type) elif index_lvalue: self.check_indexed_assignment(index_lvalue, rvalue, rvalue) @@ -952,7 +952,7 @@ def check_assignment(self, lvalue: Node, rvalue: Node, force_rvalue_type: Type=N rvalue) def check_assignment_to_multiple_lvalues(self, lvalues: List[Node], rvalue: Node, context: Context, - force_rvalue_type: Type=None) -> None: + infer_lvalue_type: bool=True) -> None: if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr): # Recursively go into Tuple or List expression rhs instead of # using the type of rhs, because this allowed more fine grained @@ -965,20 +965,10 @@ def check_assignment_to_multiple_lvalues(self, lvalues: List[Node], rvalue: Node self.msg.incompatible_value_count_in_assignment( len(lvalues), len(rtuple.items), context) else: - assert not force_rvalue_type or isinstance(force_rvalue_type, TupleType) - force_rvalue_tuple_type = cast(TupleType, force_rvalue_type) - - force_rvalue_types = None # type: List[Type] - if force_rvalue_tuple_type: - assert len(rtuple.items) == len(force_rvalue_tuple_type.items) - force_rvalue_types = force_rvalue_tuple_type.items - else: - force_rvalue_types = [None] * len(rtuple.items) - - for lv, rv, forced_type in zip(lvalues, rtuple.items, force_rvalue_types): - self.check_assignment(lv, rv, forced_type) + for lv, rv in zip(lvalues, rtuple.items): + self.check_assignment(lv, rv, infer_lvalue_type) else: - self.check_multi_assignment(lvalues, rvalue, context) + self.check_multi_assignment(lvalues, rvalue, context, infer_lvalue_type) def remove_parens(self, node: Node) -> Node: if isinstance(node, ParenExpr): @@ -989,6 +979,7 @@ def remove_parens(self, node: Node) -> Node: def check_multi_assignment(self, lvalues: List[Node], rvalue: Node, context: Context, + infer_lvalue_type: Type=True, msg: str = None) -> None: """Check the assignment of one rvalue to a number of lvalues for example from a ListExpr or TupleExpr @@ -1007,18 +998,19 @@ def check_multi_assignment(self, lvalues: List[Node], if isinstance(rvalue_type, AnyType): for lv in lvalues: - self.check_assignment(lv, self.temp_node(AnyType(), context)) + self.check_assignment(lv, self.temp_node(AnyType(), context), infer_lvalue_type) elif isinstance(rvalue_type, TupleType): self.check_multi_assignment_from_tuple(lvalues, rvalue, cast(TupleType, rvalue_type), - context, undefined_rvalue) + context, undefined_rvalue, infer_lvalue_type) elif self.type_is_iterable(rvalue_type): - self.check_multi_assignment_from_iterable(lvalues, cast(Instance, rvalue_type), context) + self.check_multi_assignment_from_iterable(lvalues, cast(Instance, rvalue_type), + context, infer_lvalue_type) else: self.fail(msg, context) def check_multi_assignment_from_tuple(self, lvalues: List[Node], rvalue: Node, rvalue_type: TupleType, context: Context, - undefined_rvalue: bool) -> None: + undefined_rvalue: bool, infer_lvalue_type: bool=True) -> None: if len(rvalue_type.items) != len(lvalues): self.msg.wrong_number_values_to_unpack(len(rvalue_type.items), len(lvalues), context) else: @@ -1040,11 +1032,10 @@ def check_multi_assignment_from_tuple(self, lvalues: List[Node], rvalue: Node, lvalue_type = TupleType(type_parameters) # Infer rvalue again, now in the correct type context. - rvalue_type = cast(TupleType, self.accept(rvalue, - lvalue_type)) - + rvalue_type = cast(TupleType, self.accept(rvalue, lvalue_type)) + for lv, rv_type in zip(lvalues, rvalue_type.items): - self.check_assignment(lv, self.temp_node(rv_type, context), rv_type) + self.check_assignment(lv, self.temp_node(rv_type, context), infer_lvalue_type) def type_is_iterable(self, type: Type) -> bool: return (is_subtype(type, self.named_generic_type('typing.Iterable', @@ -1052,10 +1043,10 @@ def type_is_iterable(self, type: Type) -> bool: isinstance(type, Instance)) def check_multi_assignment_from_iterable(self, lvalues: List[Node], rvalue_type: Instance, - context: Context) -> None: + context: Context, infer_lvalue_type: bool=True) -> None: item_type = self.iterable_item_type(rvalue_type) for lv in lvalues: - self.check_assignment(lv, self.temp_node(item_type, context), item_type) + self.check_assignment(lv, self.temp_node(item_type, context), infer_lvalue_type) def check_lvalue(self, lvalue: Node) -> Tuple[Type, IndexExpr, Var]: lvalue_type = None # type: Type From ac89aade943426d9f151681f0fa99231067a25d9 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sat, 27 Sep 2014 19:55:54 +0200 Subject: [PATCH 038/144] Fixed check_lvalue to handle TupleExpr, ListExpr, and ParenExpr correctly --- mypy/checker.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 85774738c4cd..468cd09c5969 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1016,7 +1016,6 @@ def check_multi_assignment_from_tuple(self, lvalues: List[Node], rvalue: Node, else: if not undefined_rvalue: # Create lvalue_type for type inference - # TODO do this better type_parameters = [] # type: List[Type] for i in range(len(lvalues)): @@ -1069,6 +1068,11 @@ def check_lvalue(self, lvalue: Node) -> Tuple[Type, IndexExpr, Var]: elif isinstance(lvalue, NameExpr): lvalue_type = self.expr_checker.analyse_ref_expr(lvalue) self.store_type(lvalue, lvalue_type) + elif isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr): + types = [self.check_lvalue(sub_expr)[0] for sub_expr in lvalue.items] + lvalue_type = TupleType(types) + elif isinstance(lvalue, ParenExpr): + return self.check_lvalue(lvalue.expr) else: lvalue_type = self.accept(lvalue) From 62ffddf68335b1e3cb56053bbe26852f3d8d8745 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sun, 28 Sep 2014 19:34:48 +0200 Subject: [PATCH 039/144] Made error message more explicite when expecting a iterable to assign from --- mypy/checker.py | 19 ++++++++++--------- mypy/messages.py | 3 +++ mypy/test/data/check-inference.test | 8 ++++---- mypy/test/data/check-tuples.test | 12 ++++++------ 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 04799b8369ec..6118535ddab3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -978,7 +978,7 @@ def check_multi_assignment(self, lvalues: List[Node], infer_lvalue_type: bool=True, msg: str = None) -> None: """Check the assignment of one rvalue to a number of lvalues - for example from a ListExpr or TupleExpr + for example from a ListExpr or TupleExpr. """ if not msg: @@ -998,11 +998,9 @@ def check_multi_assignment(self, lvalues: List[Node], elif isinstance(rvalue_type, TupleType): self.check_multi_assignment_from_tuple(lvalues, rvalue, cast(TupleType, rvalue_type), context, undefined_rvalue, infer_lvalue_type) - elif self.type_is_iterable(rvalue_type): - self.check_multi_assignment_from_iterable(lvalues, cast(Instance, rvalue_type), - context, infer_lvalue_type) else: - self.fail(msg, context) + self.check_multi_assignment_from_iterable(lvalues, rvalue_type, + context, infer_lvalue_type) def check_multi_assignment_from_tuple(self, lvalues: List[Node], rvalue: Node, rvalue_type: TupleType, context: Context, @@ -1037,11 +1035,14 @@ def type_is_iterable(self, type: Type) -> bool: [AnyType()])) and isinstance(type, Instance)) - def check_multi_assignment_from_iterable(self, lvalues: List[Node], rvalue_type: Instance, + def check_multi_assignment_from_iterable(self, lvalues: List[Node], rvalue_type: Type, context: Context, infer_lvalue_type: bool=True) -> None: - item_type = self.iterable_item_type(rvalue_type) - for lv in lvalues: - self.check_assignment(lv, self.temp_node(item_type, context), infer_lvalue_type) + if self.type_is_iterable(rvalue_type): + item_type = self.iterable_item_type(cast(Instance,rvalue_type)) + for lv in lvalues: + self.check_assignment(lv, self.temp_node(item_type, context), infer_lvalue_type) + else: + self.msg.type_not_iterable(rvalue_type, context) def check_lvalue(self, lvalue: Node) -> Tuple[Type, IndexExpr, Var]: lvalue_type = None # type: Type diff --git a/mypy/messages.py b/mypy/messages.py index e24f363671f3..cde1e548f816 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -463,6 +463,9 @@ def wrong_number_values_to_unpack(self, provided: int, expected: int, context: C elif provided > expected: self.fail('Too many values to unpack ({} expected, {} provided)'.format(expected, provided), context) + def type_not_iterable(self, type: Type, context: Context) -> None: + self.fail('\'{}\' object is not iterable'.format(type), context) + def incompatible_operator_assignment(self, op: str, context: Context) -> None: self.fail('Result type of {} incompatible in assignment'.format(op), diff --git a/mypy/test/data/check-inference.test b/mypy/test/data/check-inference.test index 8803b686c002..73f88fbed606 100644 --- a/mypy/test/data/check-inference.test +++ b/mypy/test/data/check-inference.test @@ -283,8 +283,8 @@ main, line 6: Need more than 3 values to unpack (4 expected) [case testInvalidRvalueTypeInInferredMultipleLvarDefinition] import typing def f() -> None: - a, b = f # E: Incompatible types in assignment - c, d = A() # E: Incompatible types in assignment + a, b = f # E: 'def ()' object is not iterable + c, d = A() # E: '__main__.A' object is not iterable class A: pass [builtins fixtures/for.py] [out] @@ -293,8 +293,8 @@ main: In function "f": [case testInvalidRvalueTypeInInferredNestedTupleAssignment] import typing def f() -> None: - a1, (a2, b) = A(), f # E: Incompatible types in assignment - a3, (c, d) = A(), A() # E: Incompatible types in assignment + a1, (a2, b) = A(), f # E: 'def ()' object is not iterable + a3, (c, d) = A(), A() # E: '__main__.A' object is not iterable class A: pass [builtins fixtures/for.py] [out] diff --git a/mypy/test/data/check-tuples.test b/mypy/test/data/check-tuples.test index d4dab969e333..9628d9065904 100644 --- a/mypy/test/data/check-tuples.test +++ b/mypy/test/data/check-tuples.test @@ -265,8 +265,8 @@ a, b = Undefined, Undefined # type: (A, B) a1, b1 = a, a # type: (A, B) # E: Incompatible types in assignment (expression has type "A", variable has type "B") a2, b2 = b, b # type: (A, B) # E: Incompatible types in assignment (expression has type "B", variable has type "A") -a3, b3 = a # type: (A, B) # E: Incompatible types in assignment -a4, b4 = None # type: (A, B) # E: Incompatible types in assignment +a3, b3 = a # type: (A, B) # E: '__main__.A' object is not iterable +a4, b4 = None # type: (A, B) # E: ''None'' object is not iterable a5, b5 = a, b, a # type: (A, B) # E: Too many values to assign ax, bx = a, b # type: (A, B) @@ -278,11 +278,11 @@ class B: pass [case testMultipleAssignmentWithNonTupleRvalue] from typing import Undefined a, b = Undefined, Undefined # type: (A, B) -# TODO callable f +def f(): pass -a, b = None # E: Incompatible types in assignment -a, b = a # E: Incompatible types in assignment -#a, b = f # Incompatible types in assignment +a, b = None # E: ''None'' object is not iterable +a, b = a # E: '__main__.A' object is not iterable +a, b = f # E: 'def () -> Any' object is not iterable class A: pass class B: pass From fb77d28d06dcc309554ec5edee636cc070acbdbb Mon Sep 17 00:00:00 2001 From: YuanchaoZhu Date: Mon, 29 Sep 2014 03:47:59 -0400 Subject: [PATCH 040/144] Add more raise from statement test cases --- mypy/test/data/check-statements.test | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/mypy/test/data/check-statements.test b/mypy/test/data/check-statements.test index 6cc3d4b6fb63..f99786c4057b 100644 --- a/mypy/test/data/check-statements.test +++ b/mypy/test/data/check-statements.test @@ -315,10 +315,26 @@ raise f # E: Exception must be derived from BaseException [case testRaiseFromStatement] from typing import Undefined -e = Undefined # type: Exception -a = Undefined # type: BaseException -raise e from a -raise e from 1 # E: Exception must be derived from BaseException +e = Undefined # type: BaseException +f = Undefined # type: MyError +a = Undefined # type: A +raise e from a # E: Exception must be derived from BaseException +raise e from e +raise e from f +class A: pass +class MyError(BaseException): pass +[builtins fixtures/exception.py] + +[case testRaiseFromClassobject] +import typing +class A: pass +class MyError(BaseException): pass +def f(): pass +raise BaseException from BaseException +raise BaseException from MyError +raise BaseException from A # E: Exception must be derived from BaseException +raise BaseException from object # E: Exception must be derived from BaseException +raise BaseException from f # E: Exception must be derived from BaseException [builtins fixtures/exception.py] [case testTryFinallyStatement] From 99561a04fc3f30f8424b71da4cab3cf7729a402f Mon Sep 17 00:00:00 2001 From: YuanchaoZhu Date: Mon, 29 Sep 2014 03:48:54 -0400 Subject: [PATCH 041/144] Remove unused class in fixtures/exception.py --- mypy/test/data/fixtures/exception.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mypy/test/data/fixtures/exception.py b/mypy/test/data/fixtures/exception.py index 928272f64d2d..8177156ccc4d 100644 --- a/mypy/test/data/fixtures/exception.py +++ b/mypy/test/data/fixtures/exception.py @@ -7,5 +7,3 @@ class tuple: pass class function: pass class BaseException: pass -class Exception: pass -class int: pass From 6de93540517a86494e5b9ed1c8694db1dc31c8cf Mon Sep 17 00:00:00 2001 From: YuanchaoZhu Date: Mon, 29 Sep 2014 03:51:32 -0400 Subject: [PATCH 042/144] Semantic analyze from_expr of RaiseStmt --- mypy/semanal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index c354cfc823cf..ca80caeefcec 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -993,6 +993,8 @@ def visit_return_stmt(self, s: ReturnStmt) -> None: def visit_raise_stmt(self, s: RaiseStmt) -> None: if s.expr: s.expr.accept(self) + if s.from_expr: + s.from_expr.accept(self) def visit_yield_stmt(self, s: YieldStmt) -> None: if not self.is_func_scope(): From 5ec84c969ab25ceaa2a25de3e0388a506751d76c Mon Sep 17 00:00:00 2001 From: YuanchaoZhu Date: Mon, 29 Sep 2014 03:52:29 -0400 Subject: [PATCH 043/144] Add raise from type checking --- mypy/checker.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index a2aa9f33e118..88c4232839b9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1328,22 +1328,39 @@ def visit_raise_stmt(self, s: RaiseStmt) -> Type: """Type check a raise statement.""" self.breaking_out = True if s.expr: + skip_check_subtype = False typ = self.accept(s.expr) if isinstance(typ, FunctionLike): if typ.is_type_obj(): # Cases like "raise ExceptionClass". typeinfo = typ.type_object() base = self.lookup_typeinfo('builtins.BaseException') + if base in typeinfo.mro: + # Good! + if s.from_expr: + skip_check_subtype = True + else: + return None + # Else fall back to the check below (which will fail). + if not skip_check_subtype: + self.check_subtype(typ, + self.named_type('builtins.BaseException'), s, + messages.INVALID_EXCEPTION) + + # Type checking from + if s.from_expr: + typ = self.accept(s.from_expr) + if isinstance(typ, FunctionLike): + if typ.is_type_obj(): + # Cases like "from ExceptionClass". + typeinfo = typ.type_object() + base = self.lookup_typeinfo('builtins.BaseException') if base in typeinfo.mro: # Good! return None # Else fall back to the check below (which will fail). - super_type = cast(Type, self.named_type('builtins.BaseException')) - # Type checking "raise from" - if s.from_expr: - super_type = self.accept(s.from_expr) self.check_subtype(typ, - super_type, s, + self.named_type('builtins.BaseException'), s, messages.INVALID_EXCEPTION) def visit_try_stmt(self, s: TryStmt) -> Type: From 5bdc697950fbd7ad2a50c73a8fa485044feea402 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 1 Oct 2014 20:24:24 +0200 Subject: [PATCH 044/144] Added semantic analysis test for assignment to nested tuples --- mypy/test/data/semanal-statements.test | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mypy/test/data/semanal-statements.test b/mypy/test/data/semanal-statements.test index 2d22b54424aa..4039d66d59a9 100644 --- a/mypy/test/data/semanal-statements.test +++ b/mypy/test/data/semanal-statements.test @@ -408,6 +408,23 @@ MypyFile:1( NameExpr(x [__main__.x])) IntExpr(1))) +[case testMultipleDefOnlySomeNewNested] +x = 1 +y, (x, z) = 1 +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x* [__main__.x]) + IntExpr(1)) + AssignmentStmt:2( + TupleExpr:2( + NameExpr(y* [__main__.y]) + ParenExpr:2( + TupleExpr:2( + NameExpr(x [__main__.x]) + NameExpr(z* [__main__.z])))) + IntExpr(1))) + [case testIndexedDel] x = y = 1 del x[y] From 70bed8d3210de27f0343831e5ff80ccaf90afa6f Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 1 Oct 2014 20:35:14 +0200 Subject: [PATCH 045/144] Added nested tuple syntax to type annotations; plus test cases --- mypy/parsetype.py | 25 +++++++++++++++++++------ mypy/test/data/check-tuples.test | 12 ++++++++++++ mypy/test/data/semanal-errors.test | 25 +++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/mypy/parsetype.py b/mypy/parsetype.py index b76ac13fcd3c..2143b108a4f7 100644 --- a/mypy/parsetype.py +++ b/mypy/parsetype.py @@ -51,6 +51,8 @@ def index(self) -> int: def parse_type(self) -> Type: """Parse a type.""" t = self.current_token() + if t.string == '(': + return self.parse_parens() if isinstance(t, Name): return self.parse_named_type() elif t.string == '[': @@ -68,20 +70,31 @@ def parse_type(self) -> Type: else: self.parse_error() + def parse_parens(self) -> Type: + self.expect('(') + types = self.parse_types() + self.expect(')') + return types + def parse_types(self) -> Type: - parens = False - if self.current_token_str() == '(': - self.skip() - parens = True + """ Parse either a single type or a comma separated + list of types as a tuple type. In the latter case, a + trailing comma is needed when the list contains only + a single type (and optional otherwise). + + int -> int + int, -> TupleType[int] + int, int, int -> TupleType[int, int, int] + """ type = self.parse_type() if self.current_token_str() == ',': items = [type] while self.current_token_str() == ',': self.skip() + if self.current_token_str() == ')': + break items.append(self.parse_type()) type = TupleType(items, None) - if parens: - self.expect(')') return type def parse_type_list(self) -> TypeList: diff --git a/mypy/test/data/check-tuples.test b/mypy/test/data/check-tuples.test index 9628d9065904..031992992fec 100644 --- a/mypy/test/data/check-tuples.test +++ b/mypy/test/data/check-tuples.test @@ -74,6 +74,18 @@ class A: pass class B(A): pass [builtins fixtures/tuple.py] +[case testNestedTupleTypes2] +from typing import Undefined, Tuple +t1 = Undefined # type: (A, (A, A)) +t2 = Undefined # type: B, (B, B) + +t2 = t1 # E: Incompatible types in assignment (expression has type "Tuple[A, Tuple[A, A]]", variable has type "Tuple[B, Tuple[B, B]]") +t1 = t2 + +class A: pass +class B(A): pass +[builtins fixtures/tuple.py] + [case testSubtypingWithNamedTupleType] from typing import Undefined, Tuple t1 = Undefined # type: Tuple[A, A] diff --git a/mypy/test/data/semanal-errors.test b/mypy/test/data/semanal-errors.test index af0a89b05e66..cce29130a476 100644 --- a/mypy/test/data/semanal-errors.test +++ b/mypy/test/data/semanal-errors.test @@ -815,6 +815,31 @@ from typing import Undefined x, y = 1, 2 # type: int, str, int # E: Incompatible number of tuple items [out] +[case testVariableDeclWithInvalidNumberOfTypesNested] +from typing import Undefined +x, (y, z) = 1, (2, 3) # type: int, (str, int, int) # E: Incompatible number of tuple items +[out] + +[case testVariableDeclWithInvalidNumberOfTypesNested2] +from typing import Undefined +x, (y, z) = 1, (2, 3) # type: int, (str, ) # E: Incompatible number of tuple items +[out] + +[case testVariableDeclWithInvalidNumberOfTypesNested3] +from typing import Undefined +x, (y, z) = 1, (2, 3) # type: int, str # E: Tuple type expected for multiple variables +[out] + +[case testVariableDeclWithInvalidNumberOfTypesNested4] +from typing import Undefined +x, (y, z) = 1, (2, 3) # type: int, str, int # E: Incompatible number of tuple items +[out] + +[case testVariableDeclWithInvalidNumberOfTypesNested5] +from typing import Undefined +x, (y, ) = 1, (2, ) # type: int, str # E: Tuple type expected for multiple variables +[out] + [case testVariableDeclWithInvalidType] from typing import Undefined x, y = 1, 2 # type: int # E: Tuple type expected for multiple variables From 01bda0e2aec44b5fd84a7e9961b7842899b6e9cd Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Thu, 2 Oct 2014 21:22:09 +0200 Subject: [PATCH 046/144] Added parse test for nested tupple assignment --- mypy/test/data/parse.test | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/mypy/test/data/parse.test b/mypy/test/data/parse.test index 0320ec6546c8..783a6de71efc 100644 --- a/mypy/test/data/parse.test +++ b/mypy/test/data/parse.test @@ -614,6 +614,21 @@ MypyFile:1( NameExpr(i))) IntExpr(1) Tuple[a?[c?], Any?, int?])) + +[case testMultipleVarDef3] +(xx, (z, i)) = 1 # type: (a[c], (Any, int)) +[out] +MypyFile:1( + AssignmentStmt:1( + ParenExpr:1( + TupleExpr:1( + NameExpr(xx) + ParenExpr:1( + TupleExpr:1( + NameExpr(z) + NameExpr(i))))) + IntExpr(1) + Tuple[a?[c?], Tuple[Any?, int?]])) [case testAnnotateAssignmentViaSelf] class A: From fea1f2399b273f0c5708a8a9b81d1cdd44c5e550 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Thu, 2 Oct 2014 21:24:37 +0200 Subject: [PATCH 047/144] Added semanal error message for assignment to empty tuple --- mypy/semanal.py | 2 ++ mypy/test/data/semanal-errors.test | 2 ++ 2 files changed, 4 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index c354cfc823cf..a0dc20d9acd6 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -797,6 +797,8 @@ def analyse_lvalue(self, lval: Node, nested: bool = False, elif (isinstance(lval, TupleExpr) or isinstance(lval, ListExpr)) and not nested: items = (Any(lval)).items + if len(items) == 0 and isinstance(lval, TupleExpr): + self.fail("Can't assign to ()", lval) for i in items: self.analyse_lvalue(i, nested=True, add_global=add_global, explicit_type = explicit_type) diff --git a/mypy/test/data/semanal-errors.test b/mypy/test/data/semanal-errors.test index 0d7407355339..8863171a5da2 100644 --- a/mypy/test/data/semanal-errors.test +++ b/mypy/test/data/semanal-errors.test @@ -353,11 +353,13 @@ main, line 2: 'yield' outside function (1) = 1 (1, 1) = 1 [1, 1] = 1 +() = 1 [out] main, line 1: Invalid assignment target main, line 2: Invalid assignment target main, line 3: Invalid assignment target main, line 4: Invalid assignment target +main, line 5: Can't assign to () [case testInvalidLvalues2] x = y = z = 1 From b5d2bacf53868d9cd508a4ed93addddfb317d502 Mon Sep 17 00:00:00 2001 From: YuanchaoZhu Date: Sun, 5 Oct 2014 22:17:26 -0400 Subject: [PATCH 048/144] Add type_check_raise() function and remove duplicate code --- mypy/checker.py | 50 +++++++++++++++++-------------------------------- 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 88c4232839b9..ccacd8d8065f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1328,40 +1328,24 @@ def visit_raise_stmt(self, s: RaiseStmt) -> Type: """Type check a raise statement.""" self.breaking_out = True if s.expr: - skip_check_subtype = False - typ = self.accept(s.expr) - if isinstance(typ, FunctionLike): - if typ.is_type_obj(): - # Cases like "raise ExceptionClass". - typeinfo = typ.type_object() - base = self.lookup_typeinfo('builtins.BaseException') - if base in typeinfo.mro: - # Good! - if s.from_expr: - skip_check_subtype = True - else: - return None - # Else fall back to the check below (which will fail). - if not skip_check_subtype: - self.check_subtype(typ, - self.named_type('builtins.BaseException'), s, - messages.INVALID_EXCEPTION) - - # Type checking from + self.type_check_raise(s.expr, s) if s.from_expr: - typ = self.accept(s.from_expr) - if isinstance(typ, FunctionLike): - if typ.is_type_obj(): - # Cases like "from ExceptionClass". - typeinfo = typ.type_object() - base = self.lookup_typeinfo('builtins.BaseException') - if base in typeinfo.mro: - # Good! - return None - # Else fall back to the check below (which will fail). - self.check_subtype(typ, - self.named_type('builtins.BaseException'), s, - messages.INVALID_EXCEPTION) + self.type_check_raise(s.from_expr, s) + + def type_check_raise(self, e: NameExpr, s: RaiseStmt) -> None: + typ = self.accept(e) + if isinstance(typ, FunctionLike): + if typ.is_type_obj(): + # Cases like "raise/from ExceptionClass". + typeinfo = typ.type_object() + base = self.lookup_typeinfo('builtins.BaseException') + if base in typeinfo.mro: + # Good! + return None + # Else fall back to the check below (which will fail). + self.check_subtype(typ, + self.named_type('builtins.BaseException'), s, + messages.INVALID_EXCEPTION) def visit_try_stmt(self, s: TryStmt) -> Type: """Type check a try statement.""" From 134ae8c612ec2baa018dfdaa8e1be0600828a531 Mon Sep 17 00:00:00 2001 From: YuanchaoZhu Date: Sun, 5 Oct 2014 22:47:56 -0400 Subject: [PATCH 049/144] Change type to Node --- mypy/checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index ccacd8d8065f..19cf07536b86 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1332,7 +1332,7 @@ def visit_raise_stmt(self, s: RaiseStmt) -> Type: if s.from_expr: self.type_check_raise(s.from_expr, s) - def type_check_raise(self, e: NameExpr, s: RaiseStmt) -> None: + def type_check_raise(self, e: Node, s: RaiseStmt) -> None: typ = self.accept(e) if isinstance(typ, FunctionLike): if typ.is_type_obj(): From a17f94e22518d3c2511ce3dde5602822a7b8ada8 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 8 Oct 2014 17:37:44 +0200 Subject: [PATCH 050/144] Renamed identically named tests --- mypy/test/data/check-inference.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/test/data/check-inference.test b/mypy/test/data/check-inference.test index 73f88fbed606..fc6898de4efd 100644 --- a/mypy/test/data/check-inference.test +++ b/mypy/test/data/check-inference.test @@ -211,7 +211,7 @@ class B: pass [out] main: In function "f": -[case testInferringLvarTypesInNestedTupleAssignment] +[case testInferringLvarTypesInNestedTupleAssignment1] from typing import Undefined, Tuple def f() -> None: t = Undefined # type: Tuple[A, B] @@ -228,7 +228,7 @@ class B: pass [out] main: In function "f": -[case testInferringLvarTypesInNestedTupleAssignment] +[case testInferringLvarTypesInNestedTupleAssignment2] import typing def f() -> None: a, (b, c) = A(), (B(), C()) From 9ba1aea257756810b565ab57971b513e8e5f2ca0 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 8 Oct 2014 20:14:02 +0200 Subject: [PATCH 051/144] Added tests for assignments from/to (nested) lists --- mypy/test/data/check-inference.test | 19 ++++++++ mypy/test/data/check-lists.test | 62 ++++++++++++++++++++++++++ mypy/test/data/semanal-statements.test | 26 ++++++++++- mypy/test/testcheck.py | 1 + 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 mypy/test/data/check-lists.test diff --git a/mypy/test/data/check-inference.test b/mypy/test/data/check-inference.test index fc6898de4efd..46765394c34e 100644 --- a/mypy/test/data/check-inference.test +++ b/mypy/test/data/check-inference.test @@ -247,6 +247,25 @@ class C: pass [out] main: In function "f": +[case testInferringLvarTypesInNestedListAssignment] +import typing +def f() -> None: + a, (b, c) = A(), [B(), C()] + a = b # E: Incompatible types in assignment (expression has type "B", variable has type "A") + a = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") + b = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B") + c = A() # E: Incompatible types in assignment (expression has type "A", variable has type "C") + + a = A() + b = B() + c = C() + +class A: pass +class B: pass +class C: pass +[out] +main: In function "f": + [case testInferringLvarTypesInMultiDefWithNoneTypes] import typing def f() -> None: diff --git a/mypy/test/data/check-lists.test b/mypy/test/data/check-lists.test new file mode 100644 index 000000000000..19e20ae987c0 --- /dev/null +++ b/mypy/test/data/check-lists.test @@ -0,0 +1,62 @@ +-- Nested list assignment +-- ----------------------------- + + +[case testNestedListAssignment] +from typing import Undefined, List +a1, b1, c1 = Undefined, Undefined, Undefined # type: (A, B, C) +a2, b2, c2 = Undefined, Undefined, Undefined # type: (A, B, C) + +a1, [b1, c1] = a2, [b2, c2] +a1, [a1, [b1, c1]] = a2, [a2, [b2, c2]] +a1, [a1, [a1, b1]] = a1, [a1, [a1, c1]] # E: Incompatible types in assignment (expression has type "C", variable has type "B") + +class A: pass +class B: pass +class C: pass +[builtins fixtures/list.py] +[out] + +[case testNestedListAssignmenToTuple] +from typing import Undefined, List +a, b, c = Undefined, Undefined, Undefined # type: (A, B, C) + +a, b = [a, b] +a, b = [a] # E: Need 2 values to assign +a, b = [a, b, c] # E: Too many values to assign + +class A: pass +class B: pass +class C: pass +[builtins fixtures/list.py] +[out] + +[case testListAssignmentFromTuple] +from typing import Undefined, List +a, b, c = Undefined, Undefined, Undefined # type: (A, B, C) +t = a, b + +[a, b], c = t, c +[a, c], c = t, c # E: Incompatible types in assignment (expression has type "B", variable has type "C") +[a, a, a], c = t, c # E: Need more than 2 values to unpack (3 expected) +[a], c = t, c # E: Too many values to unpack (1 expected, 2 provided) + +class A: pass +class B: pass +class C: pass +[builtins fixtures/list.py] +[out] + +[case testListAssignmentUnequalAmountToUnpack] +from typing import Undefined, List +a, b, c = Undefined, Undefined, Undefined # type: (A, B, C) + +a, b = [a, b] +a, b = [a] # E: Need 2 values to assign +a, b = [a, b, c] # E: Too many values to assign + +class A: pass +class B: pass +class C: pass +[builtins fixtures/list.py] +[out] \ No newline at end of file diff --git a/mypy/test/data/semanal-statements.test b/mypy/test/data/semanal-statements.test index 4039d66d59a9..502b37a9a485 100644 --- a/mypy/test/data/semanal-statements.test +++ b/mypy/test/data/semanal-statements.test @@ -408,7 +408,7 @@ MypyFile:1( NameExpr(x [__main__.x])) IntExpr(1))) -[case testMultipleDefOnlySomeNewNested] +[case testMultipleDefOnlySomeNewNestedTuples] x = 1 y, (x, z) = 1 [out] @@ -425,6 +425,30 @@ MypyFile:1( NameExpr(z* [__main__.z])))) IntExpr(1))) +[case testMultipleDefOnlySomeNewNestedLists] +x = 1 +y, [x, z] = 1 +[p, [x, r]] = 1 +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(x* [__main__.x]) + IntExpr(1)) + AssignmentStmt:2( + TupleExpr:2( + NameExpr(y* [__main__.y]) + ListExpr:2( + NameExpr(x [__main__.x]) + NameExpr(z* [__main__.z]))) + IntExpr(1)) + AssignmentStmt:3( + ListExpr:3( + NameExpr(p* [__main__.p]) + ListExpr:3( + NameExpr(x [__main__.x]) + NameExpr(r* [__main__.r]))) + IntExpr(1))) + [case testIndexedDel] x = y = 1 del x[y] diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 87456a5fde68..a2c43d7b540e 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -43,6 +43,7 @@ 'check-unreachable-code.test', 'check-unions.test', 'check-isinstance.test', + 'check-lists.test' ] From 2d6b7975c69f5e5a22334835dd7743153d78738f Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 8 Oct 2014 20:23:09 +0200 Subject: [PATCH 052/144] Fixed style issues --- mypy/checker.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6118535ddab3..b679420d5088 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -922,7 +922,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> Type: for lv in s.lvalues[:-1]: self.check_assignment(lv, rvalue, s.type == None) - def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool=True) -> None: + def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool = True) -> None: """Type check a single assignment: lvalue = rvalue """ if isinstance(lvalue, ParenExpr): @@ -948,7 +948,7 @@ def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool=T rvalue) def check_assignment_to_multiple_lvalues(self, lvalues: List[Node], rvalue: Node, context: Context, - infer_lvalue_type: bool=True) -> None: + infer_lvalue_type: bool = True) -> None: if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr): # Recursively go into Tuple or List expression rhs instead of # using the type of rhs, because this allowed more fine grained @@ -975,7 +975,7 @@ def remove_parens(self, node: Node) -> Node: def check_multi_assignment(self, lvalues: List[Node], rvalue: Node, context: Context, - infer_lvalue_type: bool=True, + infer_lvalue_type: bool = True, msg: str = None) -> None: """Check the assignment of one rvalue to a number of lvalues for example from a ListExpr or TupleExpr. @@ -1004,7 +1004,7 @@ def check_multi_assignment(self, lvalues: List[Node], def check_multi_assignment_from_tuple(self, lvalues: List[Node], rvalue: Node, rvalue_type: TupleType, context: Context, - undefined_rvalue: bool, infer_lvalue_type: bool=True) -> None: + undefined_rvalue: bool, infer_lvalue_type: bool = True) -> None: if len(rvalue_type.items) != len(lvalues): self.msg.wrong_number_values_to_unpack(len(rvalue_type.items), len(lvalues), context) else: @@ -1036,7 +1036,7 @@ def type_is_iterable(self, type: Type) -> bool: isinstance(type, Instance)) def check_multi_assignment_from_iterable(self, lvalues: List[Node], rvalue_type: Type, - context: Context, infer_lvalue_type: bool=True) -> None: + context: Context, infer_lvalue_type: bool = True) -> None: if self.type_is_iterable(rvalue_type): item_type = self.iterable_item_type(cast(Instance,rvalue_type)) for lv in lvalues: From 7dc9014d6fff052f96091e391290813e8edd75e9 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Thu, 9 Oct 2014 21:41:18 +0200 Subject: [PATCH 053/144] Added tests for string interpolation --- mypy/test/data/check-expressions.test | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index 3ffb639f1db5..2d40722ee5f7 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -893,6 +893,34 @@ a[:None] [builtins fixtures/slice.py] +-- String interpolation +-- -------------------- + + +[case testStringInterpolationType] +from typing import Undefined, Any +i, f, s = Undefined, Undefined, Undefined # type: (int, float, str) +'%d' % i +'%f' % f +'%s' % s +'%d' % s # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "int") +'%f' % s # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "float") +[builtins fixtures/primitives.py] + +[case testStringInterpolationSAcceptsAnyType] +from typing import Undefined, Any +i, o, s = Undefined, Undefined, Undefined # type: (int, object, str) +'%s %s %s' % (i, o, s) +[builtins fixtures/primitives.py] + +[case testStringInterpolationCount] +'%d %d' % 1 # E: Not enough arguments for format string +'%d %d' % (1, 2) +'%d %d' % (1, 2, 3) # E: Not all arguments converted during string formatting +t = 1, 2 +'%d' % t # E: Not all arguments converted during string formatting +[builtins fixtures/primitives.py] + -- Lambdas -- ------- From 410c7ca63eda0eb1a71b5d0f131c656693e62ec3 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Thu, 9 Oct 2014 21:41:40 +0200 Subject: [PATCH 054/144] Added type checking for string interpolation --- mypy/checkexpr.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++- mypy/messages.py | 10 ++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 14378c70e562..4f022f41bbfa 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1,5 +1,7 @@ """Expression type checker. This file is conceptually part of TypeChecker.""" +import re + from typing import Undefined, cast, List, Tuple, Dict, Function from mypy.types import ( @@ -32,7 +34,6 @@ from mypy.semanal import self_type from mypy.constraints import get_actual_type - class ExpressionChecker: """Expression type checker. @@ -748,6 +749,8 @@ def visit_op_expr(self, e: OpExpr) -> Type: if e.op == '*' and isinstance(e.left, ListExpr): # Expressions of form [...] * e get special type inference. return self.check_list_multiply(e) + if e.op == '%' and isinstance(e.left, StrExpr): + return self.check_str_interpolation(e.left, e.right) left_type = self.accept(e.left) if e.op in nodes.op_methods: @@ -759,6 +762,48 @@ def visit_op_expr(self, e: OpExpr) -> Type: else: raise RuntimeError('Unknown operator {}'.format(e.op)) + def check_str_interpolation(self, str: StrExpr, replacements: Node) -> Type: + regex = '%[^%]' + place_holders = re.findall(regex, str.value) + + replacements = self.strip_parens(replacements) + + rhs_type = self.accept(replacements) + rep_types = [] + if isinstance(rhs_type, TupleType): + rep_types = cast(TupleType, rhs_type).items + else: + rep_types = [rhs_type] + + if len(place_holders) > len(rep_types): + self.msg.too_few_string_formatting_arguments(str) + elif len(place_holders) < len(rep_types): + self.msg.too_many_string_formatting_arguments(str) + else: + for ph, rep in zip(place_holders, rep_types): + ph_type = self.placeholder_type(ph) + self.chk.check_subtype(rep, ph_type, str, + messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + 'expression has type', 'placeholder has type') + return self.named_type('builtins.str') + + def placeholder_type(self, p: str) -> Type: + if p[1] == 's': + return AnyType() + elif p[1] in ['d', 'o', 'x', 'X', 'c']: + return self.named_type('builtins.int') + elif p[1] in ['e', 'E', 'f', 'g', 'G']: + return self.named_type('builtins.float') + else: + self.msg.unsupported_placeholder(p[1], str) + return None + + def strip_parens(self, node: Node) -> Node: + if isinstance(node, ParenExpr): + return self.strip_parens(node.expr) + else: + return node + def visit_comparison_expr(self, e: ComparisonExpr) -> Type: """Type check a comparison expression. diff --git a/mypy/messages.py b/mypy/messages.py index f388f21bd0a1..917b5e2dc5d3 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -35,6 +35,7 @@ INCOMPATIBLE_TYPES = 'Incompatible types' INCOMPATIBLE_TYPES_IN_ASSIGNMENT = 'Incompatible types in assignment' INCOMPATIBLE_TYPES_IN_YIELD = 'Incompatible types in yield' +INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION = 'Incompatible types in string interpolation' INIT_MUST_NOT_HAVE_RETURN_TYPE = 'Cannot define return type for "__init__"' GETTER_TYPE_INCOMPATIBLE_WITH_SETTER = \ 'Type of getter incompatible with setter' @@ -563,6 +564,15 @@ def check_void(self, typ: Type, context: Context) -> bool: else: return False + def too_few_string_formatting_arguments(self, context: Context) -> None: + self.fail('Not enough arguments for format string', context) + + def too_many_string_formatting_arguments(self, context: Context) -> None: + self.fail('Not all arguments converted during string formatting', context) + + def unsupported_placeholder(self, placeholder:str, context: Context) -> None: + self.fail('Unsupported format character \'%s\'' % str, context) + def cannot_determine_type(self, name: str, context: Context) -> None: self.fail("Cannot determine type of '%s'" % name, context) From 30ff476ec4f7b817908a328a90dba97022b43917 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Thu, 9 Oct 2014 22:04:58 +0200 Subject: [PATCH 055/144] Add test for unsupported placeholders in string interpolation --- mypy/test/data/check-expressions.test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index 2d40722ee5f7..64ccbcfed0b1 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -921,6 +921,9 @@ t = 1, 2 '%d' % t # E: Not all arguments converted during string formatting [builtins fixtures/primitives.py] +[case testStringInterpolationInvalidPlaceholder] +'%W' % 1 # E: Unsupported format character 'W' + -- Lambdas -- ------- From b62ed81dbe6af7b4a99963ac9624763a308292b9 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Thu, 9 Oct 2014 22:05:24 +0200 Subject: [PATCH 056/144] Fixed handling of unsupported placeholders in string interpolation --- mypy/checkexpr.py | 9 +++++---- mypy/messages.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4f022f41bbfa..1223f6639b3f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -781,13 +781,14 @@ def check_str_interpolation(self, str: StrExpr, replacements: Node) -> Type: self.msg.too_many_string_formatting_arguments(str) else: for ph, rep in zip(place_holders, rep_types): - ph_type = self.placeholder_type(ph) - self.chk.check_subtype(rep, ph_type, str, + ph_type = self.placeholder_type(ph, replacements) + if ph_type: + self.chk.check_subtype(rep, ph_type, str, messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, 'expression has type', 'placeholder has type') return self.named_type('builtins.str') - def placeholder_type(self, p: str) -> Type: + def placeholder_type(self, p: str, context: Context) -> Type: if p[1] == 's': return AnyType() elif p[1] in ['d', 'o', 'x', 'X', 'c']: @@ -795,7 +796,7 @@ def placeholder_type(self, p: str) -> Type: elif p[1] in ['e', 'E', 'f', 'g', 'G']: return self.named_type('builtins.float') else: - self.msg.unsupported_placeholder(p[1], str) + self.msg.unsupported_placeholder(p[1], context) return None def strip_parens(self, node: Node) -> Node: diff --git a/mypy/messages.py b/mypy/messages.py index 917b5e2dc5d3..be7cb8672355 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -570,8 +570,8 @@ def too_few_string_formatting_arguments(self, context: Context) -> None: def too_many_string_formatting_arguments(self, context: Context) -> None: self.fail('Not all arguments converted during string formatting', context) - def unsupported_placeholder(self, placeholder:str, context: Context) -> None: - self.fail('Unsupported format character \'%s\'' % str, context) + def unsupported_placeholder(self, placeholder: str, context: Context) -> None: + self.fail('Unsupported format character \'%s\'' % placeholder, context) def cannot_determine_type(self, name: str, context: Context) -> None: self.fail("Cannot determine type of '%s'" % name, context) From 153f503119d1a03694700e27a24c40a9f3382e18 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Fri, 10 Oct 2014 17:21:22 +0200 Subject: [PATCH 057/144] Added tests for complex conversion specifiers (everything except mapping keys) --- mypy/test/data/check-expressions.test | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index 64ccbcfed0b1..caa231a77d72 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -924,6 +924,39 @@ t = 1, 2 [case testStringInterpolationInvalidPlaceholder] '%W' % 1 # E: Unsupported format character 'W' +[case testStringInterpolationWidth] +'%2f' % 3.14 +'%*f' % 3.14 # E: Not enough arguments for format string +'%*f' % (4, 3.14) +'%*f' % (1.1, 3.14) # E: * wants int +[builtins fixtures/primitives.py] + +[case testStringInterpolationPrecision] +'%.2f' % 3.14 +'%.*f' % 3.14 # E: Not enough arguments for format string +'%.*f' % (4, 3.14) +'%.*f' % (1.1, 3.14) # E: * wants int +[builtins fixtures/primitives.py] + +[case testStringInterpolationWidthAndPrecision] +'%4.2f' % 3.14 +'%4.*f' % 3.14 # E: Not enough arguments for format string +'%*.2f' % 3.14 # E: Not enough arguments for format string +'%*.*f' % 3.14 # E: Not enough arguments for format string +'%*.*f' % (4, 2, 3.14) +[builtins fixtures/primitives.py] + +[case testStringInterpolationFlagsAndLengthModifers] +'%04hd' % 1 +'%-.4ld' % 1 +'%+*Ld' % (1, 1) +'% .*ld' % (1, 1) +[builtins fixtures/primitives.py] + +[case testStringInterpolationDoublePercentage] +'%% %d' % 1 +'%3%' % 1 # E: Unsupported format character '%' + -- Lambdas -- ------- From 22bdfc1013752343f3f939b3eecd36c2aacdefad Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Fri, 10 Oct 2014 17:28:18 +0200 Subject: [PATCH 058/144] Extended string interpolation checking to accept all conversion specifier formats, except for mapping keys --- mypy/checkexpr.py | 88 ++++++++++++++++++++++++++++++++--------------- mypy/messages.py | 3 ++ 2 files changed, 63 insertions(+), 28 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 1223f6639b3f..bde7b0a90242 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -750,7 +750,7 @@ def visit_op_expr(self, e: OpExpr) -> Type: # Expressions of form [...] * e get special type inference. return self.check_list_multiply(e) if e.op == '%' and isinstance(e.left, StrExpr): - return self.check_str_interpolation(e.left, e.right) + return self.check_str_interpolation(cast(StrExpr, e.left), e.right) left_type = self.accept(e.left) if e.op in nodes.op_methods: @@ -763,40 +763,72 @@ def visit_op_expr(self, e: OpExpr) -> Type: raise RuntimeError('Unknown operator {}'.format(e.op)) def check_str_interpolation(self, str: StrExpr, replacements: Node) -> Type: - regex = '%[^%]' - place_holders = re.findall(regex, str.value) - - replacements = self.strip_parens(replacements) - - rhs_type = self.accept(replacements) - rep_types = [] - if isinstance(rhs_type, TupleType): - rep_types = cast(TupleType, rhs_type).items - else: - rep_types = [rhs_type] + expected_types = self.parse_conversion_specifiers(str.value, str) + if expected_types: + replacements = self.strip_parens(replacements) + rhs_type = self.accept(replacements) + rep_types = [] # type: List[Type] + if isinstance(rhs_type, TupleType): + rep_types = cast(TupleType, rhs_type).items + else: + rep_types = [rhs_type] - if len(place_holders) > len(rep_types): - self.msg.too_few_string_formatting_arguments(str) - elif len(place_holders) < len(rep_types): - self.msg.too_many_string_formatting_arguments(str) - else: - for ph, rep in zip(place_holders, rep_types): - ph_type = self.placeholder_type(ph, replacements) - if ph_type: - self.chk.check_subtype(rep, ph_type, str, - messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, - 'expression has type', 'placeholder has type') + if len(expected_types) > len(rep_types): + self.msg.too_few_string_formatting_arguments(str) + elif len(expected_types) < len(rep_types): + self.msg.too_many_string_formatting_arguments(str) + else: + for exp, rep_type in zip(expected_types, rep_types): + exp_type, msg, msg_exp, msg_ph = exp + if exp_type: + self.chk.check_subtype(rep_type, exp_type, str, + msg, msg_exp, msg_ph) return self.named_type('builtins.str') - def placeholder_type(self, p: str, context: Context) -> Type: - if p[1] == 's': + def parse_conversion_specifiers(self, format: str, context: Context) -> List[Tuple[Type, str, str, str]]: + #key_regex = '(\((.*)\))?' # (optional) parenthesised sequence of characters + flags_regex = '([#0\-+ ]*)' # (optional) sequence of flags + width_regex = '(\*|[1-9][0-9]*)?' # (optional) minimum field width (* or numbers) + precision_regex = '(\.(\*|[0-9]+))?' # (optional) . followed by * of numbers + length_mod_regex = '[hlL]?' # (optional) length modifier (unused) + type_regex = '(.)?' # conversion type + regex = ('%' + flags_regex + width_regex + + precision_regex + length_mod_regex + type_regex) + expected_types = [] # type: List[Tuple[Type, str, str, str]] + for flags, width, _, precision, type in re.findall(regex, format): + if type == '': + self.msg.incomplete_conversion_specifier_format(context) + return None + elif type == '%' and width == '' and precision == '': + pass + else: + expected_types.extend(self.specifier_types(width, precision, type, context)) + return expected_types + + def specifier_types(self, width: str, precision: str, type: str, + context: Context) -> List[Tuple[Type, str, str, str]]: + types = [] # type: List[Tuple[Type, str, str, str]] + if width == '*': + types.append( (self.named_type('builtins.int'), '* wants int', None, None) ) + if precision == '*': + types.append( (self.named_type('builtins.int'), '* wants int', None, None) ) + types.append( (self.conversion_type(type, context), + messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + 'expression has type', 'placeholder has type') ) + return types + + def conversion_type(self, p: str, context: Context) -> Type: + if p in ['s', 'r']: return AnyType() - elif p[1] in ['d', 'o', 'x', 'X', 'c']: + elif p in ['d', 'i', 'o', 'u', 'x', 'X', 'c']: return self.named_type('builtins.int') - elif p[1] in ['e', 'E', 'f', 'g', 'G']: + elif p in ['e', 'E', 'f', 'F', 'g', 'G']: return self.named_type('builtins.float') + elif p in ['c']: + return UnionType([self.named_type('builtins.int'), + self.named_type('builtins.str')]) else: - self.msg.unsupported_placeholder(p[1], context) + self.msg.unsupported_placeholder(p, context) return None def strip_parens(self, node: Node) -> Node: diff --git a/mypy/messages.py b/mypy/messages.py index be7cb8672355..a657c0f91f88 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -570,6 +570,9 @@ def too_few_string_formatting_arguments(self, context: Context) -> None: def too_many_string_formatting_arguments(self, context: Context) -> None: self.fail('Not all arguments converted during string formatting', context) + def incomplete_conversion_specifier_format(self, context: Context) -> None: + self.fail('Incomplete format', context) + def unsupported_placeholder(self, placeholder: str, context: Context) -> None: self.fail('Unsupported format character \'%s\'' % placeholder, context) From 99c7f5ce0fc95c3c9af6015ee903ed57f40f68d6 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Fri, 10 Oct 2014 18:38:27 +0200 Subject: [PATCH 059/144] Fixed handling of replacements of Any type --- mypy/checkexpr.py | 2 ++ mypy/test/data/check-expressions.test | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index bde7b0a90242..4403380ad41b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -770,6 +770,8 @@ def check_str_interpolation(self, str: StrExpr, replacements: Node) -> Type: rep_types = [] # type: List[Type] if isinstance(rhs_type, TupleType): rep_types = cast(TupleType, rhs_type).items + elif isinstance(rhs_type, AnyType): + rep_types = [AnyType()] * len(expected_types) else: rep_types = [rhs_type] diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index caa231a77d72..fe031bec8108 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -921,6 +921,12 @@ t = 1, 2 '%d' % t # E: Not all arguments converted during string formatting [builtins fixtures/primitives.py] +[case testStringInterpolationWithAnyType] +from typing import Undefined, Any +a = Undefined # type: Any +'%d %d' % a +[builtins fixtures/primitives.py] + [case testStringInterpolationInvalidPlaceholder] '%W' % 1 # E: Unsupported format character 'W' From b7faad48b2fa876c4f8011ff7e0a89f828e5f91e Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sat, 11 Oct 2014 12:07:11 +0200 Subject: [PATCH 060/144] Fixed handling of conversion type %. --- mypy/checkexpr.py | 5 ++--- mypy/test/data/check-expressions.test | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4403380ad41b..b327a7c41479 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -801,8 +801,6 @@ def parse_conversion_specifiers(self, format: str, context: Context) -> List[Tup if type == '': self.msg.incomplete_conversion_specifier_format(context) return None - elif type == '%' and width == '' and precision == '': - pass else: expected_types.extend(self.specifier_types(width, precision, type, context)) return expected_types @@ -814,7 +812,8 @@ def specifier_types(self, width: str, precision: str, type: str, types.append( (self.named_type('builtins.int'), '* wants int', None, None) ) if precision == '*': types.append( (self.named_type('builtins.int'), '* wants int', None, None) ) - types.append( (self.conversion_type(type, context), + if type != '%': + types.append( (self.conversion_type(type, context), messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, 'expression has type', 'placeholder has type') ) return types diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index fe031bec8108..c2911e9c7cc0 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -961,7 +961,9 @@ a = Undefined # type: Any [case testStringInterpolationDoublePercentage] '%% %d' % 1 -'%3%' % 1 # E: Unsupported format character '%' +'%3% %d' % 1 +'%*%' % 1 +'%*% %d' % 1 # E: Not enough arguments for format string -- Lambdas -- ------- From d17c000964d830f0cd90c76d8557131b04eb2cbc Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sat, 11 Oct 2014 14:30:19 +0200 Subject: [PATCH 061/144] Extended checking for %c conversion type in case the replacement is a str to make sure that str is of length 1. Made checker aware of mapping keys: errors are reported when specifiers with mapping keys are mixed with those without. Error is reported when mapping keys and stars (in precision of minimum field width) are mixed. When mapping keys are present, the replacement expression is not yet checked. --- mypy/checkexpr.py | 140 +++++++++++++++++++------- mypy/messages.py | 12 +++ mypy/test/data/check-expressions.test | 6 ++ 3 files changed, 124 insertions(+), 34 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index b327a7c41479..571db5d560d9 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -763,65 +763,137 @@ def visit_op_expr(self, e: OpExpr) -> Type: raise RuntimeError('Unknown operator {}'.format(e.op)) def check_str_interpolation(self, str: StrExpr, replacements: Node) -> Type: - expected_types = self.parse_conversion_specifiers(str.value, str) - if expected_types: + specifiers = self.parse_conversion_specifiers(str.value) + checkers = self.analyse_conversion_specifiers(specifiers, str) + if checkers: replacements = self.strip_parens(replacements) rhs_type = self.accept(replacements) rep_types = [] # type: List[Type] if isinstance(rhs_type, TupleType): rep_types = cast(TupleType, rhs_type).items elif isinstance(rhs_type, AnyType): - rep_types = [AnyType()] * len(expected_types) + rep_types = [AnyType()] * len(checkers) else: rep_types = [rhs_type] - if len(expected_types) > len(rep_types): + if len(checkers) > len(rep_types): self.msg.too_few_string_formatting_arguments(str) - elif len(expected_types) < len(rep_types): + elif len(checkers) < len(rep_types): self.msg.too_many_string_formatting_arguments(str) else: - for exp, rep_type in zip(expected_types, rep_types): - exp_type, msg, msg_exp, msg_ph = exp - if exp_type: - self.chk.check_subtype(rep_type, exp_type, str, - msg, msg_exp, msg_ph) + if len(checkers) == 1: + checkers[0](node=replacements) + elif isinstance(replacements, TupleExpr): + for check, rep_node in zip(checkers, replacements.items): + check(node=rep_node) + else: + for check, rep_type in zip(checkers, rep_types): + check(type=rep_type) return self.named_type('builtins.str') - def parse_conversion_specifiers(self, format: str, context: Context) -> List[Tuple[Type, str, str, str]]: - #key_regex = '(\((.*)\))?' # (optional) parenthesised sequence of characters + class ConversionSpecifier: + def __init__(self, key: str, flags: str, width: str, precision: str, type: str) -> None: + self.key = key + self.flags = flags + self.width = width + self.precision = precision + self.type = type + + def has_key(self): + return self.key != None + + def has_star(self): + return self.width == '*' or self.precision == '*' + + def parse_conversion_specifiers(self, format: str) -> List[ConversionSpecifier]: + key_regex = '(\((.*)\))?' # (optional) parenthesised sequence of characters flags_regex = '([#0\-+ ]*)' # (optional) sequence of flags width_regex = '(\*|[1-9][0-9]*)?' # (optional) minimum field width (* or numbers) - precision_regex = '(\.(\*|[0-9]+))?' # (optional) . followed by * of numbers + precision_regex = '(?:\.(\*|[0-9]+))?' # (optional) . followed by * of numbers length_mod_regex = '[hlL]?' # (optional) length modifier (unused) type_regex = '(.)?' # conversion type - regex = ('%' + flags_regex + width_regex + + regex = ('%' + key_regex + flags_regex + width_regex + precision_regex + length_mod_regex + type_regex) - expected_types = [] # type: List[Tuple[Type, str, str, str]] - for flags, width, _, precision, type in re.findall(regex, format): - if type == '': - self.msg.incomplete_conversion_specifier_format(context) + specifiers = [] + for parens_key, key, flags, width, precision, type in re.findall(regex, format): + if parens_key == '': + key = None + specifiers.append(self.ConversionSpecifier(key, flags, width, precision, type)) + return specifiers + + def analyse_conversion_specifiers(self, specifiers: List[ConversionSpecifier], + context: Context) -> List[ Function[[Node, Type], None] ]: + has_star = any(specifier.has_star() for specifier in specifiers) + has_key = any(specifier.has_key() for specifier in specifiers) + all_have_keys = all(specifier.has_key() for specifier in specifiers) + + if has_key and has_star: + self.msg.string_interpolation_with_star_and_key(context) + return None + if has_key and not all_have_keys: + self.msg.string_interpolation_mixing_key_and_non_keys(context) + return None + + elif has_key: + return None # TODO + else: + checkers = [] # type: List[ Function[[Node, Type], None] ] + for specifier in specifiers: + checker = self.replacement_checkers(specifier, context) + if checker == None: + return None + checkers.extend(checker) + return checkers + + def replacement_checkers(self, specifier: ConversionSpecifier, + context: Context) -> List[ Function[[Node, Type], None] ]: + ''' Returns a list of functions that check whether a replacement is + of the right type for a specifier. The functions take either a node + or a type. When a node is specified, the type of the node is checked + again, now in the right type context + ''' + checkers = [] # type: List[ Function[[Node, Type], None] ] + + def check_star(node: Node = None, type: Type = None) -> None: + expected = self.named_type('builtins.int') + if node: + type = self.accept(node, expected) + self.chk.check_subtype(type, expected, context, '* wants int') + + if specifier.width == '*': + checkers.append(check_star) + if specifier.precision == '*': + checkers.append(check_star) + if specifier.type != '%': + expected_type = self.conversion_type(specifier.type, context) + if expected_type == None: return None + + if specifier.type == 'c': + def check_c(node: Node = None, type: Type = None) -> None: + '''int, or str with length 1''' + if node: + type = self.accept(node, expected_type) + if isinstance(node, StrExpr) and len(node.value) != 1: + self.msg.requires_int_or_char(context) + self.chk.check_subtype(type, expected_type, context, + messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + 'expression has type', 'placeholder has type') + checkers.append(check_c) else: - expected_types.extend(self.specifier_types(width, precision, type, context)) - return expected_types - - def specifier_types(self, width: str, precision: str, type: str, - context: Context) -> List[Tuple[Type, str, str, str]]: - types = [] # type: List[Tuple[Type, str, str, str]] - if width == '*': - types.append( (self.named_type('builtins.int'), '* wants int', None, None) ) - if precision == '*': - types.append( (self.named_type('builtins.int'), '* wants int', None, None) ) - if type != '%': - types.append( (self.conversion_type(type, context), - messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, - 'expression has type', 'placeholder has type') ) - return types + def check(node: Node = None, type: Type = None) -> None: + if node: + type = self.accept(node, expected_type) + self.chk.check_subtype(type, expected_type, context, + messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + 'expression has type', 'placeholder has type') + checkers.append(check) + return checkers def conversion_type(self, p: str, context: Context) -> Type: if p in ['s', 'r']: return AnyType() - elif p in ['d', 'i', 'o', 'u', 'x', 'X', 'c']: + elif p in ['d', 'i', 'o', 'u', 'x', 'X']: return self.named_type('builtins.int') elif p in ['e', 'E', 'f', 'F', 'g', 'G']: return self.named_type('builtins.float') diff --git a/mypy/messages.py b/mypy/messages.py index a657c0f91f88..013120a98b87 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -576,6 +576,18 @@ def incomplete_conversion_specifier_format(self, context: Context) -> None: def unsupported_placeholder(self, placeholder: str, context: Context) -> None: self.fail('Unsupported format character \'%s\'' % placeholder, context) + def string_interpolation_with_star_and_key(self, context: Context) -> None: + self.fail('String interpolation contains both stars and mapping keys', context) + + def requires_int_or_char(self, context: Context) -> None: + self.fail('%c requires int or char', context) + + def format_requires_mapping(self, context: Context) -> None: + self.fail('Format requires a mapping', context) + + def string_interpolation_mixing_key_and_non_keys(self, context: Context) -> None: + self.fail('String interpolation mixes specifier with and without mapping keys', context) + def cannot_determine_type(self, name: str, context: Context) -> None: self.fail("Cannot determine type of '%s'" % name, context) diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index c2911e9c7cc0..c8faf184804b 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -965,6 +965,12 @@ a = Undefined # type: Any '%*%' % 1 '%*% %d' % 1 # E: Not enough arguments for format string +[case testStringInterpolationC] +'%c' % 1 +'%c' % 's' +'%c' % '' # E: %c requires int or char +'%c' % 'ab' # E: %c requires int or char + -- Lambdas -- ------- From c9b979d73b738eb0f20c9934140e206b61543e45 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sat, 11 Oct 2014 21:45:33 +0200 Subject: [PATCH 062/144] Added tests for checking of key mapping in string interpolation --- mypy/test/data/check-expressions.test | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index c8faf184804b..204d513ab13c 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -971,6 +971,44 @@ a = Undefined # type: Any '%c' % '' # E: %c requires int or char '%c' % 'ab' # E: %c requires int or char +[case testStringInterpolationMappingTypes] +'%(a)d %(b)s' % {'a': 1, 'b': 's'} +'%(a)d %(b)s' % {'a': 's', 'b': 1} # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "int") + +[case testStringInterpolationMappingKeys] +'%()d' % {'': 2} +'%(a)d' % {'a': 1, 'b': 2, 'c': 3} +'%(q)d' % {'a': 1, 'b': 2, 'c': 3} # E: Key 'q' not found in mapping +[builtins fixtures/dict.py] + +[case testStringInterpolationMappingDictTypes] +from typing import Undefined, Any, Dict +a = Undefined # type: Any +ds, do, di = Undefined, Undefined, Undefined # type: Dict[str, int], Dict[object, int], Dict[int, int] +'%(a)' % 1 # E: Format requires a mapping (expression has type "int", expected type for mapping is Dict[Any, Any]) +'%()d' % a +'%()d' % ds +'%()d' % do +[builtins fixtures/dict.py] + +[case testStringInterpolationMappingInvalidDictTypes-skip] +from typing import Undefined, Any, Dict +di = Undefined # type: Dict[int, int] +'%()d' % di # E: Format requires a mapping (expression has type Dict[int, int], expected type for mapping is Dict[str, Any]) +[builtins fixtures/dict.py] + +[case testStringInterpolationMappingInvalidSpecifiers] +'%(a)d %d' % 1 # E: String interpolation mixes specifier with and without mapping keys +'%(b)*d' % 1 # E: String interpolation contains both stars and mapping keys +'%(b).*d' % 1 # E: String interpolation contains both stars and mapping keys + +[case testStringInterpolationMappingFlagsAndLengthModifiers] +'%(a)1d' % {'a': 1} +'%(a).1d' % {'a': 1} +'%(a)#1.1ld' % {'a': 1} +[builtins fixtures/dict.py] + + -- Lambdas -- ------- From 4714b6b7a758357f8af25d86ae16ed5ed1c1fa9c Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sat, 11 Oct 2014 21:46:10 +0200 Subject: [PATCH 063/144] Added checking of key mapping in string interpolation --- mypy/checkexpr.py | 118 +++++++++++++++++++++++++++++++--------------- mypy/messages.py | 6 +-- 2 files changed, 84 insertions(+), 40 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 571db5d560d9..fa3cff5ca94f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -763,32 +763,15 @@ def visit_op_expr(self, e: OpExpr) -> Type: raise RuntimeError('Unknown operator {}'.format(e.op)) def check_str_interpolation(self, str: StrExpr, replacements: Node) -> Type: + replacements = self.strip_parens(replacements) specifiers = self.parse_conversion_specifiers(str.value) - checkers = self.analyse_conversion_specifiers(specifiers, str) - if checkers: - replacements = self.strip_parens(replacements) - rhs_type = self.accept(replacements) - rep_types = [] # type: List[Type] - if isinstance(rhs_type, TupleType): - rep_types = cast(TupleType, rhs_type).items - elif isinstance(rhs_type, AnyType): - rep_types = [AnyType()] * len(checkers) - else: - rep_types = [rhs_type] - - if len(checkers) > len(rep_types): - self.msg.too_few_string_formatting_arguments(str) - elif len(checkers) < len(rep_types): - self.msg.too_many_string_formatting_arguments(str) - else: - if len(checkers) == 1: - checkers[0](node=replacements) - elif isinstance(replacements, TupleExpr): - for check, rep_node in zip(checkers, replacements.items): - check(node=rep_node) - else: - for check, rep_type in zip(checkers, rep_types): - check(type=rep_type) + has_mapping_keys = self.analyse_conversion_specifiers(specifiers, str) + if has_mapping_keys == None: + pass # Error was reported + elif has_mapping_keys: + self.check_mapping_str_interpolation(specifiers, replacements) + else: + self.check_simple_str_interpolation(specifiers, replacements) return self.named_type('builtins.str') class ConversionSpecifier: @@ -806,7 +789,7 @@ def has_star(self): return self.width == '*' or self.precision == '*' def parse_conversion_specifiers(self, format: str) -> List[ConversionSpecifier]: - key_regex = '(\((.*)\))?' # (optional) parenthesised sequence of characters + key_regex = '(\((\w*)\))?' # (optional) parenthesised sequence of characters flags_regex = '([#0\-+ ]*)' # (optional) sequence of flags width_regex = '(\*|[1-9][0-9]*)?' # (optional) minimum field width (* or numbers) precision_regex = '(?:\.(\*|[0-9]+))?' # (optional) . followed by * of numbers @@ -814,7 +797,7 @@ def parse_conversion_specifiers(self, format: str) -> List[ConversionSpecifier]: type_regex = '(.)?' # conversion type regex = ('%' + key_regex + flags_regex + width_regex + precision_regex + length_mod_regex + type_regex) - specifiers = [] + specifiers = [] # type: List[ExpressionChecker.ConversionSpecifier] for parens_key, key, flags, width, precision, type in re.findall(regex, format): if parens_key == '': key = None @@ -822,7 +805,7 @@ def parse_conversion_specifiers(self, format: str) -> List[ConversionSpecifier]: return specifiers def analyse_conversion_specifiers(self, specifiers: List[ConversionSpecifier], - context: Context) -> List[ Function[[Node, Type], None] ]: + context: Context) -> bool: has_star = any(specifier.has_star() for specifier in specifiers) has_key = any(specifier.has_key() for specifier in specifiers) all_have_keys = all(specifier.has_key() for specifier in specifiers) @@ -833,17 +816,78 @@ def analyse_conversion_specifiers(self, specifiers: List[ConversionSpecifier], if has_key and not all_have_keys: self.msg.string_interpolation_mixing_key_and_non_keys(context) return None + return has_key + + def check_simple_str_interpolation(self, specifiers: List[ConversionSpecifier], + replacements: Node) -> None: + checkers = self.build_replacement_checkers(specifiers, replacements) + if checkers == None: + return + + rhs_type = self.accept(replacements) + rep_types = [] # type: List[Type] + if isinstance(rhs_type, TupleType): + rep_types = cast(TupleType, rhs_type).items + elif isinstance(rhs_type, AnyType): + return + else: + rep_types = [rhs_type] - elif has_key: - return None # TODO + if len(checkers) > len(rep_types): + self.msg.too_few_string_formatting_arguments(replacements) + elif len(checkers) < len(rep_types): + self.msg.too_many_string_formatting_arguments(replacements) else: - checkers = [] # type: List[ Function[[Node, Type], None] ] + if len(checkers) == 1: + checkers[0](node=replacements) + elif isinstance(replacements, TupleExpr): + for check, rep_node in zip(checkers, replacements.items): + check(node=rep_node) + else: + for check, rep_type in zip(checkers, rep_types): + check(type=rep_type) + + def check_mapping_str_interpolation(self, specifiers: List[ConversionSpecifier], + replacements: Node) -> None: + dict_with_only_str_literal_keys = (isinstance(replacements, DictExpr) and + all(isinstance(self.strip_parens(k), StrExpr) + for k, v in cast(DictExpr, replacements).items)) + if dict_with_only_str_literal_keys: + # check key mapping + checkers = self.build_replacement_checkers(specifiers, replacements) + if checkers == None: + return + + mapping = {} # type: Dict[str, Type] + for k, v in cast(DictExpr, replacements).items: + key_str = cast(StrExpr, k).value + mapping[key_str] = self.accept(v) + for specifier in specifiers: - checker = self.replacement_checkers(specifier, context) - if checker == None: - return None - checkers.extend(checker) - return checkers + if specifier.key not in mapping: + self.msg.key_not_in_mapping(specifier.key, replacements) + return + rep_type = mapping[specifier.key] + expected_type = self.conversion_type(specifier.type, replacements) + self.chk.check_subtype(rep_type, expected_type, replacements, + messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + 'expression has type', 'placeholder has type') + else: + rep_type = self.accept(replacements) + dict_type = self.chk.named_generic_type('builtins.dict', + [AnyType(), AnyType()]) + self.chk.check_subtype(rep_type, dict_type, replacements, messages.FORMAT_REQUIRES_MAPPING, + 'expression has type', 'expected type for mapping is') + + def build_replacement_checkers(self, specifiers: List[ConversionSpecifier], + context: Context) -> List[ Function[[Node, Type], None] ]: + checkers = [] # type: List[ Function[[Node, Type], None] ] + for specifier in specifiers: + checker = self.replacement_checkers(specifier, context) + if checker == None: + return None + checkers.extend(checker) + return checkers def replacement_checkers(self, specifier: ConversionSpecifier, context: Context) -> List[ Function[[Node, Type], None] ]: @@ -874,7 +918,7 @@ def check_c(node: Node = None, type: Type = None) -> None: '''int, or str with length 1''' if node: type = self.accept(node, expected_type) - if isinstance(node, StrExpr) and len(node.value) != 1: + if isinstance(node, StrExpr) and len(cast(StrExpr, node).value) != 1: self.msg.requires_int_or_char(context) self.chk.check_subtype(type, expected_type, context, messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, diff --git a/mypy/messages.py b/mypy/messages.py index 013120a98b87..b2d2881e26be 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -60,7 +60,7 @@ INCONSISTENT_ABSTRACT_OVERLOAD = \ 'Overloaded method has both abstract and non-abstract variants' INSTANCE_LAYOUT_CONFLICT = 'Instance layout conflict in multiple inheritance' - +FORMAT_REQUIRES_MAPPING = 'Format requires a mapping' class MessageBuilder: """Helper class for reporting type checker error messages with parameters. @@ -582,8 +582,8 @@ def string_interpolation_with_star_and_key(self, context: Context) -> None: def requires_int_or_char(self, context: Context) -> None: self.fail('%c requires int or char', context) - def format_requires_mapping(self, context: Context) -> None: - self.fail('Format requires a mapping', context) + def key_not_in_mapping(self, key: str, context: Context) -> None: + self.fail('Key \'%s\' not found in mapping' % key, context) def string_interpolation_mixing_key_and_non_keys(self, context: Context) -> None: self.fail('String interpolation mixes specifier with and without mapping keys', context) From 8215df29cd61ec7da2335c3223d8d6e2632b96ab Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sun, 12 Oct 2014 10:14:26 +0200 Subject: [PATCH 064/144] Small fixes --- mypy/checkexpr.py | 18 +++++++++--------- mypy/test/data/check-expressions.test | 6 ++++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index fa3cff5ca94f..89b21962ec44 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -789,12 +789,12 @@ def has_star(self): return self.width == '*' or self.precision == '*' def parse_conversion_specifiers(self, format: str) -> List[ConversionSpecifier]: - key_regex = '(\((\w*)\))?' # (optional) parenthesised sequence of characters - flags_regex = '([#0\-+ ]*)' # (optional) sequence of flags - width_regex = '(\*|[1-9][0-9]*)?' # (optional) minimum field width (* or numbers) - precision_regex = '(?:\.(\*|[0-9]+))?' # (optional) . followed by * of numbers - length_mod_regex = '[hlL]?' # (optional) length modifier (unused) - type_regex = '(.)?' # conversion type + key_regex = r'(\((\w*)\))?' # (optional) parenthesised sequence of characters + flags_regex = r'([#0\-+ ]*)' # (optional) sequence of flags + width_regex = r'(\*|[1-9][0-9]*)?' # (optional) minimum field width (* or numbers) + precision_regex = r'(?:\.(\*|[0-9]+))?' # (optional) . followed by * of numbers + length_mod_regex = r'[hlL]?' # (optional) length modifier (unused) + type_regex = r'(.)?' # conversion type regex = ('%' + key_regex + flags_regex + width_regex + precision_regex + length_mod_regex + type_regex) specifiers = [] # type: List[ExpressionChecker.ConversionSpecifier] @@ -871,7 +871,7 @@ def check_mapping_str_interpolation(self, specifiers: List[ConversionSpecifier], expected_type = self.conversion_type(specifier.type, replacements) self.chk.check_subtype(rep_type, expected_type, replacements, messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, - 'expression has type', 'placeholder has type') + 'expression has type', 'placeholder with key \'%s\' has type' % specifier.key) else: rep_type = self.accept(replacements) dict_type = self.chk.named_generic_type('builtins.dict', @@ -891,11 +891,11 @@ def build_replacement_checkers(self, specifiers: List[ConversionSpecifier], def replacement_checkers(self, specifier: ConversionSpecifier, context: Context) -> List[ Function[[Node, Type], None] ]: - ''' Returns a list of functions that check whether a replacement is + """Returns a list of functions that check whether a replacement is of the right type for a specifier. The functions take either a node or a type. When a node is specified, the type of the node is checked again, now in the right type context - ''' + """ checkers = [] # type: List[ Function[[Node, Type], None] ] def check_star(node: Node = None, type: Type = None) -> None: diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index 204d513ab13c..6df9380fc2a6 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -917,7 +917,9 @@ i, o, s = Undefined, Undefined, Undefined # type: (int, object, str) '%d %d' % 1 # E: Not enough arguments for format string '%d %d' % (1, 2) '%d %d' % (1, 2, 3) # E: Not all arguments converted during string formatting -t = 1, 2 +t = 1, 's' +'%d %s' % t +'%s %d' % t # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "int") '%d' % t # E: Not all arguments converted during string formatting [builtins fixtures/primitives.py] @@ -973,7 +975,7 @@ a = Undefined # type: Any [case testStringInterpolationMappingTypes] '%(a)d %(b)s' % {'a': 1, 'b': 's'} -'%(a)d %(b)s' % {'a': 's', 'b': 1} # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "int") +'%(a)d %(b)s' % {'a': 's', 'b': 1} # E: Incompatible types in string interpolation (expression has type "str", placeholder with key 'a' has type "int") [case testStringInterpolationMappingKeys] '%()d' % {'': 2} From d90f5065b09104d787911d8e6b7a8a1b030a5c5c Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 12 Oct 2014 16:29:30 -0700 Subject: [PATCH 065/144] Fix #307: Method default args can refer to class attributes --- mypy/semanal.py | 6 +++--- mypy/test/data/semanal-classes.test | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 149e7a3cf999..c8bb6a88df43 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -284,11 +284,11 @@ def analyse_function(self, defn: FuncItem) -> None: if isinstance(defn, FuncDef): defn.info = self.type defn.type = set_callable_name(defn.type, defn) - self.function_stack.append(defn) - self.enter() for init in defn.init: if init: init.rvalue.accept(self) + self.function_stack.append(defn) + self.enter() for v in defn.args: self.add_local(v, defn) for init_ in defn.init: @@ -735,7 +735,7 @@ def analyse_lvalue(self, lval: Node, nested: bool = False, Only if add_global is True, add name to globals table. If nested is true, the lvalue is within a tuple or list lvalue expression. """ - + if isinstance(lval, NameExpr): nested_global = (not self.is_func_scope() and self.block_depth[-1] > 0 and diff --git a/mypy/test/data/semanal-classes.test b/mypy/test/data/semanal-classes.test index 4dde4f929fed..2c85e119fe49 100644 --- a/mypy/test/data/semanal-classes.test +++ b/mypy/test/data/semanal-classes.test @@ -514,3 +514,29 @@ MypyFile:1( Decorators( NameExpr(builtinclass [typing.builtinclass])) PassStmt:3())) + +[case testClassAttributeAsMethodDefaultArgumentValue] +import typing +class A: + X = 1 + def f(self, x : int = X) -> None: pass +[out] +MypyFile:1( + Import:1(typing : typing) + ClassDef:2( + A + AssignmentStmt:3( + NameExpr(X* [m]) + IntExpr(1)) + FuncDef:4( + f + Args( + Var(self) + Var(x)) + def (self: __main__.A, x: builtins.int =) + Init( + AssignmentStmt:4( + NameExpr(x [l]) + NameExpr(X [m]))) + Block:4( + PassStmt:4())))) From 998a583fbef5703419abe5266a7c1ca3844eccf5 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 12 Oct 2014 16:51:47 -0700 Subject: [PATCH 066/144] Update CREDITS --- CREDITS | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/CREDITS b/CREDITS index 05c66b83b69e..6ae070531a16 100644 --- a/CREDITS +++ b/CREDITS @@ -7,17 +7,25 @@ Lead developer: Contributors (in alphabetical order): - Steve Allen - Reid Barton - Ashley Hewson - Bob Ippolito - Florian Ludwig - Jared Pochtar - Eric Price + Steven Allen (@Stebalien) + Reid Barton (@rwbarton) + Ryan Gonzalez (@kirbyfan64) + Ashley Hewson (@ashleyh) + Bob Ippolito (@etrepum) + Sander Kersten (@spkersten) + Ian Kronquist (@iankronquist) + Florian Ludwig (@FlorianLudwig) + Jared Pochtar (@jaredp) + Eric Price (@ecprice) + rockneurotiko (@rockneurotiko) Ron Murawski Sebastian Riikonen Schuyler Smith - Jeff Walden + Marcell Vazquez-Chanlatte (@mvcisback) + Igor Vuk (@ivuk) + Jeff Walden (@jswalden) + Yuanchao Zhu (@yczhu) + Gennadiy Zlobin (@gennad) Additional thanks to: From b63829edcf90b8bcc5b097a53250b1e91dfe6004 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 12 Oct 2014 17:46:44 -0700 Subject: [PATCH 067/144] Clean up obsolete code Remove traces of for loop and generator expression annotations. Closes #464. --- mypy/nodes.py | 5 +---- mypy/output.py | 1 - mypy/parse.py | 15 +++++---------- mypy/treetransform.py | 1 - 4 files changed, 6 insertions(+), 16 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 630895649f69..c309e88c3470 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1176,16 +1176,13 @@ class GeneratorExpr(Node): sequences_expr = Undefined(List[Node]) condlists = Undefined(List[List[Node]]) indices = Undefined(List[List[NameExpr]]) - types = Undefined(List[List['mypy.types.Type']]) def __init__(self, left_expr: Node, indices: List[List[NameExpr]], - types: List[List['mypy.types.Type']], sequences: List[Node], - condlists: List[List[Node]]) -> None: + sequences: List[Node], condlists: List[List[Node]]) -> None: self.left_expr = left_expr self.sequences = sequences self.condlists = condlists self.indices = indices - self.types = types def accept(self, visitor: NodeVisitor[T]) -> T: return visitor.visit_generator_expr(self) diff --git a/mypy/output.py b/mypy/output.py index 1f3302927dd0..3668c92804c3 100644 --- a/mypy/output.py +++ b/mypy/output.py @@ -447,7 +447,6 @@ def visit_generator_expr(self, o): for i in range(len(o.indices)): self.token(r.for_toks[i]) for j in range(len(o.indices[i])): - self.node(o.types[i][j]) self.node(o.indices[i][j]) if j < len(o.indices[i]) - 1: self.token(r.commas[0]) diff --git a/mypy/parse.py b/mypy/parse.py index 693ec184c903..bb5235e4beb0 100755 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -851,7 +851,7 @@ def parse_while_stmt(self) -> WhileStmt: def parse_for_stmt(self) -> ForStmt: for_tok = self.expect('for') - index, types, commas = self.parse_for_index_variables() + index, commas = self.parse_for_index_variables() in_tok = self.expect('in') expr = self.parse_expression() @@ -869,11 +869,9 @@ def parse_for_stmt(self) -> ForStmt: else_tok)) return node - def parse_for_index_variables(self) -> Tuple[List[NameExpr], List[Type], - List[Token]]: + def parse_for_index_variables(self) -> Tuple[List[NameExpr], List[Token]]: # Parse index variables of a 'for' statement. index = List[NameExpr]() - types = List[Type]() commas = List[Token]() is_paren = self.current_str() == '(' @@ -883,7 +881,6 @@ def parse_for_index_variables(self) -> Tuple[List[NameExpr], List[Type], while True: v = self.parse_name_expr() index.append(v) - types.append(None) if self.current_str() != ',': commas.append(none) break @@ -892,7 +889,7 @@ def parse_for_index_variables(self) -> Tuple[List[NameExpr], List[Type], if is_paren: self.expect(')') - return index, types, commas + return index, commas def parse_if_stmt(self) -> IfStmt: is_error = False @@ -1179,7 +1176,6 @@ def parse_list_expr(self) -> Node: def parse_generator_expr(self, left_expr: Node) -> GeneratorExpr: indices = List[List[NameExpr]]() sequences = List[Node]() - types = List[List[Type]]() for_toks = List[Token]() in_toks = List[Token]() if_toklists = List[List[Token]]() @@ -1188,9 +1184,8 @@ def parse_generator_expr(self, left_expr: Node) -> GeneratorExpr: if_toks = List[Token]() conds = List[Node]() for_toks.append(self.expect('for')) - index, type, commas = self.parse_for_index_variables() + index, commas = self.parse_for_index_variables() indices.append(index) - types.append(type) in_toks.append(self.expect('in')) sequence = self.parse_expression_list() sequences.append(sequence) @@ -1200,7 +1195,7 @@ def parse_generator_expr(self, left_expr: Node) -> GeneratorExpr: if_toklists.append(if_toks) condlists.append(conds) - gen = GeneratorExpr(left_expr, indices, types, sequences, condlists) + gen = GeneratorExpr(left_expr, indices, sequences, condlists) gen.set_line(for_toks[0]) self.set_repr(gen, noderepr.GeneratorExprRepr(for_toks, commas, in_toks, if_toklists)) diff --git a/mypy/treetransform.py b/mypy/treetransform.py index a0428377eb29..809417caccdc 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -372,7 +372,6 @@ def visit_generator_expr(self, node: GeneratorExpr) -> Node: def duplicate_generator(self, node: GeneratorExpr) -> GeneratorExpr: return GeneratorExpr(self.node(node.left_expr), [self.names(index) for index in node.indices], - [self.optional_types(t) for t in node.types], [self.node(s) for s in node.sequences], [[self.node(cond) for cond in conditions] for conditions in node.condlists]) From 73a74dc09c7e4fce69e7d1c836d7f4aa8c9d864a Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Mon, 13 Oct 2014 13:46:29 -0500 Subject: [PATCH 068/144] s/code::/code-block::/ --- docs/source/tutorial.rst | 108 +++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index 2017f5a1398c..de858ccef7a9 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -95,7 +95,7 @@ Built-in types These are examples of some of the most common built-in types: -.. code:: python +.. code-block:: python int # integer objects of arbitrary size float # floating point number @@ -120,14 +120,14 @@ Type inference The initial assignment defines a variable. If you do not explicitly specify the type of the variable, mypy infers the type based on the static type of the value expression: -.. code:: python +.. code-block:: python i = 1 # Infer type int for i l = [1, 2] # Infer type List[int] for l Type inference is bidirectional and takes context into account. For example, the following is valid: -.. code:: python +.. code-block:: python def f(l: List[object]) -> None: l = [1, 2] # Infer type List[object] for [1, 2] @@ -136,14 +136,14 @@ In an assignment, the type context is determined by the assignment target. In th Note that the following is not valid, since List[int] is not compatible with List[object]: -.. code:: python +.. code-block:: python def f(l: List[object], k: List[int]) -> None: l = k # Type check error: incompatible types in assignment The reason why the above assignment is disallowed is that allowing the assignment could result in non-int values stored in a list of int: -.. code:: python +.. code-block:: python def f(l: List[object], k: List[int]) -> None: l = k @@ -159,21 +159,21 @@ Explicit types for collections The type checker cannot always infer the type of a list or a dictionary. This often arises when creating an empty list or dictionary and assigning it to a new variable without an explicit variable type. In these cases you can give the type explicitly using the type name as a constructor: -.. code:: python +.. code-block:: python l = List[int]() # Create empty list with type List[int] d = Dict[str, int]() # Create empty dictionary (str -> int) Similarly, you can also give an explicit type when creating an empty set: -.. code:: python +.. code-block:: python s = Set[int]() Explicit types for variables **************************** -.. code:: python +.. code-block:: python s = Undefined(str) # Declare type of x to be str. s = 'x' # OK @@ -181,7 +181,7 @@ Explicit types for variables The Undefined call evaluates to a special "Undefined" object that raises an exception on any operation: -.. code:: python +.. code-block:: python s = Undefined(str) if s: # Runtime error: undefined value @@ -189,13 +189,13 @@ The Undefined call evaluates to a special "Undefined" object that raises an exce You can also override the inferred type of a variable by using a special comment after an assignment statement: -.. code:: python +.. code-block:: python x = [] # type: List[int] Here the # type comment applies both to the assignment target, in this case x, and also the initializer expression, via context. The above code is equivalent to this: -.. code:: python +.. code-block:: python x = List[int]() @@ -206,7 +206,7 @@ User-defined types Each class is also a type. Any instance of a subclass is also compatible with all superclasses. All values are compatible with the object type (and also the Any type). -.. code:: python +.. code-block:: python class A: def f(self) -> int: # Type of self inferred (A) @@ -229,7 +229,7 @@ A value with the Any type is dynamically typed. Any operations are permitted on Any is compatible with every other type, and vice versa. No implicit type check is inserted when assigning a value of type Any to a variable with a more precise type: -.. code:: python +.. code-block:: python a, s = Undefined(Any), Undefined(str) a = 2 # OK @@ -242,7 +242,7 @@ Tuple types The type Tuple[t, ...] represents a tuple with the item types t, ...: -.. code:: python +.. code-block:: python def f(t: Tuple[int, str]) -> None: t = 1, 'foo' # OK @@ -253,7 +253,7 @@ Class name forward references Python does not allow references to a class object before the class is defined. Thus this code is does not work as expected: -.. code:: python +.. code-block:: python def f(x: A) -> None: # Error: Name A not defined .... @@ -263,7 +263,7 @@ Python does not allow references to a class object before the class is defined. In cases like these you can enter the type as a string literal — this is a *forward reference*: -.. code:: python +.. code-block:: python def f(x: 'A') -> None: # OK ... @@ -275,7 +275,7 @@ Of course, instead of using a string literal type, you could move the function d Any type can be entered as a string literal, and youn can combine string-literal types with non-string-literal types freely: -.. code:: python +.. code-block:: python a = Undefined(List['A']) # OK n = Undefined('int') # OK, though not useful @@ -289,7 +289,7 @@ Instance and class attributes Mypy type checker detects if you are trying to access a missing attribute, which is a very common programming error. For this to work correctly, instance and class attributes must be defined or initialized within the class. Mypy infers the types of attributes: -.. code:: python +.. code-block:: python class A: def __init__(self, x: int) -> None: @@ -301,7 +301,7 @@ Mypy type checker detects if you are trying to access a missing attribute, which This is a bit like each class having an implicitly defined __slots__ attribute. In Python semantics this is only enforced during type checking: at runtime we use standard Python semantics. You can selectively define a class as *dynamic*; dynamic classes have Python-like compile-time semantics, and they allow you to assign to arbitrary attributes anywhere in a program without the type checker complaining: -.. code:: python +.. code-block:: python from typing import Dynamic @@ -319,7 +319,7 @@ Mypy also lets you read arbitrary attributes of dynamic class instances. This li You can declare variables in the class body explicitly using Undefined or a type comment: -.. code:: python +.. code-block:: python class A: x = Undefined(List[int]) # Declare attribute y of type List[int] @@ -332,7 +332,7 @@ As in Python, a variable defined in the class body can used as a class or an ins Similarly, you can give explicit types to instance variables defined in a method: -.. code:: python +.. code-block:: python class A: def __init__(self) -> None: @@ -343,7 +343,7 @@ Similarly, you can give explicit types to instance variables defined in a method You can only define an instance variable within a method if you assign to it explicitly using self: -.. code:: python +.. code-block:: python class A: def __init__(self) -> None: @@ -356,7 +356,7 @@ Overriding statically typed methods When overriding a statically typed method, mypy checks that the override has a compatible signature: -.. code:: python +.. code-block:: python class A: def f(self, x: int) -> None: @@ -382,7 +382,7 @@ You can also override a statically typed method with a dynamically typed one. Th There is no runtime enforcement that the method override returns a value that is compatible with the original return type, since types are erased in the Python semantics: -.. code:: python +.. code-block:: python class A: def inc(self, x: int) -> int: @@ -402,14 +402,14 @@ Declaring multiple variable types on a line You can declare more than a single variable at a time. In order to nicely work with multiple assignment, you must give each variable a type separately: -.. code:: python +.. code-block:: python n, s = Undefined(int), Undefined(str) # Declare an integer and a string i, found = 0, False # type: int, bool When using the latter form, you can optinally use parentheses around the types, assignment targets and assigned expression: -.. code:: python +.. code-block:: python i, found = 0, False # type: (int, bool) # OK (i, found) = 0, False # type: int, bool # OK @@ -421,7 +421,7 @@ Dynamically typed code As mentioned earlier, bodies of functions that don't have have an explicit return type are dynamically typed (operations are checked at runtime). Code outside functions is statically typed by default, and types of variables are inferred. This does usually the right thing, but you can also make any variable dynamically typed by defining it explicitly with the type Any: -.. code:: python +.. code-block:: python from typing import Any @@ -432,7 +432,7 @@ As mentioned earlier, bodies of functions that don't have have an explicit retur Alternatively, you can use the Undefined construct to define dynamically typed variables, as Any can be used anywhere any other type is valid: -.. code:: python +.. code-block:: python from typing import Undefined, Any @@ -451,7 +451,7 @@ Abstract base classes and multiple inheritance Mypy uses Python abstract base classes for protocol types. There are several built-in abstract base classes types (for example, Sequence, Iterable and Iterator). You can define abstract base classes using the abc.ABCMeta metaclass and the abc.abstractmethod function decorator. -.. code:: python +.. code-block:: python from abc import ABCMeta, abstractmethod import typing @@ -484,7 +484,7 @@ Function overloading You can define multiple instances of a function with the same name but different signatures. The first matching signature is selected at runtime when evaluating each individual call. This enables also a form of multiple dispatch. -.. code:: python +.. code-block:: python from typing import overload @@ -501,7 +501,7 @@ You can define multiple instances of a function with the same name but different Overloaded function variants still define a single runtime object; the following code is valid: -.. code:: python +.. code-block:: python my_abs = abs my_abs(-2) # 2 (int) @@ -518,7 +518,7 @@ Callable types and lambdas You can pass around function objects and bound methods in statically typed code. The type of a function that accepts arguments A1, ..., An and returns Rt is Function[[A1, ..., An], Rt]. Example: -.. code:: python +.. code-block:: python def twice(i: int, next: Function[[int], int]) -> int: return next(next(i)) @@ -530,7 +530,7 @@ You can pass around function objects and bound methods in statically typed code. Lambdas are also supported. The lambda argument and return value types cannot be given explicitly; they are always inferred based on context using bidirectional type inference: -.. code:: python +.. code-block:: python l = map(lambda x: x + 1, [1, 2, 3]) # infer x as int and l as List[int] @@ -541,7 +541,7 @@ Casts Mypy supports type casts that are usually used to coerce a statically typed value to a subtype. Unlike languages such as Java or C#, however, mypy casts are only used as hints for the type checker when using Python semantics, and they have no runtime effect. Use the function cast to perform a cast: -.. code:: python +.. code-block:: python from typing import cast @@ -555,7 +555,7 @@ You don't need a cast for expressions with type Any, of when assigning to a vari You can cast to a dynamically typed value by just calling Any: -.. code:: python +.. code-block:: python from typing import Any @@ -569,7 +569,7 @@ Statically typed function bodies are often identical to normal Python code, but First, you need to specify the type when creating an empty list or dict and when you assign to a new variable, as mentioned earlier: -.. code:: python +.. code-block:: python a = List[int]() # Explicit type required in statically typed code a = [] # Fine in a dynamically typed function, or if type @@ -577,7 +577,7 @@ First, you need to specify the type when creating an empty list or dict and when Sometimes you can avoid the explicit list item type by using a list comprehension. Here a type annotation is needed: -.. code:: python +.. code-block:: python l = List[int]() for i in range(n): @@ -589,7 +589,7 @@ Sometimes you can avoid the explicit list item type by using a list comprehensio No type annotation needed if using a list comprehension: -.. code:: python +.. code-block:: python l = [i * i for i in range(n)] @@ -597,7 +597,7 @@ However, in more complex cases the explicit type annotation can improve the clar Second, each name within a function only has a single type. You can reuse for loop indices etc., but if you want to use a variable with multiple types within a single function, you may need to declare it with the Any type. -.. code:: python +.. code-block:: python def f() -> None: n = 1 @@ -610,7 +610,7 @@ Second, each name within a function only has a single type. You can reuse for lo Third, sometimes the inferred type is a subtype of the desired type. The type inference uses the first assignment to infer the type of a name: -.. code:: python +.. code-block:: python # Assume Shape is the base class of both Circle and Triangle. shape = Circle() # Infer shape to be Circle @@ -619,7 +619,7 @@ Third, sometimes the inferred type is a subtype of the desired type. The type in You can just give an explicit type for the variable in cases such the above example: -.. code:: python +.. code-block:: python shape = Circle() # type: Shape # The variable s can be any Shape, # not just Circle @@ -628,7 +628,7 @@ You can just give an explicit type for the variable in cases such the above exam Fourth, if you use isinstance tests or other kinds of runtime type tests, you may have to add casts (this is similar to instanceof tests in Java): -.. code:: python +.. code-block:: python def f(o: object) -> None: if isinstance(o, int): @@ -649,7 +649,7 @@ The built-in collection classes are generic classes. Generic types have one or m Programs can also define new generic classes. Here is a very simple generic class that represents a stack: -.. code:: python +.. code-block:: python from typing import typevar, Generic @@ -672,7 +672,7 @@ The Stack class can be used to represent a stack of any type: Stack[int], Stack[ Using Stack is similar to built-in container types: -.. code:: python +.. code-block:: python stack = Stack[int]() # Construct an empty Stack[int] instance stack.push(2) @@ -681,7 +681,7 @@ Using Stack is similar to built-in container types: Type inference works for user-defined generic types as well: -.. code:: python +.. code-block:: python def process(stack: Stack[int]) -> None: ... @@ -705,7 +705,7 @@ Note that built-in types list, dict and so on do not support indexing in Python. The above examples illustrate that type variables are erased at runtime when running in a Python VM. Generic Stack or list instances are just ordinary Python objects, and they have no extra runtime overhead or magic due to being generic, other than a metaclass that overloads the indexing operator. If you worry about the overhead introduced by the type indexing operation when constructing instances, you can often rewrite such code using a # type annotation, which has no runtime impact: -.. code:: python +.. code-block:: python x = List[int]() x = [] # type: List[int] # Like the above but faster. @@ -717,7 +717,7 @@ Generic functions Generic type variables can also be used to define generic functions: -.. code:: python +.. code-block:: python from typing import typevar, Sequence @@ -728,7 +728,7 @@ Generic type variables can also be used to define generic functions: As with generic classes, the type variable can be replaced with any type. That means first can we used with any sequence type, and the return type is derived from the sequence item type. For example: -.. code:: python +.. code-block:: python # Assume first defined as above. @@ -737,7 +737,7 @@ As with generic classes, the type variable can be replaced with any type. That m Note also that a single definition of a type variable (such as T above) can be used in multiple generic functions or classes. In this example we use the same type variable in two generic functions: -.. code:: python +.. code-block:: python from typing typevar, Sequence @@ -784,7 +784,7 @@ None Currently, None is a valid value for each type, similar to null or NULL in many languages. However, it is likely that this decision will be reversed, and types do not include None default. The Optional type modifier can be used to define a type variant that includes None, such as Optional(int): -.. code:: python +.. code-block:: python def f() -> Optional[int]: return None # OK @@ -795,14 +795,14 @@ Currently, None is a valid value for each type, similar to null or NULL in many Also, most operations would not be supported on None values: -.. code:: python +.. code-block:: python def f(x: Optional[int]) -> int: return x + 1 # Error: Cannot add None and int Instead, an explicit None check would be required. This would benefit from more powerful type inference: -.. code:: python +.. code-block:: python def f(x: Optional[int]) -> int: if x is None: @@ -820,7 +820,7 @@ Python functions often accept values of two or more different types. You can use Use the Union[...] type constructor to construct a union type. For example, the type Union[int, str] is compatible with both integers and strings. You can use an isinstance check to narrow down the type to a specific type: -.. code:: python +.. code-block:: python from typing import Union @@ -842,7 +842,7 @@ More general type inference It may be useful to support type inference also for variables defined in multiple locations in an if/else statement, even if the initializer types are different: -.. code:: python +.. code-block:: python if x: y = None # First definition of y From 6f64be8c44a6a4c21a16bbd6a3c7b4dca1232f6e Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Tue, 14 Oct 2014 20:32:09 +0200 Subject: [PATCH 069/144] Fixed %f and %d to both accept floats and ints. Small fixes --- mypy/checker.py | 2 +- mypy/checkexpr.py | 38 +++++++++++++-------------- mypy/test/data/check-expressions.test | 13 ++++++--- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 19cf07536b86..a17511f4d5ec 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1383,7 +1383,7 @@ def visit_try_stmt(self, s: TryStmt) -> Type: def exception_type(self, n: Node) -> Type: if isinstance(n, ParenExpr): # Multiple exception types (...). - unwrapped = self.expr_checker.unwrap(n) + unwrapped = self.expr_checker.strip_parens(n) if isinstance(unwrapped, TupleExpr): t = None # type: Type for item in unwrapped.items: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 89b21962ec44..055d83566733 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -34,6 +34,7 @@ from mypy.semanal import self_type from mypy.constraints import get_actual_type + class ExpressionChecker: """Expression type checker. @@ -801,7 +802,7 @@ def parse_conversion_specifiers(self, format: str) -> List[ConversionSpecifier]: for parens_key, key, flags, width, precision, type in re.findall(regex, format): if parens_key == '': key = None - specifiers.append(self.ConversionSpecifier(key, flags, width, precision, type)) + specifiers.append(ExpressionChecker.ConversionSpecifier(key, flags, width, precision, type)) return specifiers def analyse_conversion_specifiers(self, specifiers: List[ConversionSpecifier], @@ -827,7 +828,7 @@ def check_simple_str_interpolation(self, specifiers: List[ConversionSpecifier], rhs_type = self.accept(replacements) rep_types = [] # type: List[Type] if isinstance(rhs_type, TupleType): - rep_types = cast(TupleType, rhs_type).items + rep_types = rhs_type.items elif isinstance(rhs_type, AnyType): return else: @@ -853,11 +854,6 @@ def check_mapping_str_interpolation(self, specifiers: List[ConversionSpecifier], all(isinstance(self.strip_parens(k), StrExpr) for k, v in cast(DictExpr, replacements).items)) if dict_with_only_str_literal_keys: - # check key mapping - checkers = self.build_replacement_checkers(specifiers, replacements) - if checkers == None: - return - mapping = {} # type: Dict[str, Type] for k, v in cast(DictExpr, replacements).items: key_str = cast(StrExpr, k).value @@ -869,6 +865,8 @@ def check_mapping_str_interpolation(self, specifiers: List[ConversionSpecifier], return rep_type = mapping[specifier.key] expected_type = self.conversion_type(specifier.type, replacements) + if expected_type == None: + return self.chk.check_subtype(rep_type, expected_type, replacements, messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, 'expression has type', 'placeholder with key \'%s\' has type' % specifier.key) @@ -935,14 +933,21 @@ def check(node: Node = None, type: Type = None) -> None: return checkers def conversion_type(self, p: str, context: Context) -> Type: + """Return the type that is accepted for a string interpolation + conversion specifier type. + + Note that both Python's float (e.g. %f) and integer (e.g. %d) + specifier types accept both float and integers. + """ if p in ['s', 'r']: return AnyType() - elif p in ['d', 'i', 'o', 'u', 'x', 'X']: - return self.named_type('builtins.int') - elif p in ['e', 'E', 'f', 'F', 'g', 'G']: - return self.named_type('builtins.float') + elif p in ['d', 'i', 'o', 'u', 'x', 'X', + 'e', 'E', 'f', 'F', 'g', 'G']: + return UnionType([self.named_type('builtins.int'), + self.named_type('builtins.float')]) elif p in ['c']: return UnionType([self.named_type('builtins.int'), + self.named_type('builtins.float'), self.named_type('builtins.str')]) else: self.msg.unsupported_placeholder(p, context) @@ -1162,7 +1167,7 @@ def visit_index_expr_helper(self, e: IndexExpr) -> Type: if isinstance(left_type, TupleType): # Special case for tuples. They support indexing only by integer # literals. - index = self.unwrap(e.index) + index = self.strip_parens(e.index) ok = False if isinstance(index, IntExpr): n = index.value @@ -1466,18 +1471,11 @@ def has_member(self, typ: Type, member: str) -> bool: else: return False - def unwrap(self, e: Node) -> Node: - """Unwrap parentheses from an expression node.""" - if isinstance(e, ParenExpr): - return self.unwrap(e.expr) - else: - return e - def unwrap_list(self, a: List[Node]) -> List[Node]: """Unwrap parentheses from a list of expression nodes.""" r = List[Node]() for n in a: - r.append(self.unwrap(n)) + r.append(self.strip_parens(n)) return r def erase(self, type: Type) -> Type: diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index 6df9380fc2a6..0b19d8553af4 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -903,8 +903,8 @@ i, f, s = Undefined, Undefined, Undefined # type: (int, float, str) '%d' % i '%f' % f '%s' % s -'%d' % s # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "int") -'%f' % s # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "float") +'%d' % s # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "Union[int, float]") +'%f' % s # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "Union[int, float]") [builtins fixtures/primitives.py] [case testStringInterpolationSAcceptsAnyType] @@ -919,7 +919,7 @@ i, o, s = Undefined, Undefined, Undefined # type: (int, object, str) '%d %d' % (1, 2, 3) # E: Not all arguments converted during string formatting t = 1, 's' '%d %s' % t -'%s %d' % t # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "int") +'%s %d' % t # E: Incompatible types in string interpolation (expression has type "str", placeholder has type "Union[int, float]") '%d' % t # E: Not all arguments converted during string formatting [builtins fixtures/primitives.py] @@ -966,21 +966,25 @@ a = Undefined # type: Any '%3% %d' % 1 '%*%' % 1 '%*% %d' % 1 # E: Not enough arguments for format string +[builtins fixtures/primitives.py] [case testStringInterpolationC] '%c' % 1 '%c' % 's' '%c' % '' # E: %c requires int or char '%c' % 'ab' # E: %c requires int or char +[builtins fixtures/primitives.py] [case testStringInterpolationMappingTypes] '%(a)d %(b)s' % {'a': 1, 'b': 's'} -'%(a)d %(b)s' % {'a': 's', 'b': 1} # E: Incompatible types in string interpolation (expression has type "str", placeholder with key 'a' has type "int") +'%(a)d %(b)s' % {'a': 's', 'b': 1} # E: Incompatible types in string interpolation (expression has type "str", placeholder with key 'a' has type "Union[int, float]") +[builtins fixtures/primitives.py] [case testStringInterpolationMappingKeys] '%()d' % {'': 2} '%(a)d' % {'a': 1, 'b': 2, 'c': 3} '%(q)d' % {'a': 1, 'b': 2, 'c': 3} # E: Key 'q' not found in mapping +[builtins fixtures/primitives.py] [builtins fixtures/dict.py] [case testStringInterpolationMappingDictTypes] @@ -1009,6 +1013,7 @@ di = Undefined # type: Dict[int, int] '%(a).1d' % {'a': 1} '%(a)#1.1ld' % {'a': 1} [builtins fixtures/dict.py] +[builtins fixtures/primitives.py] -- Lambdas From e329313db3f771905f22d781db6effd29f98a837 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Tue, 14 Oct 2014 21:23:39 +0200 Subject: [PATCH 070/144] Refactored code for creation of conversion specifier checkers --- mypy/checkexpr.py | 132 ++++++++++++++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 44 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 055d83566733..0a78118389c7 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -840,13 +840,16 @@ def check_simple_str_interpolation(self, specifiers: List[ConversionSpecifier], self.msg.too_many_string_formatting_arguments(replacements) else: if len(checkers) == 1: - checkers[0](node=replacements) + check_node, check_type = checkers[0] + check_node(replacements) elif isinstance(replacements, TupleExpr): - for check, rep_node in zip(checkers, replacements.items): - check(node=rep_node) + for checks, rep_node in zip(checkers, replacements.items): + check_node, check_type = checks + check_node(rep_node) else: - for check, rep_type in zip(checkers, rep_types): - check(type=rep_type) + for checks, rep_type in zip(checkers, rep_types): + check_node, check_type = checks + check_type(rep_type) def check_mapping_str_interpolation(self, specifiers: List[ConversionSpecifier], replacements: Node) -> None: @@ -878,8 +881,9 @@ def check_mapping_str_interpolation(self, specifiers: List[ConversionSpecifier], 'expression has type', 'expected type for mapping is') def build_replacement_checkers(self, specifiers: List[ConversionSpecifier], - context: Context) -> List[ Function[[Node, Type], None] ]: - checkers = [] # type: List[ Function[[Node, Type], None] ] + context: Context) -> List[ Tuple[ Function[[Node], None], + Function[[Type], None] ] ]: + checkers = [] # type: List[ Tuple[ Function[[Node], None], Function[[Type], None] ] ] for specifier in specifiers: checker = self.replacement_checkers(specifier, context) if checker == None: @@ -888,50 +892,90 @@ def build_replacement_checkers(self, specifiers: List[ConversionSpecifier], return checkers def replacement_checkers(self, specifier: ConversionSpecifier, - context: Context) -> List[ Function[[Node, Type], None] ]: - """Returns a list of functions that check whether a replacement is - of the right type for a specifier. The functions take either a node - or a type. When a node is specified, the type of the node is checked - again, now in the right type context + context: Context) -> List[ Tuple[ Function[[Node], None], + Function[[Type], None] ] ]: + """Returns a list of tuples of two functions that check whether a replacement is + of the right type for the specifier. The first functions take a node and checks + its type in the right type context. The second function just checks a type. """ - checkers = [] # type: List[ Function[[Node, Type], None] ] - - def check_star(node: Node = None, type: Type = None) -> None: - expected = self.named_type('builtins.int') - if node: - type = self.accept(node, expected) - self.chk.check_subtype(type, expected, context, '* wants int') + checkers = [] # type: List[ Tuple[ Function[[Node], None], Function[[Type], None] ] ] if specifier.width == '*': - checkers.append(check_star) + checkers.append(self.checkers_for_star(context)) if specifier.precision == '*': - checkers.append(check_star) - if specifier.type != '%': - expected_type = self.conversion_type(specifier.type, context) - if expected_type == None: + checkers.append(self.checkers_for_star(context)) + if specifier.type == 'c': + c = self.checkers_for_c_type(specifier.type, context) + if c == None: return None - - if specifier.type == 'c': - def check_c(node: Node = None, type: Type = None) -> None: - '''int, or str with length 1''' - if node: - type = self.accept(node, expected_type) - if isinstance(node, StrExpr) and len(cast(StrExpr, node).value) != 1: - self.msg.requires_int_or_char(context) - self.chk.check_subtype(type, expected_type, context, - messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, - 'expression has type', 'placeholder has type') - checkers.append(check_c) - else: - def check(node: Node = None, type: Type = None) -> None: - if node: - type = self.accept(node, expected_type) - self.chk.check_subtype(type, expected_type, context, - messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, - 'expression has type', 'placeholder has type') - checkers.append(check) + checkers.append(c) + elif specifier.type != '%': + c = self.checkers_for_regular_type(specifier.type, context) + if c == None: + return None + checkers.append(c) return checkers + def checkers_for_star(self, context: Context) -> Tuple[ Function[[Node], None], + Function[[Type], None] ]: + """Returns a tuple of check functions that check whether, respectively, + a node or a type is compatible with a star in a conversion specifier + """ + expected = self.named_type('builtins.int') + + def check_type(type: Type = None) -> None: + expected = self.named_type('builtins.int') + self.chk.check_subtype(type, expected, context, '* wants int') + + def check_node(node: Node) -> None: + type = self.accept(node, expected) + check_type(type) + + return check_node, check_type + + def checkers_for_regular_type(self, type: str, context: Context) -> Tuple[ Function[[Node], None], + Function[[Type], None] ]: + """Returns a tuple of check functions that check whether, respectively, + a node or a type is compatible with 'type'. Return None in case of an + """ + expected_type = self.conversion_type(type, context) + if expected_type == None: + return None + + def check_type(type: Type = None) -> None: + self.chk.check_subtype(type, expected_type, context, + messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + 'expression has type', 'placeholder has type') + + def check_node(node: Node) -> None: + type = self.accept(node, expected_type) + check_type(type) + + return check_node, check_type + + def checkers_for_c_type(self, type: str, context: Context) -> Tuple[ Function[[Node], None], + Function[[Type], None] ]: + """Returns a tuple of check functions that check whether, respectively, + a node or a type is compatible with 'type' that is a character type + """ + expected_type = self.conversion_type(type, context) + if expected_type == None: + return None + + def check_type(type: Type = None) -> None: + self.chk.check_subtype(type, expected_type, context, + messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + 'expression has type', 'placeholder has type') + + def check_node(node: Node) -> None: + """int, or str with length 1""" + type = self.accept(node, expected_type) + if isinstance(node, StrExpr) and len(cast(StrExpr, node).value) != 1: + self.msg.requires_int_or_char(context) + check_type(type) + + return check_node, check_type + def conversion_type(self, p: str, context: Context) -> Type: """Return the type that is accepted for a string interpolation conversion specifier type. From 9add187964e4e3794f7725b654b5ac9c837e114f Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Tue, 14 Oct 2014 21:56:11 +0200 Subject: [PATCH 071/144] Moved all string formatter checker code into a new module --- mypy/checkexpr.py | 242 +--------------------------------- mypy/checkstrformat.py | 290 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+), 237 deletions(-) create mode 100644 mypy/checkstrformat.py diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0a78118389c7..14df17832f7a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1,7 +1,5 @@ """Expression type checker. This file is conceptually part of TypeChecker.""" -import re - from typing import Undefined, cast, List, Tuple, Dict, Function from mypy.types import ( @@ -33,6 +31,7 @@ from mypy.checkmember import analyse_member_access, type_object_type from mypy.semanal import self_type from mypy.constraints import get_actual_type +from mypy.checkstrformat import StringFormatterChecker class ExpressionChecker: @@ -46,12 +45,15 @@ class ExpressionChecker: # This is shared with TypeChecker, but stored also here for convenience. msg = Undefined(MessageBuilder) + strfrm_checker = Undefined('mypy.checkstrformat.StringFormatterChecker') + def __init__(self, chk: 'mypy.checker.TypeChecker', msg: MessageBuilder) -> None: """Construct an expression type checker.""" self.chk = chk self.msg = msg + self.strfrm_checker = mypy.checkexpr.StringFormatterChecker(self, self.chk, self.msg) def visit_name_expr(self, e: NameExpr) -> Type: """Type check a name expression. @@ -751,7 +753,7 @@ def visit_op_expr(self, e: OpExpr) -> Type: # Expressions of form [...] * e get special type inference. return self.check_list_multiply(e) if e.op == '%' and isinstance(e.left, StrExpr): - return self.check_str_interpolation(cast(StrExpr, e.left), e.right) + return self.strfrm_checker.check_str_interpolation(cast(StrExpr, e.left), e.right) left_type = self.accept(e.left) if e.op in nodes.op_methods: @@ -763,240 +765,6 @@ def visit_op_expr(self, e: OpExpr) -> Type: else: raise RuntimeError('Unknown operator {}'.format(e.op)) - def check_str_interpolation(self, str: StrExpr, replacements: Node) -> Type: - replacements = self.strip_parens(replacements) - specifiers = self.parse_conversion_specifiers(str.value) - has_mapping_keys = self.analyse_conversion_specifiers(specifiers, str) - if has_mapping_keys == None: - pass # Error was reported - elif has_mapping_keys: - self.check_mapping_str_interpolation(specifiers, replacements) - else: - self.check_simple_str_interpolation(specifiers, replacements) - return self.named_type('builtins.str') - - class ConversionSpecifier: - def __init__(self, key: str, flags: str, width: str, precision: str, type: str) -> None: - self.key = key - self.flags = flags - self.width = width - self.precision = precision - self.type = type - - def has_key(self): - return self.key != None - - def has_star(self): - return self.width == '*' or self.precision == '*' - - def parse_conversion_specifiers(self, format: str) -> List[ConversionSpecifier]: - key_regex = r'(\((\w*)\))?' # (optional) parenthesised sequence of characters - flags_regex = r'([#0\-+ ]*)' # (optional) sequence of flags - width_regex = r'(\*|[1-9][0-9]*)?' # (optional) minimum field width (* or numbers) - precision_regex = r'(?:\.(\*|[0-9]+))?' # (optional) . followed by * of numbers - length_mod_regex = r'[hlL]?' # (optional) length modifier (unused) - type_regex = r'(.)?' # conversion type - regex = ('%' + key_regex + flags_regex + width_regex + - precision_regex + length_mod_regex + type_regex) - specifiers = [] # type: List[ExpressionChecker.ConversionSpecifier] - for parens_key, key, flags, width, precision, type in re.findall(regex, format): - if parens_key == '': - key = None - specifiers.append(ExpressionChecker.ConversionSpecifier(key, flags, width, precision, type)) - return specifiers - - def analyse_conversion_specifiers(self, specifiers: List[ConversionSpecifier], - context: Context) -> bool: - has_star = any(specifier.has_star() for specifier in specifiers) - has_key = any(specifier.has_key() for specifier in specifiers) - all_have_keys = all(specifier.has_key() for specifier in specifiers) - - if has_key and has_star: - self.msg.string_interpolation_with_star_and_key(context) - return None - if has_key and not all_have_keys: - self.msg.string_interpolation_mixing_key_and_non_keys(context) - return None - return has_key - - def check_simple_str_interpolation(self, specifiers: List[ConversionSpecifier], - replacements: Node) -> None: - checkers = self.build_replacement_checkers(specifiers, replacements) - if checkers == None: - return - - rhs_type = self.accept(replacements) - rep_types = [] # type: List[Type] - if isinstance(rhs_type, TupleType): - rep_types = rhs_type.items - elif isinstance(rhs_type, AnyType): - return - else: - rep_types = [rhs_type] - - if len(checkers) > len(rep_types): - self.msg.too_few_string_formatting_arguments(replacements) - elif len(checkers) < len(rep_types): - self.msg.too_many_string_formatting_arguments(replacements) - else: - if len(checkers) == 1: - check_node, check_type = checkers[0] - check_node(replacements) - elif isinstance(replacements, TupleExpr): - for checks, rep_node in zip(checkers, replacements.items): - check_node, check_type = checks - check_node(rep_node) - else: - for checks, rep_type in zip(checkers, rep_types): - check_node, check_type = checks - check_type(rep_type) - - def check_mapping_str_interpolation(self, specifiers: List[ConversionSpecifier], - replacements: Node) -> None: - dict_with_only_str_literal_keys = (isinstance(replacements, DictExpr) and - all(isinstance(self.strip_parens(k), StrExpr) - for k, v in cast(DictExpr, replacements).items)) - if dict_with_only_str_literal_keys: - mapping = {} # type: Dict[str, Type] - for k, v in cast(DictExpr, replacements).items: - key_str = cast(StrExpr, k).value - mapping[key_str] = self.accept(v) - - for specifier in specifiers: - if specifier.key not in mapping: - self.msg.key_not_in_mapping(specifier.key, replacements) - return - rep_type = mapping[specifier.key] - expected_type = self.conversion_type(specifier.type, replacements) - if expected_type == None: - return - self.chk.check_subtype(rep_type, expected_type, replacements, - messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, - 'expression has type', 'placeholder with key \'%s\' has type' % specifier.key) - else: - rep_type = self.accept(replacements) - dict_type = self.chk.named_generic_type('builtins.dict', - [AnyType(), AnyType()]) - self.chk.check_subtype(rep_type, dict_type, replacements, messages.FORMAT_REQUIRES_MAPPING, - 'expression has type', 'expected type for mapping is') - - def build_replacement_checkers(self, specifiers: List[ConversionSpecifier], - context: Context) -> List[ Tuple[ Function[[Node], None], - Function[[Type], None] ] ]: - checkers = [] # type: List[ Tuple[ Function[[Node], None], Function[[Type], None] ] ] - for specifier in specifiers: - checker = self.replacement_checkers(specifier, context) - if checker == None: - return None - checkers.extend(checker) - return checkers - - def replacement_checkers(self, specifier: ConversionSpecifier, - context: Context) -> List[ Tuple[ Function[[Node], None], - Function[[Type], None] ] ]: - """Returns a list of tuples of two functions that check whether a replacement is - of the right type for the specifier. The first functions take a node and checks - its type in the right type context. The second function just checks a type. - """ - checkers = [] # type: List[ Tuple[ Function[[Node], None], Function[[Type], None] ] ] - - if specifier.width == '*': - checkers.append(self.checkers_for_star(context)) - if specifier.precision == '*': - checkers.append(self.checkers_for_star(context)) - if specifier.type == 'c': - c = self.checkers_for_c_type(specifier.type, context) - if c == None: - return None - checkers.append(c) - elif specifier.type != '%': - c = self.checkers_for_regular_type(specifier.type, context) - if c == None: - return None - checkers.append(c) - return checkers - - def checkers_for_star(self, context: Context) -> Tuple[ Function[[Node], None], - Function[[Type], None] ]: - """Returns a tuple of check functions that check whether, respectively, - a node or a type is compatible with a star in a conversion specifier - """ - expected = self.named_type('builtins.int') - - def check_type(type: Type = None) -> None: - expected = self.named_type('builtins.int') - self.chk.check_subtype(type, expected, context, '* wants int') - - def check_node(node: Node) -> None: - type = self.accept(node, expected) - check_type(type) - - return check_node, check_type - - def checkers_for_regular_type(self, type: str, context: Context) -> Tuple[ Function[[Node], None], - Function[[Type], None] ]: - """Returns a tuple of check functions that check whether, respectively, - a node or a type is compatible with 'type'. Return None in case of an - """ - expected_type = self.conversion_type(type, context) - if expected_type == None: - return None - - def check_type(type: Type = None) -> None: - self.chk.check_subtype(type, expected_type, context, - messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, - 'expression has type', 'placeholder has type') - - def check_node(node: Node) -> None: - type = self.accept(node, expected_type) - check_type(type) - - return check_node, check_type - - def checkers_for_c_type(self, type: str, context: Context) -> Tuple[ Function[[Node], None], - Function[[Type], None] ]: - """Returns a tuple of check functions that check whether, respectively, - a node or a type is compatible with 'type' that is a character type - """ - expected_type = self.conversion_type(type, context) - if expected_type == None: - return None - - def check_type(type: Type = None) -> None: - self.chk.check_subtype(type, expected_type, context, - messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, - 'expression has type', 'placeholder has type') - - def check_node(node: Node) -> None: - """int, or str with length 1""" - type = self.accept(node, expected_type) - if isinstance(node, StrExpr) and len(cast(StrExpr, node).value) != 1: - self.msg.requires_int_or_char(context) - check_type(type) - - return check_node, check_type - - def conversion_type(self, p: str, context: Context) -> Type: - """Return the type that is accepted for a string interpolation - conversion specifier type. - - Note that both Python's float (e.g. %f) and integer (e.g. %d) - specifier types accept both float and integers. - """ - if p in ['s', 'r']: - return AnyType() - elif p in ['d', 'i', 'o', 'u', 'x', 'X', - 'e', 'E', 'f', 'F', 'g', 'G']: - return UnionType([self.named_type('builtins.int'), - self.named_type('builtins.float')]) - elif p in ['c']: - return UnionType([self.named_type('builtins.int'), - self.named_type('builtins.float'), - self.named_type('builtins.str')]) - else: - self.msg.unsupported_placeholder(p, context) - return None - def strip_parens(self, node: Node) -> Node: if isinstance(node, ParenExpr): return self.strip_parens(node.expr) diff --git a/mypy/checkstrformat.py b/mypy/checkstrformat.py new file mode 100644 index 000000000000..f6e5e68f5e8e --- /dev/null +++ b/mypy/checkstrformat.py @@ -0,0 +1,290 @@ +"""Expression type checker. This file is conceptually part of ExpressionChecker and TypeChecker.""" + +import re + +from typing import Undefined, cast, List, Tuple, Dict, Function + +from mypy.types import ( + Type, AnyType, TupleType, Instance, UnionType +) +from mypy.nodes import ( + Node, StrExpr, TupleExpr, DictExpr, Context +) +import mypy.checker +from mypy import messages +from mypy.messages import MessageBuilder + + +class ConversionSpecifier: + def __init__(self, key: str, flags: str, width: str, precision: str, type: str) -> None: + self.key = key + self.flags = flags + self.width = width + self.precision = precision + self.type = type + + def has_key(self): + return self.key != None + + def has_star(self): + return self.width == '*' or self.precision == '*' + + +class StringFormatterChecker: + """String interplation/formatter type checker. + + This class works closely together with checker.ExpressionChecker. + """ + + # Some services are provided by a TypeChecker instance. + chk = Undefined('mypy.checker.TypeChecker') + # This is shared with TypeChecker, but stored also here for convenience. + msg = Undefined(MessageBuilder) + # Some services are provided by a ExpressionChecker instance. + exprchk = Undefined('mypy.checkexpr.ExpressionChecker') + + def __init__(self, + exprchk: 'mypy.checkexpr.ExpressionChecker', + chk: 'mypy.checker.TypeChecker', + msg: MessageBuilder) -> None: + """Construct an expression type checker.""" + self.chk = chk + self.exprchk = exprchk + self.msg = msg + + def check_str_interpolation(self, str: StrExpr, replacements: Node) -> Type: + """Check the types of the 'replacements' in a string interpolation + expression: str % replacements + """ + replacements = self.exprchk.strip_parens(replacements) + specifiers = self.parse_conversion_specifiers(str.value) + has_mapping_keys = self.analyse_conversion_specifiers(specifiers, str) + if has_mapping_keys == None: + pass # Error was reported + elif has_mapping_keys: + self.check_mapping_str_interpolation(specifiers, replacements) + else: + self.check_simple_str_interpolation(specifiers, replacements) + return self.named_type('builtins.str') + + def parse_conversion_specifiers(self, format: str) -> List[ConversionSpecifier]: + key_regex = r'(\((\w*)\))?' # (optional) parenthesised sequence of characters + flags_regex = r'([#0\-+ ]*)' # (optional) sequence of flags + width_regex = r'(\*|[1-9][0-9]*)?' # (optional) minimum field width (* or numbers) + precision_regex = r'(?:\.(\*|[0-9]+))?' # (optional) . followed by * of numbers + length_mod_regex = r'[hlL]?' # (optional) length modifier (unused) + type_regex = r'(.)?' # conversion type + regex = ('%' + key_regex + flags_regex + width_regex + + precision_regex + length_mod_regex + type_regex) + specifiers = [] # type: List[ConversionSpecifier] + for parens_key, key, flags, width, precision, type in re.findall(regex, format): + if parens_key == '': + key = None + specifiers.append(ConversionSpecifier(key, flags, width, precision, type)) + return specifiers + + def analyse_conversion_specifiers(self, specifiers: List[ConversionSpecifier], + context: Context) -> bool: + has_star = any(specifier.has_star() for specifier in specifiers) + has_key = any(specifier.has_key() for specifier in specifiers) + all_have_keys = all(specifier.has_key() for specifier in specifiers) + + if has_key and has_star: + self.msg.string_interpolation_with_star_and_key(context) + return None + if has_key and not all_have_keys: + self.msg.string_interpolation_mixing_key_and_non_keys(context) + return None + return has_key + + def check_simple_str_interpolation(self, specifiers: List[ConversionSpecifier], + replacements: Node) -> None: + checkers = self.build_replacement_checkers(specifiers, replacements) + if checkers == None: + return + + rhs_type = self.accept(replacements) + rep_types = [] # type: List[Type] + if isinstance(rhs_type, TupleType): + rep_types = rhs_type.items + elif isinstance(rhs_type, AnyType): + return + else: + rep_types = [rhs_type] + + if len(checkers) > len(rep_types): + self.msg.too_few_string_formatting_arguments(replacements) + elif len(checkers) < len(rep_types): + self.msg.too_many_string_formatting_arguments(replacements) + else: + if len(checkers) == 1: + check_node, check_type = checkers[0] + check_node(replacements) + elif isinstance(replacements, TupleExpr): + for checks, rep_node in zip(checkers, replacements.items): + check_node, check_type = checks + check_node(rep_node) + else: + for checks, rep_type in zip(checkers, rep_types): + check_node, check_type = checks + check_type(rep_type) + + def check_mapping_str_interpolation(self, specifiers: List[ConversionSpecifier], + replacements: Node) -> None: + dict_with_only_str_literal_keys = (isinstance(replacements, DictExpr) and + all(isinstance(self.exprchk.strip_parens(k), StrExpr) + for k, v in cast(DictExpr, replacements).items)) + if dict_with_only_str_literal_keys: + mapping = {} # type: Dict[str, Type] + for k, v in cast(DictExpr, replacements).items: + key_str = cast(StrExpr, k).value + mapping[key_str] = self.accept(v) + + for specifier in specifiers: + if specifier.key not in mapping: + self.msg.key_not_in_mapping(specifier.key, replacements) + return + rep_type = mapping[specifier.key] + expected_type = self.conversion_type(specifier.type, replacements) + if expected_type == None: + return + self.chk.check_subtype(rep_type, expected_type, replacements, + messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + 'expression has type', 'placeholder with key \'%s\' has type' % specifier.key) + else: + rep_type = self.accept(replacements) + dict_type = self.chk.named_generic_type('builtins.dict', + [AnyType(), AnyType()]) + self.chk.check_subtype(rep_type, dict_type, replacements, messages.FORMAT_REQUIRES_MAPPING, + 'expression has type', 'expected type for mapping is') + + def build_replacement_checkers(self, specifiers: List[ConversionSpecifier], + context: Context) -> List[ Tuple[ Function[[Node], None], + Function[[Type], None] ] ]: + checkers = [] # type: List[ Tuple[ Function[[Node], None], Function[[Type], None] ] ] + for specifier in specifiers: + checker = self.replacement_checkers(specifier, context) + if checker == None: + return None + checkers.extend(checker) + return checkers + + def replacement_checkers(self, specifier: ConversionSpecifier, + context: Context) -> List[ Tuple[ Function[[Node], None], + Function[[Type], None] ] ]: + """Returns a list of tuples of two functions that check whether a replacement is + of the right type for the specifier. The first functions take a node and checks + its type in the right type context. The second function just checks a type. + """ + checkers = [] # type: List[ Tuple[ Function[[Node], None], Function[[Type], None] ] ] + + if specifier.width == '*': + checkers.append(self.checkers_for_star(context)) + if specifier.precision == '*': + checkers.append(self.checkers_for_star(context)) + if specifier.type == 'c': + c = self.checkers_for_c_type(specifier.type, context) + if c == None: + return None + checkers.append(c) + elif specifier.type != '%': + c = self.checkers_for_regular_type(specifier.type, context) + if c == None: + return None + checkers.append(c) + return checkers + + def checkers_for_star(self, context: Context) -> Tuple[ Function[[Node], None], + Function[[Type], None] ]: + """Returns a tuple of check functions that check whether, respectively, + a node or a type is compatible with a star in a conversion specifier + """ + expected = self.named_type('builtins.int') + + def check_type(type: Type = None) -> None: + expected = self.named_type('builtins.int') + self.chk.check_subtype(type, expected, context, '* wants int') + + def check_node(node: Node) -> None: + type = self.accept(node, expected) + check_type(type) + + return check_node, check_type + + def checkers_for_regular_type(self, type: str, context: Context) -> Tuple[ Function[[Node], None], + Function[[Type], None] ]: + """Returns a tuple of check functions that check whether, respectively, + a node or a type is compatible with 'type'. Return None in case of an + """ + expected_type = self.conversion_type(type, context) + if expected_type == None: + return None + + def check_type(type: Type = None) -> None: + self.chk.check_subtype(type, expected_type, context, + messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + 'expression has type', 'placeholder has type') + + def check_node(node: Node) -> None: + type = self.accept(node, expected_type) + check_type(type) + + return check_node, check_type + + def checkers_for_c_type(self, type: str, context: Context) -> Tuple[ Function[[Node], None], + Function[[Type], None] ]: + """Returns a tuple of check functions that check whether, respectively, + a node or a type is compatible with 'type' that is a character type + """ + expected_type = self.conversion_type(type, context) + if expected_type == None: + return None + + def check_type(type: Type = None) -> None: + self.chk.check_subtype(type, expected_type, context, + messages.INCOMPATIBLE_TYPES_IN_STR_INTERPOLATION, + 'expression has type', 'placeholder has type') + + def check_node(node: Node) -> None: + """int, or str with length 1""" + type = self.accept(node, expected_type) + if isinstance(node, StrExpr) and len(cast(StrExpr, node).value) != 1: + self.msg.requires_int_or_char(context) + check_type(type) + + return check_node, check_type + + def conversion_type(self, p: str, context: Context) -> Type: + """Return the type that is accepted for a string interpolation + conversion specifier type. + + Note that both Python's float (e.g. %f) and integer (e.g. %d) + specifier types accept both float and integers. + """ + if p in ['s', 'r']: + return AnyType() + elif p in ['d', 'i', 'o', 'u', 'x', 'X', + 'e', 'E', 'f', 'F', 'g', 'G']: + return UnionType([self.named_type('builtins.int'), + self.named_type('builtins.float')]) + elif p in ['c']: + return UnionType([self.named_type('builtins.int'), + self.named_type('builtins.float'), + self.named_type('builtins.str')]) + else: + self.msg.unsupported_placeholder(p, context) + return None + + # + # Helpers + # + + def named_type(self, name: str) -> Instance: + """Return an instance type with type given by the name and no type + arguments. Alias for TypeChecker.named_type. + """ + return self.chk.named_type(name) + + def accept(self, node: Node, context: Type = None) -> Type: + """Type check a node. Alias for TypeChecker.accept.""" + return self.chk.accept(node, context) From 2bbf16764ef8cf763a9e1b533ddf971f3a27f379 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sun, 28 Sep 2014 15:41:05 +0200 Subject: [PATCH 072/144] Added test for parsing nested tuples and lists in for-statements --- mypy/test/data/parse.test | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/mypy/test/data/parse.test b/mypy/test/data/parse.test index 783a6de71efc..efaf296ad25d 100644 --- a/mypy/test/data/parse.test +++ b/mypy/test/data/parse.test @@ -831,8 +831,10 @@ MypyFile:1( [case testForStatement] for x in y: pass -for x, y in z: +for x, (y, w) in z: 1 +for [x, (y, w)] in z: + 1 [out] MypyFile:1( ForStmt:1( @@ -842,10 +844,24 @@ MypyFile:1( PassStmt:2())) ForStmt:3( NameExpr(x) - NameExpr(y) + ParenExpr:3( + TupleExpr:3( + NameExpr(y) + NameExpr(w))) NameExpr(z) Block:3( ExpressionStmt:4( + IntExpr(1)))) + ForStmt:5( + ListExpr:5( + NameExpr(x) + ParenExpr:5( + TupleExpr:5( + NameExpr(y) + NameExpr(w)))) + NameExpr(z) + Block:5( + ExpressionStmt:6( IntExpr(1))))) [case testGlobalDecl] From 311c9ca436193d327d3cde13604e4d1ae33e71f2 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sat, 27 Sep 2014 13:41:08 +0200 Subject: [PATCH 073/144] Added parsing of nested tuples in for statements --- mypy/checker.py | 2 +- mypy/nodes.py | 4 ++-- mypy/parse.py | 4 ++-- mypy/treetransform.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 5c930bf61574..5f8d60c0f224 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1451,7 +1451,7 @@ def analyse_iterable_item_type(self, expr: Node) -> Type: expr) return echk.check_call(method, [], [], expr)[0] - def analyse_index_variables(self, index: List[NameExpr], + def analyse_index_variables(self, index: List[Node], item_type: Type, context: Context) -> None: """Type check or infer for loop or list comprehension index vars.""" # Create a temporary copy of variables with Node item type. diff --git a/mypy/nodes.py b/mypy/nodes.py index c309e88c3470..f84deb04a26f 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -568,13 +568,13 @@ def accept(self, visitor: NodeVisitor[T]) -> T: class ForStmt(Node): # Index variables - index = Undefined(List['NameExpr']) + index = Undefined(List['Node']) # Expression to iterate expr = Undefined(Node) body = Undefined(Block) else_body = Undefined(Block) - def __init__(self, index: List['NameExpr'], expr: Node, body: Block, + def __init__(self, index: List['Node'], expr: Node, body: Block, else_body: Block) -> None: self.index = index self.expr = expr diff --git a/mypy/parse.py b/mypy/parse.py index bb5235e4beb0..fce7965752be 100755 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -879,7 +879,7 @@ def parse_for_index_variables(self) -> Tuple[List[NameExpr], List[Token]]: self.skip() while True: - v = self.parse_name_expr() + v = self.parse_expression(precedence['in']) # prevent parsing of for's 'in' index.append(v) if self.current_str() != ',': commas.append(none) @@ -1174,7 +1174,7 @@ def parse_list_expr(self) -> Node: return expr def parse_generator_expr(self, left_expr: Node) -> GeneratorExpr: - indices = List[List[NameExpr]]() + indices = List[List[Node]]() sequences = List[Node]() for_toks = List[Token]() in_toks = List[Token]() diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 809417caccdc..8e971a758305 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -203,7 +203,7 @@ def visit_while_stmt(self, node: WhileStmt) -> Node: self.optional_block(node.else_body)) def visit_for_stmt(self, node: ForStmt) -> Node: - return ForStmt(self.names(node.index), + return ForStmt(self.nodes(node.index), self.node(node.expr), self.block(node.body), self.optional_block(node.else_body)) From b39106c4e9cc1288e2bd002c48086ceaef6dca96 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sun, 28 Sep 2014 19:21:22 +0200 Subject: [PATCH 074/144] Added test for type inference from nested tuples to index variable of for-statement --- mypy/test/data/check-inference.test | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/mypy/test/data/check-inference.test b/mypy/test/data/check-inference.test index 46765394c34e..a96df8a540d0 100644 --- a/mypy/test/data/check-inference.test +++ b/mypy/test/data/check-inference.test @@ -740,25 +740,32 @@ class B: pass [case testInferenceOfFor2] from typing import Undefined -a, b = Undefined, Undefined # type: (A, B) -for x, y in [(A(), B())]: +a, b, c = Undefined, Undefined, Undefined # type: (A, B, C) +for x, (y, z) in [(A(), (B(), C()))]: b = x # Fail - a = y # Fail + c = y # Fail + a = z # Fail a = x b = y + c = z for xx, yy, zz in [(A(), B())]: # Fail pass +for xx, (yy, zz) in [(A(), B())]: # Fail + pass for xxx, yyy in [(None, None)]: # Fail pass class A: pass class B: pass +class C: pass [builtins fixtures/for.py] [out] main, line 4: Incompatible types in assignment (expression has type "A", variable has type "B") -main, line 5: Incompatible types in assignment (expression has type "B", variable has type "A") -main, line 8: Need more than 2 values to unpack (3 expected) -main, line 10: Need type annotation for variable +main, line 5: Incompatible types in assignment (expression has type "B", variable has type "C") +main, line 6: Incompatible types in assignment (expression has type "C", variable has type "A") +main, line 10: Need more than 2 values to unpack (3 expected) +main, line 12: '__main__.B' object is not iterable +main, line 14: Need type annotation for variable [case testInferenceOfFor3] from typing import Undefined From 1507e00dabddd1444da2ad043dc2fb425b673e49 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Fri, 3 Oct 2014 20:31:51 +0200 Subject: [PATCH 075/144] Removed unused types field from GeneratorExpr and parse_for_index_variables --- mypy/nodes.py | 4 ++-- mypy/parse.py | 4 ++-- mypy/semanal.py | 2 -- mypy/strconv.py | 1 - mypy/treetransform.py | 2 +- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index f84deb04a26f..6da23a30476a 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1175,9 +1175,9 @@ class GeneratorExpr(Node): left_expr = Undefined(Node) sequences_expr = Undefined(List[Node]) condlists = Undefined(List[List[Node]]) - indices = Undefined(List[List[NameExpr]]) + indices = Undefined(List[List[Node]]) - def __init__(self, left_expr: Node, indices: List[List[NameExpr]], + def __init__(self, left_expr: Node, indices: List[List[Node]], sequences: List[Node], condlists: List[List[Node]]) -> None: self.left_expr = left_expr self.sequences = sequences diff --git a/mypy/parse.py b/mypy/parse.py index fce7965752be..443b642554dd 100755 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -869,9 +869,9 @@ def parse_for_stmt(self) -> ForStmt: else_tok)) return node - def parse_for_index_variables(self) -> Tuple[List[NameExpr], List[Token]]: + def parse_for_index_variables(self) -> Tuple[List[Node], List[Token]]: # Parse index variables of a 'for' statement. - index = List[NameExpr]() + index = List[Node]() commas = List[Token]() is_paren = self.current_str() == '(' diff --git a/mypy/semanal.py b/mypy/semanal.py index 567dae3139c6..311f1c674806 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1304,8 +1304,6 @@ def visit_generator_expr(self, expr: GeneratorExpr) -> None: for cond in conditions: cond.accept(self) - # TODO analyze variable types (see visit_for_stmt) - expr.left_expr.accept(self) self.leave() diff --git a/mypy/strconv.py b/mypy/strconv.py index 201b6a7ac4fa..c0ba5c17979f 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -393,7 +393,6 @@ def visit_func_expr(self, o): return self.dump(a, o) def visit_generator_expr(self, o): - # FIX types condlists = o.condlists if any(o.condlists) else None return self.dump([o.left_expr, o.indices, o.sequences, condlists], o) diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 8e971a758305..8d3113e68b1b 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -371,7 +371,7 @@ def visit_generator_expr(self, node: GeneratorExpr) -> Node: def duplicate_generator(self, node: GeneratorExpr) -> GeneratorExpr: return GeneratorExpr(self.node(node.left_expr), - [self.names(index) for index in node.indices], + [self.nodes(index) for index in node.indices], [self.node(s) for s in node.sequences], [[self.node(cond) for cond in conditions] for conditions in node.condlists]) From 5a4e0718f3a3e48e0348f5fe3e0b2fb2edc2e964 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Sat, 4 Oct 2014 16:52:15 +0200 Subject: [PATCH 076/144] Added test for nested tuples in generator expressions --- mypy/test/data/parse.test | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mypy/test/data/parse.test b/mypy/test/data/parse.test index efaf296ad25d..f56a2084115c 100644 --- a/mypy/test/data/parse.test +++ b/mypy/test/data/parse.test @@ -1189,6 +1189,20 @@ MypyFile:1( NameExpr(y) NameExpr(z)))) +[case testGeneratorExpressionNested] +x for y, (p, q) in z +[out] +MypyFile:1( + ExpressionStmt:1( + GeneratorExpr:1( + NameExpr(x) + NameExpr(y) + ParenExpr:1( + TupleExpr:1( + NameExpr(p) + NameExpr(q))) + NameExpr(z)))) + [case testListComprehension] x=[x for y in z] [out] From c6f48622698fa3b19ae09d4b7a135f81e9f6bc6d Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 15 Oct 2014 21:06:32 +0200 Subject: [PATCH 077/144] Added semanal test for nested tuples in generator expressions --- mypy/test/data/semanal-expressions.test | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mypy/test/data/semanal-expressions.test b/mypy/test/data/semanal-expressions.test index f18d62a91c01..f2dc78eee68e 100644 --- a/mypy/test/data/semanal-expressions.test +++ b/mypy/test/data/semanal-expressions.test @@ -250,6 +250,24 @@ MypyFile:1( NameExpr(x* [l]) NameExpr(a [__main__.a])))) +[case testGeneratorExpressionNestedIndex] +a = 0 +x for x, (y, z) in a +[out] +MypyFile:1( + AssignmentStmt:1( + NameExpr(a* [__main__.a]) + IntExpr(0)) + ExpressionStmt:2( + GeneratorExpr:2( + NameExpr(x [l]) + NameExpr(x* [l]) + ParenExpr:2( + TupleExpr:2( + NameExpr(y* [l]) + NameExpr(z* [l]))) + NameExpr(a [__main__.a])))) + [case testLambda] x = 0 lambda: x From c467649a330a86549fde737a5e6d09fb97090eaa Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Wed, 15 Oct 2014 21:23:41 +0200 Subject: [PATCH 078/144] Added type checker test for nested tuples in list comprehension --- mypy/test/data/check-expressions.test | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index 3ffb639f1db5..6b59178b0824 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -927,6 +927,23 @@ class A: pass class B: pass [builtins fixtures/for.py] +[case testSimpleListComprehensionNestedTuples] +from typing import Undefined, List +l = Undefined # type: List[(A, (A, B))] +a = [a2 for a1, (a2, b1) in l] # type: List[A] +b = [a2 for a1, (a2, b1) in l] # type: List[B] # E: List comprehension has incompatible type List[A] +class A: pass +class B: pass +[builtins fixtures/for.py] + +[case testSimpleListComprehensionNestedTuples] +from typing import Undefined, List +l = Undefined # type: List[(int, (int, str))] +[i+d for d, (i, s) in l] +[i+s for d, (i, s) in l] # E: x +[builtins fixtures/for.py] +[builtins fixtures/primitives.py] + [case testListComprehensionWithNonDirectMapping] from typing import Undefined, List a = Undefined # type: List[A] From 2181447455a45adc8de88f4a896d83d89d470832 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Thu, 16 Oct 2014 21:12:26 +0200 Subject: [PATCH 079/144] Fixed testSimpleListComprehensionNestedTuples2 --- mypy/test/data/check-expressions.test | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index 6b59178b0824..392c6d025dce 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -936,13 +936,14 @@ class A: pass class B: pass [builtins fixtures/for.py] -[case testSimpleListComprehensionNestedTuples] +[case testSimpleListComprehensionNestedTuples2] from typing import Undefined, List l = Undefined # type: List[(int, (int, str))] -[i+d for d, (i, s) in l] -[i+s for d, (i, s) in l] # E: x +a = [f(d) for d, (i, s) in l] +b = [f(s) for d, (i, s) in l] # E: Argument 1 to "f" has incompatible type "str"; expected "int" + +def f(x: int): pass [builtins fixtures/for.py] -[builtins fixtures/primitives.py] [case testListComprehensionWithNonDirectMapping] from typing import Undefined, List From 68c375832a770caa2e318e86a330b9a6dcea0232 Mon Sep 17 00:00:00 2001 From: Sander Kersten Date: Thu, 16 Oct 2014 21:21:01 +0200 Subject: [PATCH 080/144] Fixed issue with double builtins in tests --- mypy/test/data/check-expressions.test | 2 -- mypy/test/data/fixtures/dict.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test index 0b19d8553af4..f7b315c24b72 100644 --- a/mypy/test/data/check-expressions.test +++ b/mypy/test/data/check-expressions.test @@ -984,7 +984,6 @@ a = Undefined # type: Any '%()d' % {'': 2} '%(a)d' % {'a': 1, 'b': 2, 'c': 3} '%(q)d' % {'a': 1, 'b': 2, 'c': 3} # E: Key 'q' not found in mapping -[builtins fixtures/primitives.py] [builtins fixtures/dict.py] [case testStringInterpolationMappingDictTypes] @@ -1013,7 +1012,6 @@ di = Undefined # type: Dict[int, int] '%(a).1d' % {'a': 1} '%(a)#1.1ld' % {'a': 1} [builtins fixtures/dict.py] -[builtins fixtures/primitives.py] -- Lambdas diff --git a/mypy/test/data/fixtures/dict.py b/mypy/test/data/fixtures/dict.py index 181d25182d06..3b72368c7f7d 100644 --- a/mypy/test/data/fixtures/dict.py +++ b/mypy/test/data/fixtures/dict.py @@ -19,3 +19,4 @@ def __mul__(self, x: int) -> list[T]: pass class tuple: pass class function: pass +class float: pass From 140e949d9bd21c5b1911b6ac2799e1e48d458496 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 21 Sep 2014 20:37:26 -0700 Subject: [PATCH 081/144] Work in progress for fixing #462 --- mypy/checkexpr.py | 12 ++++++++---- mypy/test/data/check-inference-context.test | 9 +++++++++ mypy/test/data/check-overloading.test | 2 +- mypy/test/data/check-varargs.test | 14 +++++++------- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 2ef8408bee5b..6ae7b7f8b74a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -281,10 +281,14 @@ def infer_function_type_arguments_using_context( erased_ctx = replace_func_type_vars(ctx, ErasedType()) ret_type = callable.ret_type if isinstance(ret_type, TypeVar): - if ret_type.values: - # The return type is a type variable with values, but we can't easily restrict - # type inference to conform to the valid values. Give up and just use function - # arguments for type inference. + if ret_type.values or (not isinstance(ret_type, Instance) or + not cast(Instance, ret_type).args): + # The return type is a type variable. If it has values, we can't easily restrict + # type inference to conform to the valid values. If it's unrestricted, we could + # infer a too general type for the type variable if we use context. Give up and + # just use function arguments for type inference. + # + # See also github issues #462 and #360. ret_type = NoneTyp() args = infer_type_arguments(callable.type_var_ids(), ret_type, erased_ctx) # Only substite non-None and non-erased types. diff --git a/mypy/test/data/check-inference-context.test b/mypy/test/data/check-inference-context.test index 2826f494f31a..bbb6f3a8570b 100644 --- a/mypy/test/data/check-inference-context.test +++ b/mypy/test/data/check-inference-context.test @@ -719,3 +719,12 @@ f([1]) f('') f(1) # E: Argument 1 to "f" has incompatible type "int"; expected "Union[List[None], str]" [builtins fixtures/isinstancelist.py] + +[case testIgnoringInferenceContext] +from typing import typevar, List +T = typevar('T') +def f(x: List[T]) -> T: pass +def g(y: object) -> None: pass +a = [1] +g(f(a)) +[builtins fixtures/list.py] diff --git a/mypy/test/data/check-overloading.test b/mypy/test/data/check-overloading.test index 2e3d809baa4f..66f4e0a7915d 100644 --- a/mypy/test/data/check-overloading.test +++ b/mypy/test/data/check-overloading.test @@ -123,7 +123,7 @@ t = typevar('t') ab, ac, b, c = Undefined, Undefined, Undefined, Undefined # type: (A[B], A[C], B, C) b = f(ab) c = f(ac) -b = f(ac) # E: Argument 1 to "f" has incompatible type A[C]; expected A[B] +b = f(ac) # E: Incompatible types in assignment (expression has type "C", variable has type "B") b = f(b) c = f(b) # E: Incompatible types in assignment (expression has type "B", variable has type "C") @overload diff --git a/mypy/test/data/check-varargs.test b/mypy/test/data/check-varargs.test index bd2ccc649038..8b72d3e5f2f6 100644 --- a/mypy/test/data/check-varargs.test +++ b/mypy/test/data/check-varargs.test @@ -114,9 +114,9 @@ b = Undefined # type: B c = Undefined # type: C o = Undefined # type: object -a = f(o) # E: Argument 1 to "f" has incompatible type "object"; expected "A" -b = f(b, a) # E: Argument 2 to "f" has incompatible type "A"; expected "B" -b = f(a, b) # E: Argument 1 to "f" has incompatible type "A"; expected "B" +a = f(o) # E: Incompatible types in assignment (expression has type "object", variable has type "A") +b = f(b, a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") +b = f(a, b) # E: Incompatible types in assignment (expression has type "A", variable has type "B") o = f() a = f(a) @@ -139,10 +139,10 @@ T = typevar('T') a = Undefined # type: A o = Undefined # type: object -a = f(o) # E: Argument 1 to "f" has incompatible type "object"; expected "A" -a = f(a, o) # E: Argument 2 to "f" has incompatible type "object"; expected "A" -a = f(a, a, o) # E: Argument 3 to "f" has incompatible type "object"; expected "A" -a = f(a, a, a, o) # E: Argument 4 to "f" has incompatible type "object"; expected "A" +a = f(o) # E: Incompatible types in assignment (expression has type "object", variable has type "A") +a = f(a, o) # E: Incompatible types in assignment (expression has type "object", variable has type "A") +a = f(a, a, o) # E: Incompatible types in assignment (expression has type "object", variable has type "A") +a = f(a, a, a, o) # E: Incompatible types in assignment (expression has type "object", variable has type "A") a = f(a) a = f(a, a) From 75d2e0110d8ceb651d35330765dd797c5f836d33 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 25 Oct 2014 11:00:49 -0700 Subject: [PATCH 082/144] Fixes to type inference context Don't use non-generic instance types as return type context if the return type of a function is a type variable. Fixes #462. --- mypy/checkexpr.py | 4 ++-- mypy/test/data/check-generics.test | 4 ++-- mypy/test/data/check-inference.test | 17 ++++++++--------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6ae7b7f8b74a..2e27ce1878fb 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -281,8 +281,8 @@ def infer_function_type_arguments_using_context( erased_ctx = replace_func_type_vars(ctx, ErasedType()) ret_type = callable.ret_type if isinstance(ret_type, TypeVar): - if ret_type.values or (not isinstance(ret_type, Instance) or - not cast(Instance, ret_type).args): + if ret_type.values or (not isinstance(ctx, Instance) or + not cast(Instance, ctx).args): # The return type is a type variable. If it has values, we can't easily restrict # type inference to conform to the valid values. If it's unrestricted, we could # infer a too general type for the type variable if we use context. Give up and diff --git a/mypy/test/data/check-generics.test b/mypy/test/data/check-generics.test index a7c1354340ec..0a6ada737ef9 100644 --- a/mypy/test/data/check-generics.test +++ b/mypy/test/data/check-generics.test @@ -736,8 +736,8 @@ def f(a: List[T]) -> T: pass a, b = Undefined, Undefined # type: (A, B) -b = f([a]) # E: List item 1 has incompatible type "A" -a = f([b]) # E: List item 1 has incompatible type "B" +b = f([a]) # E: Incompatible types in assignment (expression has type "A", variable has type "B") +a = f([b]) # E: Incompatible types in assignment (expression has type "B", variable has type "A") a = f(b) # E: Incompatible types in assignment (expression has type "B", variable has type "A") a = f([a]) diff --git a/mypy/test/data/check-inference.test b/mypy/test/data/check-inference.test index a96df8a540d0..cddc0399dace 100644 --- a/mypy/test/data/check-inference.test +++ b/mypy/test/data/check-inference.test @@ -411,9 +411,9 @@ a = Undefined # type: A b = Undefined # type: B c = Undefined # type: Tuple[A, object] -b = id(a) # E: Argument 1 to "id" has incompatible type "A"; expected "B" -a = id(b) # E: Argument 1 to "id" has incompatible type "B"; expected "A" -a = id(c) # E: Argument 1 to "id" has incompatible type "Tuple[A, object]"; expected "A" +b = id(a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") +a = id(b) # E: Incompatible types in assignment (expression has type "B", variable has type "A") +a = id(c) # E: Incompatible types in assignment (expression has type "Tuple[A, object]", variable has type "A") a = id(a) b = id(b) @@ -426,14 +426,13 @@ class B: pass [builtins fixtures/tuple.py] [case testInferringGenericFunctionTypeForLvar] -# TODO this is actually a contextual inference test case from typing import Undefined, typevar T = typevar('T') def f() -> None: a = id b = Undefined # type: int c = Undefined # type: str - b = a(c) # E: Argument 1 has incompatible type "str"; expected "int" + b = a(c) # E: Incompatible types in assignment (expression has type "str", variable has type "int") b = a(b) c = a(c) def id(x: T) -> T: @@ -479,8 +478,8 @@ T = typevar('T') a = Undefined # type: A b = Undefined # type: B -b = f(a, b) # E: Argument 1 to "f" has incompatible type "A"; expected "B" -b = f(b, a) # E: Argument 2 to "f" has incompatible type "A"; expected "B" +b = f(a, b) # E: Incompatible types in assignment (expression has type "A", variable has type "B") +b = f(b, a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") a = f(a, b) a = f(b, a) @@ -566,8 +565,8 @@ T = typevar('T') a = Undefined # type: A o = Undefined # type: object -a = f(o) # E: Argument 1 to "f" has incompatible type "object"; expected "A" -a = g(a, o) # E: Argument 2 to "g" has incompatible type "object"; expected "A" +a = f(o) # E: Incompatible types in assignment (expression has type "object", variable has type "A") +a = g(a, o) # E: Incompatible types in assignment (expression has type "object", variable has type "A") o = f() o = f(o) From 2ba6f458ce4144ce3e1333b089e429206e563cd1 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 25 Oct 2014 11:05:57 -0700 Subject: [PATCH 083/144] Update comment --- mypy/checkexpr.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 2e27ce1878fb..b664f6955df1 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -285,8 +285,12 @@ def infer_function_type_arguments_using_context( not cast(Instance, ctx).args): # The return type is a type variable. If it has values, we can't easily restrict # type inference to conform to the valid values. If it's unrestricted, we could - # infer a too general type for the type variable if we use context. Give up and - # just use function arguments for type inference. + # infer a too general type for the type variable if we use context, and this could + # result in confusing and spurious type errors elsewhere. + # + # Give up and just use function arguments for type inference. As an exception, + # if the context is a generic instance type, actually use it as context, as + # this *seems* to usually be the reasonable thing to do. # # See also github issues #462 and #360. ret_type = NoneTyp() From c1a85c89d82e493baccfaf1d91d1f735d5347f83 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 13 Sep 2014 13:31:00 -0700 Subject: [PATCH 084/144] Fix potential crash when name can't be bound --- mypy/semanal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index 311f1c674806..273177c09e1e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1375,6 +1375,7 @@ def lookup_qualified(self, name: str, ctx: Context) -> SymbolTableNode: n = (cast(MypyFile, n.node)).names.get(parts[i], None) if not n: self.name_not_defined(name, ctx) + break if n: n = self.normalize_type_alias(n, ctx) return n From 8131d376a9d8c934ed394085206fbe46226fef17 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 13 Sep 2014 13:30:46 -0700 Subject: [PATCH 085/144] Clean up code --- mypy/semanal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 273177c09e1e..e8d810308eaa 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1370,9 +1370,9 @@ def lookup_qualified(self, name: str, ctx: Context) -> SymbolTableNode: if n: for i in range(1, len(parts)): if isinstance(n.node, TypeInfo): - n = (cast(TypeInfo, n.node)).get(parts[i]) + n = cast(TypeInfo, n.node).get(parts[i]) elif isinstance(n.node, MypyFile): - n = (cast(MypyFile, n.node)).names.get(parts[i], None) + n = cast(MypyFile, n.node).names.get(parts[i], None) if not n: self.name_not_defined(name, ctx) break From c5b1166cafe7ad866e400673d12e15c2c4f023f7 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 13 Sep 2014 13:30:15 -0700 Subject: [PATCH 086/144] Fix type check error --- scripts/mypy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/mypy b/scripts/mypy index e37f98cc8f01..f4b202a59cbc 100755 --- a/scripts/mypy +++ b/scripts/mypy @@ -76,12 +76,11 @@ def type_check_only(path: str, module: str, bin_dir: str, options: Options) -> N flags=options.build_flags) -def process_options(args: List[str]) -> Tuple[str, str, List[str], Options]: +def process_options(args: List[str]) -> Tuple[str, str, Options]: """Process command line arguments. Return (mypy program path (or None), module to run as script (or None), - remaining arguments, passed to mypy program parsed flags) """ options = Options() From 906f2faf8735a58b580df53831db9f5f7bcc36e9 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 25 Oct 2014 13:15:25 -0700 Subject: [PATCH 087/144] Minor refactoring --- mypy/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 6da23a30476a..b6380fbd52e4 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -15,7 +15,7 @@ class Context(metaclass=ABCMeta): """Base type for objects that are valid as error message locations.""" - #@abstractmethod + @abstractmethod def get_line(self) -> int: pass From 554db282edaa8a149fe9643a9c179a9c785d7fb8 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 25 Oct 2014 14:11:17 -0700 Subject: [PATCH 088/144] Whitespace changes only --- mypy/checker.py | 74 ++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 1cafd3d867e3..94c314640bc9 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -914,7 +914,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> Type: Handle all kinds of assignment statements (simple, indexed, multiple). """ self.check_assignment(s.lvalues[-1], s.rvalue, s.type == None) - + if len(s.lvalues) > 1: # Chained assignment (e.g. x = y = ...). # Make sure that rvalue type will not be reinferred. @@ -930,23 +930,23 @@ def check_assignment(self, lvalue: Node, rvalue: Node, infer_lvalue_type: bool = elif isinstance(lvalue, TupleExpr) or isinstance(lvalue, ListExpr): ltuple = cast(Union[TupleExpr, ListExpr], lvalue) rvalue = self.remove_parens(rvalue) - + self.check_assignment_to_multiple_lvalues(ltuple.items, rvalue, lvalue, infer_lvalue_type) else: lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue) - + if lvalue_type: rvalue_type = self.check_simple_assignment(lvalue_type, rvalue, lvalue) - + if rvalue_type and infer_lvalue_type: self.binder.assign_type(lvalue, rvalue_type) elif index_lvalue: self.check_indexed_assignment(index_lvalue, rvalue, rvalue) - + if inferred: self.infer_variable_type(inferred, lvalue, self.accept(rvalue), rvalue) - + def check_assignment_to_multiple_lvalues(self, lvalues: List[Node], rvalue: Node, context: Context, infer_lvalue_type: bool = True) -> None: if isinstance(rvalue, TupleExpr) or isinstance(rvalue, ListExpr): @@ -954,9 +954,9 @@ def check_assignment_to_multiple_lvalues(self, lvalues: List[Node], rvalue: Node # using the type of rhs, because this allowed more fine grained # control in cases like: a, b = [int, str] where rhs would get # type List[object] - - rtuple = cast(Union[TupleExpr, ListExpr], rvalue) - + + rtuple = cast(Union[TupleExpr, ListExpr], rvalue) + if len(rtuple.items) != len(lvalues): self.msg.incompatible_value_count_in_assignment( len(lvalues), len(rtuple.items), context) @@ -965,13 +965,13 @@ def check_assignment_to_multiple_lvalues(self, lvalues: List[Node], rvalue: Node self.check_assignment(lv, rv, infer_lvalue_type) else: self.check_multi_assignment(lvalues, rvalue, context, infer_lvalue_type) - + def remove_parens(self, node: Node) -> Node: if isinstance(node, ParenExpr): return self.remove_parens(node.expr) else: return node - + def check_multi_assignment(self, lvalues: List[Node], rvalue: Node, context: Context, @@ -980,10 +980,10 @@ def check_multi_assignment(self, lvalues: List[Node], """Check the assignment of one rvalue to a number of lvalues for example from a ListExpr or TupleExpr. """ - + if not msg: msg = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT - + # First handle case where rvalue is of form Undefined, ... rvalue_type = get_undefined_tuple(rvalue, self.named_type('builtins.tuple')) undefined_rvalue = True @@ -991,64 +991,64 @@ def check_multi_assignment(self, lvalues: List[Node], # Infer the type of an ordinary rvalue expression. rvalue_type = self.accept(rvalue) # TODO maybe elsewhere; redundant undefined_rvalue = False - + if isinstance(rvalue_type, AnyType): for lv in lvalues: self.check_assignment(lv, self.temp_node(AnyType(), context), infer_lvalue_type) elif isinstance(rvalue_type, TupleType): - self.check_multi_assignment_from_tuple(lvalues, rvalue, cast(TupleType, rvalue_type), + self.check_multi_assignment_from_tuple(lvalues, rvalue, cast(TupleType, rvalue_type), context, undefined_rvalue, infer_lvalue_type) else: - self.check_multi_assignment_from_iterable(lvalues, rvalue_type, + self.check_multi_assignment_from_iterable(lvalues, rvalue_type, context, infer_lvalue_type) - - def check_multi_assignment_from_tuple(self, lvalues: List[Node], rvalue: Node, - rvalue_type: TupleType, context: Context, + + def check_multi_assignment_from_tuple(self, lvalues: List[Node], rvalue: Node, + rvalue_type: TupleType, context: Context, undefined_rvalue: bool, infer_lvalue_type: bool = True) -> None: if len(rvalue_type.items) != len(lvalues): self.msg.wrong_number_values_to_unpack(len(rvalue_type.items), len(lvalues), context) else: if not undefined_rvalue: # Create lvalue_type for type inference - + type_parameters = [] # type: List[Type] for i in range(len(lvalues)): sub_lvalue_type, index_expr, inferred = self.check_lvalue(lvalues[i]) - + if sub_lvalue_type: type_parameters.append(sub_lvalue_type) else: # index lvalue # TODO Figure out more precise type context, probably # based on the type signature of the _set method. - type_parameters.append(rvalue_type.items[i]) - + type_parameters.append(rvalue_type.items[i]) + lvalue_type = TupleType(type_parameters, self.named_type('builtins.tuple')) # Infer rvalue again, now in the correct type context. rvalue_type = cast(TupleType, self.accept(rvalue, lvalue_type)) - + for lv, rv_type in zip(lvalues, rvalue_type.items): self.check_assignment(lv, self.temp_node(rv_type, context), infer_lvalue_type) - + def type_is_iterable(self, type: Type) -> bool: return (is_subtype(type, self.named_generic_type('typing.Iterable', [AnyType()])) and isinstance(type, Instance)) - - def check_multi_assignment_from_iterable(self, lvalues: List[Node], rvalue_type: Type, + + def check_multi_assignment_from_iterable(self, lvalues: List[Node], rvalue_type: Type, context: Context, infer_lvalue_type: bool = True) -> None: if self.type_is_iterable(rvalue_type): item_type = self.iterable_item_type(cast(Instance,rvalue_type)) for lv in lvalues: self.check_assignment(lv, self.temp_node(item_type, context), infer_lvalue_type) - else: + else: self.msg.type_not_iterable(rvalue_type, context) - + def check_lvalue(self, lvalue: Node) -> Tuple[Type, IndexExpr, Var]: lvalue_type = None # type: Type index_lvalue = None # type: IndexExpr inferred = None # type: Var - + if self.is_definition(lvalue): if isinstance(lvalue, NameExpr): inferred = cast(Var, lvalue.node) @@ -1073,9 +1073,9 @@ def check_lvalue(self, lvalue: Node) -> Tuple[Type, IndexExpr, Var]: return self.check_lvalue(lvalue.expr) else: lvalue_type = self.accept(lvalue) - + return lvalue_type, index_lvalue, inferred - + def is_definition(self, s: Node) -> bool: if isinstance(s, NameExpr): if s.is_def: @@ -1144,8 +1144,8 @@ def narrow_type_from_binder(self, expr: Node, known_type: Type) -> Type: return ans return known_type - def check_simple_assignment(self, lvalue_type: Type, rvalue: Node, - context: Node, + def check_simple_assignment(self, lvalue_type: Type, rvalue: Node, + context: Node, msg: str = messages.INCOMPATIBLE_TYPES_IN_ASSIGNMENT) -> Type: """Checks the assignment of rvalue to a lvalue of type lvalue_type""" if refers_to_fullname(rvalue, 'typing.Undefined'): @@ -1156,7 +1156,7 @@ def check_simple_assignment(self, lvalue_type: Type, rvalue: Node, else: rvalue_type = self.accept(rvalue, lvalue_type) self.check_subtype(rvalue_type, lvalue_type, context, msg, - 'expression has type', 'variable has type') + 'expression has type', 'variable has type') return rvalue_type def check_indexed_assignment(self, lvalue: IndexExpr, @@ -1452,14 +1452,14 @@ def analyse_iterable_item_type(self, expr: Node) -> Type: return echk.check_call(method, [], [], expr)[0] def analyse_index_variables(self, index: List[Node], - item_type: Type, context: Context) -> None: + item_type: Type, context: Context) -> None: """Type check or infer for loop or list comprehension index vars.""" # Create a temporary copy of variables with Node item type. # TODO this is ugly node_index = [] # type: List[Node] for i in index: node_index.append(i) - + if len(node_index) == 1: self.check_assignment(node_index[0], self.temp_node(item_type, context)) else: From b9ea575f8166d18fb3798d79c8147a18eafb9e3d Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 25 Oct 2014 14:15:35 -0700 Subject: [PATCH 089/144] Refactoring subtyping --- mypy/checker.py | 3 +- mypy/checkmember.py | 2 +- mypy/constraints.py | 4 +- mypy/join.py | 3 +- mypy/maptype.py | 106 ++++++++++++++++++++++++++++++++++++++++++ mypy/subtypes.py | 110 ++------------------------------------------ mypy/types.py | 5 ++ 7 files changed, 121 insertions(+), 112 deletions(-) create mode 100644 mypy/maptype.py diff --git a/mypy/checker.py b/mypy/checker.py index 94c314640bc9..2cecb7a9189b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -29,9 +29,10 @@ import mypy.checkexpr from mypy import messages from mypy.subtypes import ( - is_subtype, is_equivalent, map_instance_to_supertype, is_proper_subtype, + is_subtype, is_equivalent, is_proper_subtype, is_more_precise, restrict_subtype_away ) +from mypy.maptype import map_instance_to_supertype from mypy.semanal import self_type, set_callable_name, refers_to_fullname from mypy.erasetype import erase_typevars from mypy.expandtype import expand_type_by_instance, expand_type diff --git a/mypy/checkmember.py b/mypy/checkmember.py index fe7de03a3589..110d64ecd9f3 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -9,7 +9,7 @@ from mypy.nodes import TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context from mypy.nodes import ARG_POS, function_type, Decorator from mypy.messages import MessageBuilder -from mypy.subtypes import map_instance_to_supertype +from mypy.maptype import map_instance_to_supertype from mypy.expandtype import expand_type_by_instance from mypy.nodes import method_type from mypy.semanal import self_type diff --git a/mypy/constraints.py b/mypy/constraints.py index 385a0f72d834..b6b5af92c66c 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -4,10 +4,10 @@ from mypy.types import ( Callable, Type, TypeVisitor, UnboundType, AnyType, Void, NoneTyp, TypeVar, - Instance, TupleType, UnionType, Overloaded, ErasedType + Instance, TupleType, UnionType, Overloaded, ErasedType, is_named_instance ) from mypy.expandtype import expand_caller_var_args -from mypy.subtypes import map_instance_to_supertype, is_named_instance +from mypy.maptype import map_instance_to_supertype from mypy import nodes diff --git a/mypy/join.py b/mypy/join.py index 48e06f0b4cb2..d3aa7849b4fd 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -7,7 +7,8 @@ ErrorType, TypeVar, Callable, TupleType, ErasedType, TypeList, UnionType, FunctionLike ) -from mypy.subtypes import is_subtype, is_equivalent, map_instance_to_supertype +from mypy.maptype import map_instance_to_supertype +from mypy.subtypes import is_subtype, is_equivalent def join_simple(declaration: Type, s: Type, t: Type) -> Type: diff --git a/mypy/maptype.py b/mypy/maptype.py new file mode 100644 index 000000000000..73771313f2df --- /dev/null +++ b/mypy/maptype.py @@ -0,0 +1,106 @@ +from typing import Dict, List, cast + +from mypy.expandtype import expand_type +from mypy.nodes import TypeInfo +from mypy.types import Type, Instance, AnyType + + +def map_instance_to_supertype(instance: Instance, + supertype: TypeInfo) -> Instance: + """Map an Instance type, including the type arguments, to compatible + Instance of a specific supertype. + + Assume that supertype is a supertype of instance.type. + """ + if instance.type == supertype: + return instance + + # Strip type variables away if the supertype has none. + if not supertype.type_vars: + return Instance(supertype, []) + + return map_instance_to_supertypes(instance, supertype)[0] + + +def map_instance_to_direct_supertype(instance: Instance, + supertype: TypeInfo) -> Instance: + typ = instance.type + + for base in typ.bases: + if base.type == supertype: + map = type_var_map(typ, instance.args) + return cast(Instance, expand_type(base, map)) + + # Relationship with the supertype not specified explicitly. Use AnyType + # type arguments implicitly. + # TODO Should this be an error instead? + return Instance(supertype, [AnyType()] * len(supertype.type_vars)) + + +def type_var_map(typ: TypeInfo, args: List[Type]) -> Dict[int, Type]: + if not args: + return None + else: + tvars = {} # type: Dict[int, Type] + for i in range(len(args)): + tvars[i + 1] = args[i] + return tvars + + +def map_instance_to_supertypes(instance: Instance, + supertype: TypeInfo) -> List[Instance]: + # FIX: Currently we should only have one supertype per interface, so no + # need to return an array + result = [] # type: List[Instance] + for path in class_derivation_paths(instance.type, supertype): + types = [instance] + for sup in path: + a = [] # type: List[Instance] + for t in types: + a.extend(map_instance_to_direct_supertypes(t, sup)) + types = a + result.extend(types) + return result + + +def class_derivation_paths(typ: TypeInfo, + supertype: TypeInfo) -> List[List[TypeInfo]]: + """Return an array of non-empty paths of direct base classes from + type to supertype. Return [] if no such path could be found. + + InterfaceImplementationPaths(A, B) == [[B]] if A inherits B + InterfaceImplementationPaths(A, C) == [[B, C]] if A inherits B and + B inherits C + """ + # FIX: Currently we might only ever have a single path, so this could be + # simplified + result = [] # type: List[List[TypeInfo]] + + for base in typ.bases: + if base.type == supertype: + result.append([base.type]) + else: + # Try constructing a longer path via the base class. + for path in class_derivation_paths(base.type, supertype): + result.append([base.type] + path) + + return result + + +def map_instance_to_direct_supertypes(instance: Instance, + supertype: TypeInfo) -> List[Instance]: + # FIX: There should only be one supertypes, always. + typ = instance.type + result = [] # type: List[Instance] + + for b in typ.bases: + if b.type == supertype: + map = type_var_map(typ, instance.args) + result.append(cast(Instance, expand_type(b, map))) + + if result: + return result + else: + # Relationship with the supertype not specified explicitly. Use dynamic + # type arguments implicitly. + return [Instance(supertype, [AnyType()] * len(supertype.type_vars))] diff --git a/mypy/subtypes.py b/mypy/subtypes.py index f6a495ba55b7..a977bf9066dc 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -2,11 +2,13 @@ from mypy.types import ( Type, AnyType, UnboundType, TypeVisitor, ErrorType, Void, NoneTyp, - Instance, TypeVar, Callable, TupleType, UnionType, Overloaded, ErasedType, TypeList + Instance, TypeVar, Callable, TupleType, UnionType, Overloaded, ErasedType, TypeList, + is_named_instance ) from mypy import sametypes from mypy.nodes import TypeInfo from mypy.expandtype import expand_type +from mypy.maptype import map_instance_to_supertype def is_immutable(t: Instance) -> bool: @@ -190,112 +192,6 @@ def is_callable_subtype(left: Callable, right: Callable) -> bool: return True -def map_instance_to_supertype(instance: Instance, - supertype: TypeInfo) -> Instance: - """Map an Instance type, including the type arguments, to compatible - Instance of a specific supertype. - - Assume that supertype is a supertype of instance.type. - """ - if instance.type == supertype: - return instance - - # Strip type variables away if the supertype has none. - if not supertype.type_vars: - return Instance(supertype, []) - - return map_instance_to_supertypes(instance, supertype)[0] - - -def map_instance_to_direct_supertype(instance: Instance, - supertype: TypeInfo) -> Instance: - typ = instance.type - - for base in typ.bases: - if base.type == supertype: - map = type_var_map(typ, instance.args) - return cast(Instance, expand_type(base, map)) - - # Relationship with the supertype not specified explicitly. Use AnyType - # type arguments implicitly. - # TODO Should this be an error instead? - return Instance(supertype, [AnyType()] * len(supertype.type_vars)) - - -def type_var_map(typ: TypeInfo, args: List[Type]) -> Dict[int, Type]: - if not args: - return None - else: - tvars = {} # type: Dict[int, Type] - for i in range(len(args)): - tvars[i + 1] = args[i] - return tvars - - -def map_instance_to_supertypes(instance: Instance, - supertype: TypeInfo) -> List[Instance]: - # FIX: Currently we should only have one supertype per interface, so no - # need to return an array - result = [] # type: List[Instance] - for path in class_derivation_paths(instance.type, supertype): - types = [instance] - for sup in path: - a = [] # type: List[Instance] - for t in types: - a.extend(map_instance_to_direct_supertypes(t, sup)) - types = a - result.extend(types) - return result - - -def class_derivation_paths(typ: TypeInfo, - supertype: TypeInfo) -> List[List[TypeInfo]]: - """Return an array of non-empty paths of direct base classes from - type to supertype. Return [] if no such path could be found. - - InterfaceImplementationPaths(A, B) == [[B]] if A inherits B - InterfaceImplementationPaths(A, C) == [[B, C]] if A inherits B and - B inherits C - """ - # FIX: Currently we might only ever have a single path, so this could be - # simplified - result = [] # type: List[List[TypeInfo]] - - for base in typ.bases: - if base.type == supertype: - result.append([base.type]) - else: - # Try constructing a longer path via the base class. - for path in class_derivation_paths(base.type, supertype): - result.append([base.type] + path) - - return result - - -def map_instance_to_direct_supertypes(instance: Instance, - supertype: TypeInfo) -> List[Instance]: - # FIX: There should only be one supertypes, always. - typ = instance.type - result = [] # type: List[Instance] - - for b in typ.bases: - if b.type == supertype: - map = type_var_map(typ, instance.args) - result.append(cast(Instance, expand_type(b, map))) - - if result: - return result - else: - # Relationship with the supertype not specified explicitly. Use dynamic - # type arguments implicitly. - return [Instance(supertype, [AnyType()] * len(supertype.type_vars))] - - -def is_named_instance(t: Type, fullname: str) -> bool: - return (isinstance(t, Instance) and - cast(Instance, t).type.fullname() == fullname) - - def restrict_subtype_away(t: Type, s: Type) -> Type: """Return a supertype of (t intersect not s) diff --git a/mypy/types.py b/mypy/types.py index 418eaf4672ef..ea3fc740420e 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -827,3 +827,8 @@ def replace_leading_arg_type(t: Callable, self_type: Type) -> Callable: t.variables, t.bound_vars, t.line, None) + + +def is_named_instance(t: Type, fullname: str) -> bool: + return (isinstance(t, Instance) and + cast(Instance, t).type.fullname() == fullname) From e9aa9f5bcf5d9745da81429ff7fc8c1b3cb69d10 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 25 Oct 2014 14:19:44 -0700 Subject: [PATCH 090/144] Minor refactoring --- mypy/applytype.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++ mypy/checkexpr.py | 58 +++--------------------------------------- 2 files changed, 67 insertions(+), 55 deletions(-) create mode 100644 mypy/applytype.py diff --git a/mypy/applytype.py b/mypy/applytype.py new file mode 100644 index 000000000000..ff9e66caa19c --- /dev/null +++ b/mypy/applytype.py @@ -0,0 +1,64 @@ +from typing import List, Dict + +import mypy.subtypes +from mypy.expandtype import expand_type +from mypy.types import Type, Callable, AnyType +from mypy.messages import MessageBuilder +from mypy.nodes import Context + + +def apply_generic_arguments(callable: Callable, types: List[Type], + msg: MessageBuilder, context: Context) -> Type: + """Apply generic type arguments to a callable type. + + For example, applying [int] to 'def [T] (T) -> T' results in + 'def [-1:int] (int) -> int'. Here '[-1:int]' is an implicit bound type + variable. + + Note that each type can be None; in this case, it will not be applied. + """ + tvars = callable.variables + if len(tvars) != len(types): + msg.incompatible_type_application(len(tvars), len(types), context) + return AnyType() + + # Check that inferred type variable values are compatible with allowed + # values. Also, promote subtype values to allowed values. + types = types[:] + for i, type in enumerate(types): + values = callable.variables[i].values + if values and type: + if isinstance(type, AnyType): + continue + for value in values: + if mypy.subtypes.is_subtype(type, value): + types[i] = value + break + else: + msg.incompatible_typevar_value(callable, i + 1, type, context) + + # Create a map from type variable id to target type. + id_to_type = {} # type: Dict[int, Type] + for i, tv in enumerate(tvars): + if types[i]: + id_to_type[tv.id] = types[i] + + # Apply arguments to argument types. + arg_types = [expand_type(at, id_to_type) for at in callable.arg_types] + + bound_vars = [(tv.id, id_to_type[tv.id]) + for tv in tvars + if tv.id in id_to_type] + + # The callable may retain some type vars if only some were applied. + remaining_tvars = [tv for tv in tvars if tv.id not in id_to_type] + + return Callable(arg_types, + callable.arg_kinds, + callable.arg_names, + expand_type(callable.ret_type, id_to_type), + callable.fallback, + callable.name, + remaining_tvars, + callable.bound_vars + bound_vars, + callable.line, callable.repr) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index b664f6955df1..0b512aea065d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -27,6 +27,7 @@ from mypy import join from mypy.expandtype import expand_type, expand_caller_var_args from mypy.subtypes import is_subtype +from mypy import applytype from mypy import erasetype from mypy.checkmember import analyse_member_access, type_object_type from mypy.semanal import self_type @@ -639,61 +640,8 @@ def match_signature_types(self, arg_types: List[Type], is_var_arg: bool, def apply_generic_arguments(self, callable: Callable, types: List[Type], context: Context) -> Type: - """Apply generic type arguments to a callable type. - - For example, applying [int] to 'def [T] (T) -> T' results in - 'def [-1:int] (int) -> int'. Here '[-1:int]' is an implicit bound type - variable. - - Note that each type can be None; in this case, it will not be applied. - """ - tvars = callable.variables - if len(tvars) != len(types): - self.msg.incompatible_type_application(len(tvars), len(types), - context) - return AnyType() - - # Check that inferred type variable values are compatible with allowed - # values. Also, promote subtype values to allowed values. - types = types[:] - for i, type in enumerate(types): - values = callable.variables[i].values - if values and type: - if isinstance(type, AnyType): - continue - for value in values: - if is_subtype(type, value): - types[i] = value - break - else: - self.msg.incompatible_typevar_value( - callable, i + 1, type, context) - - # Create a map from type variable id to target type. - id_to_type = {} # type: Dict[int, Type] - for i, tv in enumerate(tvars): - if types[i]: - id_to_type[tv.id] = types[i] - - # Apply arguments to argument types. - arg_types = [expand_type(at, id_to_type) for at in callable.arg_types] - - bound_vars = [(tv.id, id_to_type[tv.id]) - for tv in tvars - if tv.id in id_to_type] - - # The callable may retain some type vars if only some were applied. - remaining_tvars = [tv for tv in tvars if tv.id not in id_to_type] - - return Callable(arg_types, - callable.arg_kinds, - callable.arg_names, - expand_type(callable.ret_type, id_to_type), - callable.fallback, - callable.name, - remaining_tvars, - callable.bound_vars + bound_vars, - callable.line, callable.repr) + """Simple wrapper around mypy.applytype.apply_generic_arguments.""" + return applytype.apply_generic_arguments(callable, types, self.msg, context) def apply_generic_arguments2(self, overload: Overloaded, types: List[Type], context: Context) -> Type: From fd22ca202c502d38566cbf1353de5e83c3d142c1 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 25 Oct 2014 14:20:05 -0700 Subject: [PATCH 091/144] Fix subtyping of generic functions Fixes #450. --- mypy/constraints.py | 4 +-- mypy/messages.py | 7 ++++- mypy/subtypes.py | 43 +++++++++++++++++++++++------- mypy/test/data/check-generics.test | 17 ++++++++++++ 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/mypy/constraints.py b/mypy/constraints.py index b6b5af92c66c..7f5f8311eace 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -11,8 +11,8 @@ from mypy import nodes -SUBTYPE_OF = 0 -SUPERTYPE_OF = 1 +SUBTYPE_OF = 0 # type: int +SUPERTYPE_OF = 1 # type: int class Constraint: diff --git a/mypy/messages.py b/mypy/messages.py index 9ee49ee152a5..a6fd4bc68948 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -578,7 +578,7 @@ def check_void(self, typ: Type, context: Context) -> bool: def too_few_string_formatting_arguments(self, context: Context) -> None: self.fail('Not enough arguments for format string', context) - + def too_many_string_formatting_arguments(self, context: Context) -> None: self.fail('Not all arguments converted during string formatting', context) @@ -717,3 +717,8 @@ def callable_name(type: Callable) -> str: return type.name else: return 'function' + + +def temp_message_builder() -> MessageBuilder: + """Return a message builder usable for collecting errors locally.""" + return MessageBuilder(Errors()) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index a977bf9066dc..ad80bc2f34fc 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -5,7 +5,9 @@ Instance, TypeVar, Callable, TupleType, UnionType, Overloaded, ErasedType, TypeList, is_named_instance ) -from mypy import sametypes +import mypy.applytype +import mypy.constraints +from mypy import messages, sametypes from mypy.nodes import TypeInfo from mypy.expandtype import expand_type from mypy.maptype import map_instance_to_supertype @@ -160,20 +162,24 @@ def visit_overloaded(self, left: Overloaded) -> bool: def is_callable_subtype(left: Callable, right: Callable) -> bool: """Is left a subtype of right?""" - # TODO support named arguments, **args etc. - - # Subtyping is not currently supported for generic functions. - if left.variables or right.variables: - return False - + # TODO: Support named arguments, **args, etc. # Non-type cannot be a subtype of type. if right.is_type_obj() and not left.is_type_obj(): return False + if right.variables: + # Subtyping is not currently supported for generic function as the supertype. + return False + if left.variables: + # Apply generic type variables away in left via type inference. + left = unify_generic_callable(left, right) + if left is None: + return False # Check return types. if not is_subtype(left.ret_type, right.ret_type): return False + # Check argument types. if len(left.arg_types) < len(right.arg_types): return False if left.min_args > right.min_args: @@ -181,10 +187,8 @@ def is_callable_subtype(left: Callable, right: Callable) -> bool: for i in range(len(right.arg_types)): if not is_subtype(right.arg_types[i], left.arg_types[i]): return False - if right.is_var_arg and not left.is_var_arg: return False - if (left.is_var_arg and not right.is_var_arg and len(left.arg_types) <= len(right.arg_types)): return False @@ -192,6 +196,27 @@ def is_callable_subtype(left: Callable, right: Callable) -> bool: return True +def unify_generic_callable(type: Callable, target: Callable) -> Callable: + """Try to unify a generic callable type with another callable type. + + Return unified Callable if successful; otherwise, return None. + """ + constraints = [] # type: List[mypy.constraints.Constraint] + for arg_type, target_arg_type in zip(type.arg_types, target.arg_types): + c = mypy.constraints.infer_constraints( + arg_type, target_arg_type, mypy.constraints.SUPERTYPE_OF) + constraints.extend(c) + type_var_ids = [tvar.id for tvar in type.variables] + inferred_vars = mypy.solve.solve_constraints(type_var_ids, constraints) + if None in inferred_vars: + return None + msg = messages.temp_message_builder() + applied = mypy.applytype.apply_generic_arguments(type, inferred_vars, msg, context=target) + if msg.is_errors() or not isinstance(applied, Callable): + return None + return cast(Callable, applied) + + def restrict_subtype_away(t: Type, s: Type) -> Type: """Return a supertype of (t intersect not s) diff --git a/mypy/test/data/check-generics.test b/mypy/test/data/check-generics.test index 0a6ada737ef9..8d618d7adfb7 100644 --- a/mypy/test/data/check-generics.test +++ b/mypy/test/data/check-generics.test @@ -785,3 +785,20 @@ class A: a = A().g(1) a = 1 a = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + + +-- Function subtyping with generic functions +-- ----------------------------------------- + + +[case testSubtypingWithGenericFunctionUsingTypevarWithValues] +from typing import typevar, Function +T = typevar('T', values=(int, str)) +def f(x: T) -> T: pass +def g1(f: Function[[str], str]) -> None: pass +g1(f) +def g2(f: Function[[int], int]) -> None: pass +g2(f) +def g3(f: Function[[object], object]) -> None: pass +g3(f) # E: Argument 1 to "g3" has incompatible type Function[["T"] -> "T"]; \ + expected Function[["object"] -> "object"] From cb88bcae1ab1198a5c9c6bd5aa7d1bb240675011 Mon Sep 17 00:00:00 2001 From: YuanchaoZhu Date: Thu, 11 Sep 2014 10:36:33 -0400 Subject: [PATCH 092/144] Refactor away overloads in 'os' stubs --- stubs/3.2/os/__init__.py | 255 +++++++-------------------------------- 1 file changed, 46 insertions(+), 209 deletions(-) diff --git a/stubs/3.2/os/__init__.py b/stubs/3.2/os/__init__.py index 4ada661d1fdc..87e96a9f7838 100644 --- a/stubs/3.2/os/__init__.py +++ b/stubs/3.2/os/__init__.py @@ -3,7 +3,7 @@ # based on http://docs.python.org/3.2/library/os.html -from typing import Undefined, Mapping, Dict, List, overload, Any, Tuple, Iterator +from typing import Undefined, Mapping, Dict, List, Any, Tuple, Iterator, overload, Union, AnyStr from builtins import OSError as error import os.path as path @@ -170,10 +170,7 @@ def getuid() -> int: pass # Unix only def getenv(key: str, default: str = None) -> str: pass def getenvb(key: bytes, default: bytes = None) -> bytes: pass # TODO mixed str/bytes putenv arguments -@overload -def putenv(key: str, value: str) -> None: pass -@overload -def putenv(key: bytes, value: bytes) -> None: pass +def putenv(key: AnyStr, value: AnyStr) -> None: pass def setegid(egid: int) -> None: pass # Unix only def seteuid(euid: int) -> None: pass # Unix only def setgid(gid: int) -> None: pass # Unix only @@ -190,10 +187,7 @@ def setuid(uid) -> None: pass # Unix only def strerror(code: int) -> str: pass def umask(mask: int) -> int: pass def uname() -> Tuple[str, str, str, str, str]: pass # Unix only -@overload -def unsetenv(key: str) -> None: pass -@overload -def unsetenv(key: bytes) -> None: pass +def unsetenv(key: AnyStr) -> None: pass # Return IO or TextIO def fdopen(fd: int, mode: str = 'r', encoding: str = None, errors: str = None, newline: str = None, closefd: bool = True) -> Any: pass @@ -212,10 +206,7 @@ def fsync(fd: int) -> None: pass def ftruncate(fd: int, length: int) -> None: pass # Unix only def isatty(fd: int) -> bool: pass # Unix only def lseek(fd: int, pos: int, how: int) -> int: pass -@overload -def open(file: str, flags: int, mode: int = 0o777) -> int: pass -@overload -def open(file: bytes, flags: int, mode: int = 0o777) -> int: pass +def open(file: AnyStr, flags: int, mode: int = 0o777) -> int: pass def openpty() -> Tuple[int, int]: pass # some flavors of Unix def pipe() -> Tuple[int, int]: pass def read(fd: int, n: int) -> bytes: pass @@ -223,148 +214,60 @@ def tcgetpgrp(fd: int) -> int: pass # Unix only def tcsetpgrp(fd: int, pg: int) -> None: pass # Unix only def ttyname(fd: int) -> str: pass # Unix only def write(fd: int, string: bytes) -> int: pass -@overload -def access(path: str, mode: int) -> bool: pass -@overload -def access(path: bytes, mode: int) -> bool: pass -@overload -def chdir(path: str) -> None: pass -@overload -def chdir(path: bytes) -> None: pass +def access(path: AnyStr, mode: int) -> bool: pass +def chdir(path: AnyStr) -> None: pass def fchdir(fd: int) -> None: pass def getcwd() -> str: pass def getcwdb() -> bytes: pass def chflags(path: str, flags: int) -> None: pass # Unix only def chroot(path: str) -> None: pass # Unix only - -@overload -def chmod(path: str, mode: int) -> None: pass -@overload -def chmod(path: bytes, mode: int) -> None: pass - -@overload -def chown(path: str, uid: int, gid: int) -> None: pass # Unix only -@overload -def chown(path: bytes, uid: int, gid: int) -> None: pass # Unix only - +def chmod(path: AnyStr, mode: int) -> None: pass +def chown(path: AnyStr, uid: int, gid: int) -> None: pass # Unix only def lchflags(path: str, flags: int) -> None: pass # Unix only def lchmod(path: str, mode: int) -> None: pass # Unix only def lchown(path: str, uid: int, gid: int) -> None: pass # Unix only - -@overload -def link(src: str, link_name: str) -> None: pass -@overload -def link(src: bytes, link_name: bytes) -> None: pass +def link(src: AnyStr, link_name: AnyStr) -> None: pass @overload def listdir(path: str = '.') -> List[str]: pass @overload def listdir(path: bytes) -> List[bytes]: pass -@overload -def lstat(path: str) -> stat_result: pass -@overload -def lstat(path: bytes) -> stat_result: pass - +def lstat(path: AnyStr) -> stat_result: pass def mkfifo(path, mode: int=0o666) -> None: pass # Unix only - -@overload -def mknod(filename: str, mode: int = 0o600, device: int = 0) -> None: pass -@overload -def mknod(filename: bytes, mode: int = 0o600, device: int = 0) -> None: pass - +def mknod(filename: AnyStr, mode: int = 0o600, device: int = 0) -> None: pass def major(device: int) -> int: pass def minor(device: int) -> int: pass def makedev(major: int, minor: int) -> int: pass - -@overload -def mkdir(path: str, mode: int = 0o777) -> None: pass -@overload -def mkdir(path: bytes, mode: int = 0o777) -> None: pass - -@overload -def makedirs(path: str, mode: int = 0o777, +def mkdir(path: AnyStr, mode: int = 0o777) -> None: pass +def makedirs(path: AnyStr, mode: int = 0o777, exist_ok: bool = False) -> None: pass -@overload -def makedirs(path: bytes, mode: int = 0o777, - exist_ok: bool = False) -> None: pass - def pathconf(path: str, name: str) -> int: pass # Unix only -@overload -def readlink(path: str) -> str: pass -@overload -def readlink(path: bytes) -> bytes: pass - -@overload -def remove(path: str) -> None: pass -@overload -def remove(path: bytes) -> None: pass - -@overload -def removedirs(path: str) -> None: pass -@overload -def removedirs(path: bytes) -> None: pass - -@overload -def rename(src: str, dst: str) -> None: pass -@overload -def rename(src: bytes, dst: bytes) -> None: pass - -@overload -def renames(old: str, new: str) -> None: pass -@overload -def renames(old: bytes, new: bytes) -> None: pass - -@overload -def rmdir(path: str) -> None: pass -@overload -def rmdir(path: bytes) -> None: pass - -@overload -def stat(path: str) -> stat_result: pass -@overload -def stat(path: bytes) -> stat_result: pass - -@overload -def stat_float_times() -> bool: pass -@overload -def stat_float_times(newvalue: bool) -> bool: pass - +def readlink(path: AnyStr) -> AnyStr: pass +def remove(path: AnyStr) -> None: pass +def removedirs(path: AnyStr) -> None: pass +def rename(src: AnyStr, dst: AnyStr) -> None: pass +def renames(old: AnyStr, new: AnyStr) -> None: pass +def rmdir(path: AnyStr) -> None: pass +def stat(path: AnyStr) -> stat_result: pass +def stat_float_times(newvalue: Union[bool, None] = None) -> bool: pass def statvfs(path: str) -> statvfs_result: pass # Unix only - -@overload -def symlink(source: str, link_name: str, +def symlink(source: AnyStr, link_name: AnyStr, target_is_directory: bool = False) -> None: pass # final argument in Windows only -@overload -def symlink(source: bytes, link_name: bytes, - target_is_directory: bool = False) -> None: - pass @overload def unlink(path: str) -> None: pass @overload def unlink(bytes: str) -> None: pass -@overload -def utime(path: str, times: Tuple[int, int] = None) -> None: pass -@overload -def utime(path: bytes, times: Tuple[int, int] = None) -> None: pass -@overload -def utime(path: str, times: Tuple[float, float] = None) -> None: pass -@overload -def utime(path: bytes, times: Tuple[float, float] = None) -> None: pass +def utime(path: AnyStr, times: Union[Tuple[int, int], Tuple[float, float]] = None) -> None: pass # TODO onerror: function from OSError to void -@overload -def walk(top: str, topdown: bool = True, onerror: Any = None, - followlinks: bool = False) -> Iterator[Tuple[str, List[str], - List[str]]]: pass -@overload -def walk(top: bytes, topdown: bool = True, onerror: Any = None, - followlinks: bool = False) -> Iterator[Tuple[bytes, List[bytes], - List[bytes]]]: pass +def walk(top: AnyStr, topdown: bool = True, onerror: Any = None, + followlinks: bool = False) -> Iterator[Tuple[AnyStr, List[AnyStr], + List[AnyStr]]]: pass # walk(): "By default errors from the os.listdir() call are ignored. If # optional arg 'onerror' is specified, it should be a function; it # will be called with one argument, an os.error instance. It can @@ -373,45 +276,16 @@ def walk(top: bytes, topdown: bool = True, onerror: Any = None, # filename attribute of the exception object." def abort() -> 'None': pass - -@overload -def execl(path: str, arg0: str, *args: str) -> None: pass -@overload -def execl(path: bytes, arg0: bytes, *args: bytes) -> None: pass -@overload -def execle(path: str, arg0: str, - *args: Any) -> None: pass # Imprecise signature -@overload -def execle(path: bytes, arg0: bytes, +def execl(path: AnyStr, arg0: AnyStr, *args: AnyStr) -> None: pass +def execle(path: AnyStr, arg0: AnyStr, *args: Any) -> None: pass # Imprecise signature -@overload -def execlp(path: str, arg0: str, *args: str) -> None: pass -@overload -def execlp(path: bytes, arg0: bytes, *args: bytes) -> None: pass -@overload -def execlpe(path: str, arg0: str, +def execlp(path: AnyStr, arg0: AnyStr, *args: AnyStr) -> None: pass +def execlpe(path: AnyStr, arg0: AnyStr, *args: Any) -> None: pass # Imprecise signature -@overload -def execlpe(path: bytes, arg0: bytes, - *args: Any) -> None: pass # Imprecise signature -@overload -def execv(path: str, args: List[str]) -> None: pass -@overload -def execv(path: bytes, args: List[bytes]) -> None: pass -@overload -def execve(path: str, args: List[str], env: Mapping[str, str]) -> None: pass -@overload -def execve(path: bytes, args: List[bytes], - env: Mapping[str, str]) -> None: pass -@overload -def execvp(file: str, args: List[str]) -> None: pass -@overload -def execvp(file: bytes, args: List[bytes]) -> None: pass -@overload -def execvpe(file: str, args: List[str], - env: Mapping[str, str]) -> None: pass -@overload -def execvpe(file: bytes, args: List[bytes], +def execv(path: AnyStr, args: List[AnyStr]) -> None: pass +def execve(path: AnyStr, args: List[AnyStr], env: Mapping[AnyStr, AnyStr]) -> None: pass +def execvp(file: AnyStr, args: List[AnyStr]) -> None: pass +def execvpe(file: AnyStr, args: List[AnyStr], env: Mapping[str, str]) -> None: pass def _exit(n: int) -> None: pass def fork() -> int: pass # Unix only @@ -428,66 +302,29 @@ def __init__(self, command: str, mode: str = 'r', bufsize: int = -1) -> None: pass def close(self) -> Any: pass # may return int -@overload -def spawnl(mode: int, path: str, arg0: str, *args: str) -> int: pass -@overload -def spawnl(mode: int, path: bytes, arg0: bytes, *args: bytes) -> int: pass -@overload -def spawnle(mode: int, path: str, arg0: str, +def spawnl(mode: int, path: AnyStr, arg0: AnyStr, *args: AnyStr) -> int: pass +def spawnle(mode: int, path: AnyStr, arg0: AnyStr, *args: Any) -> int: pass # Imprecise sig -@overload -def spawnle(mode: int, path: bytes, arg0: bytes, - *args: Any) -> int: pass # Imprecise sig -@overload -def spawnlp(mode: int, file: str, arg0: str, - *args: str) -> int: pass # Unix only TODO -@overload -def spawnlp(mode: int, file: bytes, arg0: bytes, *args: bytes) -> int: pass -@overload -def spawnlpe(mode: int, file: str, arg0: str, *args: Any) -> int: +def spawnlp(mode: int, file: AnyStr, arg0: AnyStr, + *args: AnyStr) -> int: pass # Unix only TODO +def spawnlpe(mode: int, file: AnyStr, arg0: AnyStr, *args: Any) -> int: pass # Imprecise signature; Unix only TODO -@overload -def spawnlpe(mode: int, file: bytes, arg0: bytes, *args: Any) -> int: - pass # Imprecise signature -@overload -def spawnv(mode: int, path: str, args: List[str]) -> int: pass -@overload -def spawnv(mode: int, path: bytes, args: List[bytes]) -> int: pass -@overload -def spawnve(mode: int, path: str, args: List[str], - env: Mapping[str, str]) -> int: pass -@overload -def spawnve(mode: int, path: bytes, args: List[bytes], +def spawnv(mode: int, path: AnyStr, args: List[AnyStr]) -> int: pass +def spawnve(mode: int, path: AnyStr, args: List[AnyStr], env: Mapping[str, str]) -> int: pass -@overload -def spawnvp(mode: int, file: str, args: List[str]) -> int: pass # Unix only -@overload -def spawnvp(mode: int, file: bytes, args: List[bytes]) -> int: pass -@overload -def spawnvpe(mode: int, file: str, args: List[str], +def spawnvp(mode: int, file: AnyStr, args: List[AnyStr]) -> int: pass # Unix only +def spawnvpe(mode: int, file: AnyStr, args: List[AnyStr], env: Mapping[str, str]) -> int: pass # Unix only -@overload -def spawnvpe(mode: int, file: bytes, args: List[bytes], - env: Mapping[str, str]) -> int: pass -@overload -def startfile(path: str) -> None: pass # Windows only -@overload -def startfile(path: str, operation: str) -> None: pass # Windows only +def startfile(path: str, operation: Union[str, None] = None) -> None: pass # Windows only -@overload -def system(command: str) -> int: pass -@overload -def system(command: bytes) -> int: pass +def system(command: AnyStr) -> int: pass def times() -> Tuple[float, float, float, float, float]: pass def wait() -> Tuple[int, int]: pass # Unix only def waitpid(pid: int, options: int) -> Tuple[int, int]: pass -@overload -def wait3() -> Tuple[int, int, Any]: pass # Unix only -@overload -def wait3(options: int) -> Tuple[int, int, Any]: pass # Unix only +def wait3(options: Union[int, None] = None) -> Tuple[int, int, Any]: pass # Unix only def wait4(pid: int, options: int) -> Tuple[int, int, Any]: pass # Unix only def WCOREDUMP(status: int) -> bool: pass # Unix only From 99191d5082cc0bca91043186b2fcbe489d0bc756 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 25 Oct 2014 15:02:09 -0700 Subject: [PATCH 093/144] Fix type check errors --- lib-python/3.2/test/test_posixpath.py | 2 +- lib-python/3.2/test/test_shutil.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib-python/3.2/test/test_posixpath.py b/lib-python/3.2/test/test_posixpath.py index 08e421bc45a8..b3da34fba213 100644 --- a/lib-python/3.2/test/test_posixpath.py +++ b/lib-python/3.2/test/test_posixpath.py @@ -275,7 +275,7 @@ def fake_lstat(path): setattr(os, 'lstat', fake_lstat) # mypy: can't modify os directly self.assertIs(posixpath.ismount(ABSTFN), True) finally: - os.lstat = save_lstat + setattr(os, 'lstat', save_lstat) def test_expanduser(self) -> None: self.assertEqual(posixpath.expanduser("foo"), "foo") diff --git a/lib-python/3.2/test/test_shutil.py b/lib-python/3.2/test/test_shutil.py index c65494fce452..af0935ecdcd6 100644 --- a/lib-python/3.2/test/test_shutil.py +++ b/lib-python/3.2/test/test_shutil.py @@ -54,7 +54,7 @@ def wrap(*args: Any, **kwargs: Any) -> Any: shutil.rename = Any(_fake_rename) return func(*args, **kwargs) finally: - shutil.rename = builtin_rename + shutil.rename = Any(builtin_rename) return wrap class TestShutil(unittest.TestCase): From 58d0fe9f1c7cd3c3ab966e79b33ef8c6e4cf327d Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 25 Oct 2014 15:06:33 -0700 Subject: [PATCH 094/144] Minor tweaks to os module stubs --- stubs/3.2/os/__init__.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/stubs/3.2/os/__init__.py b/stubs/3.2/os/__init__.py index 87e96a9f7838..3101915bee15 100644 --- a/stubs/3.2/os/__init__.py +++ b/stubs/3.2/os/__init__.py @@ -243,7 +243,6 @@ def mkdir(path: AnyStr, mode: int = 0o777) -> None: pass def makedirs(path: AnyStr, mode: int = 0o777, exist_ok: bool = False) -> None: pass def pathconf(path: str, name: str) -> int: pass # Unix only - def readlink(path: AnyStr) -> AnyStr: pass def remove(path: AnyStr) -> None: pass def removedirs(path: AnyStr) -> None: pass @@ -256,12 +255,7 @@ def statvfs(path: str) -> statvfs_result: pass # Unix only def symlink(source: AnyStr, link_name: AnyStr, target_is_directory: bool = False) -> None: pass # final argument in Windows only - -@overload -def unlink(path: str) -> None: pass -@overload -def unlink(bytes: str) -> None: pass - +def unlink(path: AnyStr) -> None: pass def utime(path: AnyStr, times: Union[Tuple[int, int], Tuple[float, float]] = None) -> None: pass # TODO onerror: function from OSError to void @@ -316,11 +310,8 @@ def spawnvp(mode: int, file: AnyStr, args: List[AnyStr]) -> int: pass # Unix on def spawnvpe(mode: int, file: AnyStr, args: List[AnyStr], env: Mapping[str, str]) -> int: pass # Unix only - def startfile(path: str, operation: Union[str, None] = None) -> None: pass # Windows only - def system(command: AnyStr) -> int: pass - def times() -> Tuple[float, float, float, float, float]: pass def wait() -> Tuple[int, int]: pass # Unix only def waitpid(pid: int, options: int) -> Tuple[int, int]: pass From f1d8cb90a3587b9789d262ac82392bafbc54df71 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 14:01:16 -0700 Subject: [PATCH 095/144] Add documentation readme --- docs/README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000000..55d8f6f4266e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,23 @@ +Mypy Documentation +================== + +What's this? +------------ + +This directory contains the source code for Mypy documentation (under sources/) +and build scripts. The documentation uses Sphinx and reStructuredText. + +Building the documentation +-------------------------- + +We use `sphinx_rtd_theme` as the theme. You should install it first: + +``` +$ pip install sphinx_rtd_theme +``` + +Build the documentation like this: + +``` +$ make html +``` From 1c395df5ec7e85a44cd393d02b9230f037d20b8b Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 14:02:18 -0700 Subject: [PATCH 096/144] Fix typo in readme --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 55d8f6f4266e..67e7d752d647 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,7 @@ Mypy Documentation What's this? ------------ -This directory contains the source code for Mypy documentation (under sources/) +This directory contains the source code for Mypy documentation (under `source/`) and build scripts. The documentation uses Sphinx and reStructuredText. Building the documentation From a3d7f250ebb7bee137097867c188209dd38542e9 Mon Sep 17 00:00:00 2001 From: Remigiusz Modrzejewski Date: Wed, 29 Oct 2014 22:07:46 +0000 Subject: [PATCH 097/144] Small fixes in setup.py to be able to submit to PyPI. --- setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 5bcb4986ac9b..c4b37b67dcde 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ sys.stderr.write("ERROR: You need Python 3.2 or later to use mypy.\n") exit(1) -version = '0.0.1.dev1' +version = '0.0.1' description = 'Optional static type checker for Python' long_description = ''' Mypy -- Optional Static Type Checker for Python @@ -24,15 +24,15 @@ stubs = [] -for version in ['3.4', '3.3', '3.2', '2.7']: - base = os.path.join('stubs', version) +for py_version in ['3.4', '3.3', '3.2', '2.7']: + base = os.path.join('stubs', py_version) if not os.path.exists(base): os.mkdir(base) stub_dirs = [''] + [name for name in os.listdir(base) if os.path.isdir(os.path.join(base, name))] for stub_dir in stub_dirs: - target = os.path.join('lib', 'mypy', 'stubs', version, stub_dir) + target = os.path.join('lib', 'mypy', 'stubs', py_version, stub_dir) files = glob.glob(os.path.join(base, stub_dir, '*.py')) stubs.append((target, files)) @@ -47,7 +47,7 @@ 'Topic :: Software Development', ] -setup(name='mypy', +setup(name='mypy-lang', version=version, description=description, long_description=long_description, From c4fe6217d8bf8085aa3aecc5d1b615a6f9609269 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 29 Oct 2014 22:22:44 -0700 Subject: [PATCH 098/144] Claim compatibility with Python 3.4 in setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index c4b37b67dcde..aacd85951c4c 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ 'Operating System :: POSIX', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', 'Topic :: Software Development', ] From 28c8b210b2f84ad94dfa40fd051faab3a4389949 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 10:54:41 -0800 Subject: [PATCH 099/144] Fix subtyping of functions with *args Fixes #231. --- mypy/subtypes.py | 36 +++++++++++++++++++++++++++---- mypy/test/data/check-varargs.test | 15 +++++++++++++ mypy/test/data/pythoneval.test | 8 +++++++ mypy/test/testsubtypes.py | 10 ++++++--- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index ad80bc2f34fc..f8b73ad2d391 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -180,15 +180,17 @@ def is_callable_subtype(left: Callable, right: Callable) -> bool: return False # Check argument types. - if len(left.arg_types) < len(right.arg_types): - return False if left.min_args > right.min_args: return False + if left.is_var_arg: + return is_var_arg_callable_subtype_helper(left, right) + if right.is_var_arg: + return False + if len(left.arg_types) < len(right.arg_types): + return False for i in range(len(right.arg_types)): if not is_subtype(right.arg_types[i], left.arg_types[i]): return False - if right.is_var_arg and not left.is_var_arg: - return False if (left.is_var_arg and not right.is_var_arg and len(left.arg_types) <= len(right.arg_types)): return False @@ -196,6 +198,32 @@ def is_callable_subtype(left: Callable, right: Callable) -> bool: return True +def is_var_arg_callable_subtype_helper(left: Callable, right: Callable) -> bool: + """Is left a subtype of right, assuming left has varargs? + + See also is_callable_subtype for additional assumptions we can make. + """ + left_fixed = left.max_fixed_args() + right_fixed = right.max_fixed_args() + num_fixed_matching = min(left_fixed, right_fixed) + for i in range(num_fixed_matching): + if not is_subtype(right.arg_types[i], left.arg_types[i]): + return False + if not right.is_var_arg: + for i in range(num_fixed_matching, len(right.arg_types)): + if not is_subtype(right.arg_types[i], left.arg_types[-1]): + return False + return True + else: + for i in range(left_fixed, right_fixed): + if not is_subtype(right.arg_types[i], left.arg_types[-1]): + return False + for i in range(right_fixed, left_fixed): + if not is_subtype(right.arg_types[-1], left.arg_types[i]): + return False + return is_subtype(right.arg_types[-1], left.arg_types[-1]) + + def unify_generic_callable(type: Callable, target: Callable) -> Callable: """Try to unify a generic callable type with another callable type. diff --git a/mypy/test/data/check-varargs.test b/mypy/test/data/check-varargs.test index 8b72d3e5f2f6..e2b83b6c4cdc 100644 --- a/mypy/test/data/check-varargs.test +++ b/mypy/test/data/check-varargs.test @@ -501,3 +501,18 @@ f(1, 2) f('') # E: Argument 1 to "f" has incompatible type "str"; expected "int" f(1, '') # E: Argument 2 to "f" has incompatible type "str"; expected "int" [builtins fixtures/list.py] + + +-- Subtyping +-- --------- + + +[case testVarArgsFunctionSubtyping] +from typing import Function, Undefined +x = Undefined # type: Function[[int], None] +def f(*x: int) -> None: pass +def g(*x: str) -> None: pass +x = f +x = g # E: Incompatible types in assignment (expression has type , variable has type ) +[builtins fixtures/list.py] +[out] diff --git a/mypy/test/data/pythoneval.test b/mypy/test/data/pythoneval.test index f1d7be8f92ef..f07afeba841e 100644 --- a/mypy/test/data/pythoneval.test +++ b/mypy/test/data/pythoneval.test @@ -911,3 +911,11 @@ def f(x: _T) -> None: pass [out] _program.py: In function "f": _program.py, line 2: Name '_T' is not defined + +[case testVarArgsFunctionSubtyping] +import typing +def f(*args: str) -> str: return args[0] +map(f, ['x']) +map(f, [1]) +[out] +_program.py, line 4: Argument 1 to "map" has incompatible type Function[["str"] -> "str"]; expected Function[["int"] -> "str"] diff --git a/mypy/test/testsubtypes.py b/mypy/test/testsubtypes.py index 8464fddfb299..42ab4a7c4b0e 100644 --- a/mypy/test/testsubtypes.py +++ b/mypy/test/testsubtypes.py @@ -87,23 +87,27 @@ def test_default_arg_callable_subtyping(self): self.fx.callable_default(1, self.fx.a, self.fx.a), self.fx.callable(self.fx.a, self.fx.a, self.fx.a)) - def test_var_arg_callable_subtyping(self): + def test_var_arg_callable_subtyping_1(self): self.assert_proper_subtype( self.fx.callable_var_arg(0, self.fx.a, self.fx.a), self.fx.callable_var_arg(0, self.fx.b, self.fx.a)) - self.assert_unrelated( + def test_var_arg_callable_subtyping_2(self): + self.assert_proper_subtype( self.fx.callable_var_arg(0, self.fx.a, self.fx.a), - self.fx.callable(self.fx.a, self.fx.a)) + self.fx.callable(self.fx.b, self.fx.a)) + def test_var_arg_callable_subtyping_3(self): self.assert_proper_subtype( self.fx.callable_var_arg(0, self.fx.a, self.fx.a), self.fx.callable(self.fx.a)) + def test_var_arg_callable_subtyping_4(self): self.assert_proper_subtype( self.fx.callable_var_arg(1, self.fx.a, self.fx.d, self.fx.a), self.fx.callable(self.fx.a, self.fx.a)) + def test_var_arg_callable_subtyping_5(self): self.assert_proper_subtype( self.fx.callable_var_arg(0, self.fx.a, self.fx.d, self.fx.a), self.fx.callable(self.fx.a, self.fx.a)) From 481a837d962234abc35ea2cbc2292185ddfc1d7e Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 10:59:19 -0800 Subject: [PATCH 100/144] Update test cases --- mypy/test/testsubtypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/test/testsubtypes.py b/mypy/test/testsubtypes.py index 42ab4a7c4b0e..68e433c8628f 100644 --- a/mypy/test/testsubtypes.py +++ b/mypy/test/testsubtypes.py @@ -105,12 +105,12 @@ def test_var_arg_callable_subtyping_3(self): def test_var_arg_callable_subtyping_4(self): self.assert_proper_subtype( self.fx.callable_var_arg(1, self.fx.a, self.fx.d, self.fx.a), - self.fx.callable(self.fx.a, self.fx.a)) + self.fx.callable(self.fx.b, self.fx.a)) def test_var_arg_callable_subtyping_5(self): self.assert_proper_subtype( self.fx.callable_var_arg(0, self.fx.a, self.fx.d, self.fx.a), - self.fx.callable(self.fx.a, self.fx.a)) + self.fx.callable(self.fx.b, self.fx.a)) def test_type_callable_subtyping(self): self.assert_proper_subtype( From f17463c99290db25610f81c2409b4563d53508b8 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 11:04:40 -0800 Subject: [PATCH 101/144] Add test cases --- mypy/test/testsubtypes.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mypy/test/testsubtypes.py b/mypy/test/testsubtypes.py index 68e433c8628f..1cb5a4abf92a 100644 --- a/mypy/test/testsubtypes.py +++ b/mypy/test/testsubtypes.py @@ -112,6 +112,16 @@ def test_var_arg_callable_subtyping_5(self): self.fx.callable_var_arg(0, self.fx.a, self.fx.d, self.fx.a), self.fx.callable(self.fx.b, self.fx.a)) + def test_var_arg_callable_subtyping_6(self): + self.assert_proper_subtype( + self.fx.callable_var_arg(0, self.fx.a, self.fx.f, self.fx.d), + self.fx.callable_var_arg(0, self.fx.b, self.fx.e, self.fx.d)) + + def test_var_arg_callable_subtyping_7(self): + self.assert_not_subtype( + self.fx.callable_var_arg(0, self.fx.b, self.fx.d), + self.fx.callable(self.fx.a, self.fx.d)) + def test_type_callable_subtyping(self): self.assert_proper_subtype( self.fx.callable_type(self.fx.d, self.fx.a), self.fx.type_type) From 73951d28571b6c329746b463b7abb4a1e6e37426 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 11:16:39 -0800 Subject: [PATCH 102/144] Add test cases --- mypy/test/testsubtypes.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mypy/test/testsubtypes.py b/mypy/test/testsubtypes.py index 1cb5a4abf92a..d9cd7e9c4b47 100644 --- a/mypy/test/testsubtypes.py +++ b/mypy/test/testsubtypes.py @@ -122,6 +122,14 @@ def test_var_arg_callable_subtyping_7(self): self.fx.callable_var_arg(0, self.fx.b, self.fx.d), self.fx.callable(self.fx.a, self.fx.d)) + def test_var_arg_callable_subtyping_8(self): + self.assert_not_subtype( + self.fx.callable_var_arg(0, self.fx.b, self.fx.d), + self.fx.callable_var_arg(0, self.fx.a, self.fx.a, self.fx.d)) + self.assert_subtype( + self.fx.callable_var_arg(0, self.fx.a, self.fx.d), + self.fx.callable_var_arg(0, self.fx.b, self.fx.b, self.fx.d)) + def test_type_callable_subtyping(self): self.assert_proper_subtype( self.fx.callable_type(self.fx.d, self.fx.a), self.fx.type_type) From be37c55540bd97a62ac62df0e914b232b64f6350 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 11:17:59 -0800 Subject: [PATCH 103/144] Add test case --- mypy/test/testsubtypes.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mypy/test/testsubtypes.py b/mypy/test/testsubtypes.py index d9cd7e9c4b47..cffedfb77931 100644 --- a/mypy/test/testsubtypes.py +++ b/mypy/test/testsubtypes.py @@ -130,6 +130,14 @@ def test_var_arg_callable_subtyping_8(self): self.fx.callable_var_arg(0, self.fx.a, self.fx.d), self.fx.callable_var_arg(0, self.fx.b, self.fx.b, self.fx.d)) + def test_var_arg_callable_subtyping_9(self): + self.assert_not_subtype( + self.fx.callable_var_arg(0, self.fx.b, self.fx.b, self.fx.d), + self.fx.callable_var_arg(0, self.fx.a, self.fx.d)) + self.assert_subtype( + self.fx.callable_var_arg(0, self.fx.a, self.fx.a, self.fx.d), + self.fx.callable_var_arg(0, self.fx.b, self.fx.d)) + def test_type_callable_subtyping(self): self.assert_proper_subtype( self.fx.callable_type(self.fx.d, self.fx.a), self.fx.type_type) From c4aba59543dd067cccc419f7cb5c6a533097e7b9 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 11:20:27 -0800 Subject: [PATCH 104/144] Simplify code a bit --- mypy/subtypes.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index f8b73ad2d391..b72c4d0058e9 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -191,10 +191,6 @@ def is_callable_subtype(left: Callable, right: Callable) -> bool: for i in range(len(right.arg_types)): if not is_subtype(right.arg_types[i], left.arg_types[i]): return False - if (left.is_var_arg and not right.is_var_arg and - len(left.arg_types) <= len(right.arg_types)): - return False - return True From 9756eb03c9b5efbfb4e29ff97f88af6a83e1479a Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 11:20:40 -0800 Subject: [PATCH 105/144] Minor docstring tweak --- mypy/subtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index b72c4d0058e9..b94c3ffea732 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -195,7 +195,7 @@ def is_callable_subtype(left: Callable, right: Callable) -> bool: def is_var_arg_callable_subtype_helper(left: Callable, right: Callable) -> bool: - """Is left a subtype of right, assuming left has varargs? + """Is left a subtype of right, assuming left has *args? See also is_callable_subtype for additional assumptions we can make. """ From 3bbba8c6b86de2904ed174e658689ad67e94996b Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 11:36:08 -0800 Subject: [PATCH 106/144] Add test cases for issue #288 These pass so it seems that we can close #288. Apparently the underlying issue was fixed by commit beff15fe01593487275b9f10a6cb71af3fea0894. --- mypy/test/data/check-generics.test | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/mypy/test/data/check-generics.test b/mypy/test/data/check-generics.test index 8d618d7adfb7..67c0bddfa01e 100644 --- a/mypy/test/data/check-generics.test +++ b/mypy/test/data/check-generics.test @@ -802,3 +802,29 @@ g2(f) def g3(f: Function[[object], object]) -> None: pass g3(f) # E: Argument 1 to "g3" has incompatible type Function[["T"] -> "T"]; \ expected Function[["object"] -> "object"] + + +-- Special cases +-- ------------- + + +[case testIdentityHigherOrderFunction] +from typing import Function, typevar +A = typevar('A') +B = typevar('B') +def square(n: int) -> int: + return n +def id(f: Function[[A], B]) -> Function[[A], B]: + return f +g = id(square) +g(1) +g('x') # E: Argument 1 has incompatible type "str"; expected "int" + + +[case testIdentityHigherOrderFunction2] +from typing import Function, typevar +A = typevar('A') +def voidify(n: int) -> None: pass +def identity(f: Function[[A], None]) -> Function[[A], None]: + return f +identity(voidify)(3) From a68742b68dac214b87fba2a6a37668498ca9df47 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 14:50:51 -0700 Subject: [PATCH 107/144] Split documentation into multiple subpages --- docs/source/additional_features.rst | 8 + docs/source/basics.rst | 91 +++++++++++ docs/source/builtin_types.rst | 24 +++ docs/source/casts.rst | 25 +++ docs/source/class_basics.rst | 148 ++++++++++++++++++ docs/source/common_issues.rst | 79 ++++++++++ docs/source/dynamic_typing.rst | 29 ++++ docs/source/function_overloading.rst | 33 ++++ docs/source/generics.rst | 111 +++++++++++++ docs/source/index.rst | 17 +- docs/source/kinds_of_types.rst | 108 +++++++++++++ docs/source/planned_features.rst | 76 +++++++++ docs/source/revision_history.rst | 14 ++ docs/source/supported_python_features.rst | 12 ++ .../source/type_inference_and_annotations.rst | 107 +++++++++++++ 15 files changed, 880 insertions(+), 2 deletions(-) create mode 100644 docs/source/additional_features.rst create mode 100644 docs/source/basics.rst create mode 100644 docs/source/builtin_types.rst create mode 100644 docs/source/casts.rst create mode 100644 docs/source/class_basics.rst create mode 100644 docs/source/common_issues.rst create mode 100644 docs/source/dynamic_typing.rst create mode 100644 docs/source/function_overloading.rst create mode 100644 docs/source/generics.rst create mode 100644 docs/source/kinds_of_types.rst create mode 100644 docs/source/planned_features.rst create mode 100644 docs/source/revision_history.rst create mode 100644 docs/source/supported_python_features.rst create mode 100644 docs/source/type_inference_and_annotations.rst diff --git a/docs/source/additional_features.rst b/docs/source/additional_features.rst new file mode 100644 index 000000000000..19b22ec74d37 --- /dev/null +++ b/docs/source/additional_features.rst @@ -0,0 +1,8 @@ +Additional features +------------------- + +Several mypy features are not currently covered by this tutorial, including the following: + +- inheritance between generic classes +- compatibility and subtyping of generic types, including covariance of generic types +- super() diff --git a/docs/source/basics.rst b/docs/source/basics.rst new file mode 100644 index 000000000000..0c9e94318884 --- /dev/null +++ b/docs/source/basics.rst @@ -0,0 +1,91 @@ +Basics +====== + +Function Signatures +******************* + +A function without a type signature is dynamically typed. You can declare the signature of a function using the Python 3 annotation syntax This makes the function statically typed (the type checker reports type errors within the function): + +.. code-block:: python + + # Dynamically typed (identical to Python) + + def greeting(name): + return 'Hello, {}'.format(name) + +.. code-block:: python + + # Statically typed (still valid Python) + + def greeting(name: str) -> str: + return 'Hello, {}'.format(name) + +A None return type indicates a function that does not explicitly return a value. Using a None result in a statically typed context results in a type check error: + +.. code-block:: python + + def p() -> None: + print('hello') + + a = p() # Type check error: p has None return value + +The typing module +***************** + +We cheated a bit in the above examples: a module is type checked only if it imports the module typing. Here is a complete statically typed example from the previous section: + +.. code-block:: python + + import typing + + def greeting(name: str) -> str: + return 'Hello, {}'.format(name) + +The typing module contains many definitions that are useful in statically typed code. You can also use from ... import to import them (we'll explain Iterable later in this document): + +.. code-block:: python + + from typing import Iterable + + def greet_all(names: Iterable[str]) -> None: + for name in names: + print('Hello, {}'.format(name)) + +For brevity, we often omit the typing import in code examples, but you should always include it in modules that contain statically typed code. + +You can still have dynamically typed functions in modules that import typing: + +.. code-block:: python + + import typing + + def f(): + 1 + 'x' # No static type error (dynamically typed) + + def g() -> None: + 1 + 'x' # Type check error (statically typed) + +Mixing dynamic and static typing within a single file is often useful. For example, if you are migrating existing Python code to static typing, it may be easiest to do this incrementally, such as by migrating a few functions at a time. Also, when prototyping a new feature, you may decide to first implement the relevant code using dynamic typing and only add type signatures later, when the code is more stable. + +.. note:: + + Currently the type checker checks the top levels and annotated functions of all modules, even those that don't import typing. However, you should not rely on this, as this will change in the future. + +Type checking and running programs +********************************** + +You can type check a program by using the mypy tool, which is basically a linter — it checks you program for errors without actually running it:: + + $ mypy program.py + +You can always run a mypy program as a Python program, without type checking, even it it has type errors:: + + $ python3 program.py + +All errors reported by mypy are essentially warnings that you are free to ignore, if you so wish. + +The `README `_ explains how to download and install mypy. + +.. note:: + + Depending on how mypy is configured, you may have to explicitly use the Python interpreter to run mypy. The mypy tool is an ordinary mypy (and so also Python) program. diff --git a/docs/source/builtin_types.rst b/docs/source/builtin_types.rst new file mode 100644 index 000000000000..792e1996920c --- /dev/null +++ b/docs/source/builtin_types.rst @@ -0,0 +1,24 @@ +Built-in types +============== + +These are examples of some of the most common built-in types: + +.. code-block:: python + + int # integer objects of arbitrary size + float # floating point number + bool # boolean value + str # unicode string + bytes # 8-bit string + object # the common base class + List[str] # list of str objects + Dict[str, int] # dictionary from str to int + Iterable[int] # iterable object containing ints + Sequence[bool] # sequence of booleans + Any # dynamically typed value + +The type Any and type constructors List, Dict, Iterable and Sequence are defined in the typing module. + +The type Dict is a *generic* class, signified by type arguments within [...]. For example, Dict[int, str] is a dictionary from integers to strings and and Dict[Any, Any] is a dictionary of dynamically typed (arbitrary) values and keys. List is another generic class. Dict and List are aliases for the built-ins dict and list, respectively. + +Iterable and Sequence are generic abstract base classes that correspond to Python protocols. For example, a str object is valid when Iterable[str] or Sequence[str] is expected. Note that even though they are similar to abstract base classes defined in abc.collections (formerly collections), they are not identical, since the built-in collection type objects do not support indexing. diff --git a/docs/source/casts.rst b/docs/source/casts.rst new file mode 100644 index 000000000000..8f4c0a2477d3 --- /dev/null +++ b/docs/source/casts.rst @@ -0,0 +1,25 @@ +Casts +===== + +Mypy supports type casts that are usually used to coerce a statically typed value to a subtype. Unlike languages such as Java or C#, however, mypy casts are only used as hints for the type checker when using Python semantics, and they have no runtime effect. Use the function cast to perform a cast: + +.. code-block:: python + + from typing import cast + + o = [1] # type: object + x = cast(List[int], o) # OK + y = cast(List[str], o) # OK (cast performs no actual runtime check) + +Supporting runtime checking of casts such as the above when using Python semantics would require emulating reified generics and this would be difficult to do and would likely degrade performance and make code more difficult to read. You should not rely in your programs on casts being checked at runtime. Use an assertion if you want to perform an actual runtime check. Casts are used to silence spurious type checker warnings. + +You don't need a cast for expressions with type Any, of when assigning to a variable with type Any, as was explained earlier. + +You can cast to a dynamically typed value by just calling Any: + +.. code-block:: python + + from typing import Any + + def f(x: object) -> None: + Any(x).foo() # OK diff --git a/docs/source/class_basics.rst b/docs/source/class_basics.rst new file mode 100644 index 000000000000..f5fab4ba1f94 --- /dev/null +++ b/docs/source/class_basics.rst @@ -0,0 +1,148 @@ +Class basics +============ + +Instance and class attributes +***************************** + +Mypy type checker detects if you are trying to access a missing attribute, which is a very common programming error. For this to work correctly, instance and class attributes must be defined or initialized within the class. Mypy infers the types of attributes: + +.. code-block:: python + + class A: + def __init__(self, x: int) -> None: + self.x = x # Attribute x of type int + + a = A(1) + a.x = 2 # OK + a.y = 3 # Error: A has no attribute y + +This is a bit like each class having an implicitly defined __slots__ attribute. In Python semantics this is only enforced during type checking: at runtime we use standard Python semantics. You can selectively define a class as *dynamic*; dynamic classes have Python-like compile-time semantics, and they allow you to assign to arbitrary attributes anywhere in a program without the type checker complaining: + +.. code-block:: python + + from typing import Dynamic + + class A(Dynamic): + pass + + a = A() + a.x = 2 # OK, no need to define x explicitly. + +Mypy also lets you read arbitrary attributes of dynamic class instances. This limits type checking effectiveness, so you should only use dynamic classes when you really need them. + +.. note:: + + Dynamic classes are not implemented in the current mypy version. + +You can declare variables in the class body explicitly using Undefined or a type comment: + +.. code-block:: python + + class A: + x = Undefined(List[int]) # Declare attribute y of type List[int] + y = 0 # type: Any # Declare attribute x of type Any + + a = A() + a.x = [1] # OK + +As in Python, a variable defined in the class body can used as a class or an instance variable. + +Similarly, you can give explicit types to instance variables defined in a method: + +.. code-block:: python + + class A: + def __init__(self) -> None: + self.x = Undefined(List[int]) # OK + + def f(self) -> None: + self.y = 0 # type: Any # OK + +You can only define an instance variable within a method if you assign to it explicitly using self: + +.. code-block:: python + + class A: + def __init__(self) -> None: + self.y = 1 # Define y + a = self + a.x = 1 # Error: x not defined + +Overriding statically typed methods +*********************************** + +When overriding a statically typed method, mypy checks that the override has a compatible signature: + +.. code-block:: python + + class A: + def f(self, x: int) -> None: + ... + + class B(A): + def f(self, x: str) -> None: # Error: type of x incompatible + ... + + class C(A): + def f(self, x: int, y: int) -> None: # Error: too many arguments + ... + + class D(A): + def f(self, x: int) -> None: # OK + ... + +.. note:: + + You can also vary return types **covariantly** in overriding. For example, you could override the return type 'object' with a subtype such as 'int'. + +You can also override a statically typed method with a dynamically typed one. This allows dynamically typed code to override methods defined in library classes without worrying about their type signatures, similar to Python. + +There is no runtime enforcement that the method override returns a value that is compatible with the original return type, since types are erased in the Python semantics: + +.. code-block:: python + + class A: + def inc(self, x: int) -> int: + return x + 1 + + class B(A): + def inc(self, x): # Override, dynamically typed + return 'hello' + + b = B() + print(b.inc(1)) # hello + a = b # type: A + print(a.inc(1)) # hello + +Abstract base classes and multiple inheritance +********************************************** + +Mypy uses Python abstract base classes for protocol types. There are several built-in abstract base classes types (for example, Sequence, Iterable and Iterator). You can define abstract base classes using the abc.ABCMeta metaclass and the abc.abstractmethod function decorator. + +.. code-block:: python + + from abc import ABCMeta, abstractmethod + import typing + + class A(metaclass=ABCMeta): + @abstractmethod + def foo(self, x: int) -> None: pass + + @abstractmethod + def bar(self) -> str: pass + + class B(A): + def foo(self, x: int) -> None: ... + def bar(self -> str: + return 'x' + + a = A() # Error: A is abstract + b = B() # OK + +Unlike most Python code, abstract base classes are likely to play a significant role in many complex mypy programs. + +A class can inherit any number of classes, both abstract and concrete. As with normal overrides, a dynamically typed method can implement a statically typed abstract method defined in an abstract base class. + +.. note:: + + There are also plans to support more Python-style "duck typing" in the type system. The details are still open. diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst new file mode 100644 index 000000000000..2195d81809e7 --- /dev/null +++ b/docs/source/common_issues.rst @@ -0,0 +1,79 @@ +Common issues +============= + +Statically typed function bodies are often identical to normal Python code, but sometimes you need to do things slightly differently. This section introduces some of the most common cases which require different conventions in statically typed code. + +First, you need to specify the type when creating an empty list or dict and when you assign to a new variable, as mentioned earlier: + +.. code-block:: python + + a = List[int]() # Explicit type required in statically typed code + a = [] # Fine in a dynamically typed function, or if type + # of a has been declared or inferred before + +Sometimes you can avoid the explicit list item type by using a list comprehension. Here a type annotation is needed: + +.. code-block:: python + + l = List[int]() + for i in range(n): + l.append(i * i) + +.. note:: + + A future mypy version may be able to deal with cases such as the above without type annotations. + +No type annotation needed if using a list comprehension: + +.. code-block:: python + + l = [i * i for i in range(n)] + +However, in more complex cases the explicit type annotation can improve the clarity of your code, whereas a complex list comprehension can make your code difficult to understand. + +Second, each name within a function only has a single type. You can reuse for loop indices etc., but if you want to use a variable with multiple types within a single function, you may need to declare it with the Any type. + +.. code-block:: python + + def f() -> None: + n = 1 + ... + n = x # Type error: n has type int + +.. note:: + + This is another limitation that could be lifted in a future mypy version. + +Third, sometimes the inferred type is a subtype of the desired type. The type inference uses the first assignment to infer the type of a name: + +.. code-block:: python + + # Assume Shape is the base class of both Circle and Triangle. + shape = Circle() # Infer shape to be Circle + ... + shape = Triangle() # Type error: Triangle is not a Circle + +You can just give an explicit type for the variable in cases such the above example: + +.. code-block:: python + + shape = Circle() # type: Shape # The variable s can be any Shape, + # not just Circle + ... + shape = Triangle() # OK + +Fourth, if you use isinstance tests or other kinds of runtime type tests, you may have to add casts (this is similar to instanceof tests in Java): + +.. code-block:: python + + def f(o: object) -> None: + if isinstance(o, int): + n = cast(int, o) + n += 1 # o += 1 would be an error + ... + +Note that the object type used in the above example is similar to Object in Java: it only supports operations defined for all objects, such as equality and isinstance(). The type Any, in contrast, supports all operations, even if they may fail at runtime. The cast above would have been unnecessary if the type of o was Any. + +Some consider casual use of isinstance tests a sign of bad programming style. Often a method override or an overloaded function is a cleaner way of implementing functionality that depends on the runtime types of values. However, use whatever techniques that work for you. Sometimes isinstance tests *are* the cleanest way of implementing a piece of functionality. + +Type inference in mypy is designed to work well in common cases, to be predictable and to let the type checker give useful error messages. More powerful type inference strategies often have complex and difficult-to-prefict failure modes and could result in very confusing error messages. diff --git a/docs/source/dynamic_typing.rst b/docs/source/dynamic_typing.rst new file mode 100644 index 000000000000..e047da365c29 --- /dev/null +++ b/docs/source/dynamic_typing.rst @@ -0,0 +1,29 @@ +Dynamically typed code +====================== + +As mentioned earlier, bodies of functions that don't have have an explicit return type are dynamically typed (operations are checked at runtime). Code outside functions is statically typed by default, and types of variables are inferred. This does usually the right thing, but you can also make any variable dynamically typed by defining it explicitly with the type Any: + +.. code-block:: python + + from typing import Any + + s = 1 # Statically typed (type int) + d = 1 # type: Any # Dynamically typed (type Any) + s = 'x' # Type check error + d = 'x' # OK + +Alternatively, you can use the Undefined construct to define dynamically typed variables, as Any can be used anywhere any other type is valid: + +.. code-block:: python + + from typing import Undefined, Any + + d = Undefined(Any) + d = 1 # OK + d = 'x' # OK + +Additionally, if you don't import the typing module in a file, all code outside functions will be dynamically typed by default, and the file is not type checked at all. This mode makes it easy to include existing Python code that is not trivially compatible with static typing. + +.. note:: + + The current mypy version type checks all modules, even those that don't import typing. This will change in a future version. diff --git a/docs/source/function_overloading.rst b/docs/source/function_overloading.rst new file mode 100644 index 000000000000..8e6ac8b18154 --- /dev/null +++ b/docs/source/function_overloading.rst @@ -0,0 +1,33 @@ +Function overloading +==================== + +You can define multiple instances of a function with the same name but different signatures. The first matching signature is selected at runtime when evaluating each individual call. This enables also a form of multiple dispatch. + +.. code-block:: python + + from typing import overload + + @overload + def abs(n: int) -> int: + return n if n >= 0 else -n + + @overload + def abs(n: float) -> float: + return n if n >= 0.0 else -n + + abs(-2) # 2 (int) + abs(-1.5) # 1.5 (float) + +Overloaded function variants still define a single runtime object; the following code is valid: + +.. code-block:: python + + my_abs = abs + my_abs(-2) # 2 (int) + my_abs(-1.5) # 1.5 (float) + +The overload variants must be adjacent in the code. This makes code clearer, and otherwise there would be awkward corner cases such as partially defined overloaded functions that could surprise the unwary programmer. + +.. note:: + + As generic type variables are erased at runtime, an overloaded function cannot dispatch based on a generic type argument, e.g. List[int] versus List[str]. diff --git a/docs/source/generics.rst b/docs/source/generics.rst new file mode 100644 index 000000000000..0f216137b375 --- /dev/null +++ b/docs/source/generics.rst @@ -0,0 +1,111 @@ +Generics +======== + +Defining generic classes +************************ + +The built-in collection classes are generic classes. Generic types have one or more type parameters, which can be arbitrary types. For example, Dict]int, str] has the type parameters int and str, and List[int] has a type parameter int. + +Programs can also define new generic classes. Here is a very simple generic class that represents a stack: + +.. code-block:: python + + from typing import typevar, Generic + + T = typevar('T') + + class Stack(Generic[T]): + def __init__(self) -> None: + self.items = List[T]() # Create an empty list with items of type T + + def push(self, item: T) -> None: + self.items.append(item) + + def pop(self) -> T: + return self.items.pop() + + def empty(self) -> bool: + return not self.items + +The Stack class can be used to represent a stack of any type: Stack[int], Stack[Tuple[int, str]], etc. + +Using Stack is similar to built-in container types: + +.. code-block:: python + + stack = Stack[int]() # Construct an empty Stack[int] instance + stack.push(2) + stack.pop() + stack.push('x') # Type error + +Type inference works for user-defined generic types as well: + +.. code-block:: python + + def process(stack: Stack[int]) -> None: ... + + process(Stack()) # Argument has inferred type Stack[int] + +Generic class internals +*********************** + +You may wonder what happens at runtime when you index Stack. Actually, indexing Stack just returns Stack: + +>>> print(Stack) + +>>> print(Stack[int]) + + +Note that built-in types list, dict and so on do not support indexing in Python. This is why we have the aliases List, Dict and so on in the typing module. Indexing these aliases just gives you the target class in Python, similar to Stack: + +>>> from typing import List +>>> List[int] + + +The above examples illustrate that type variables are erased at runtime when running in a Python VM. Generic Stack or list instances are just ordinary Python objects, and they have no extra runtime overhead or magic due to being generic, other than a metaclass that overloads the indexing operator. If you worry about the overhead introduced by the type indexing operation when constructing instances, you can often rewrite such code using a # type annotation, which has no runtime impact: + +.. code-block:: python + + x = List[int]() + x = [] # type: List[int] # Like the above but faster. + +The savings are rarely significant, but it could make a difference in a performance-critical loop or function. Function annotations, on the other hand, are only evaluated during the defintion of the function, not during every call. Constructing type objects in function signatures rarely has any noticeable performance impact. + +Generic functions +***************** + +Generic type variables can also be used to define generic functions: + +.. code-block:: python + + from typing import typevar, Sequence + + T = typevar('T') # Declare type variable + + def first(seq: Sequence[T]) -> T: # Generic function + return seq[0] + +As with generic classes, the type variable can be replaced with any type. That means first can we used with any sequence type, and the return type is derived from the sequence item type. For example: + +.. code-block:: python + + # Assume first defined as above. + + s = first('foo') # s has type str. + n = first([1, 2, 3]) # n has type int. + +Note also that a single definition of a type variable (such as T above) can be used in multiple generic functions or classes. In this example we use the same type variable in two generic functions: + +.. code-block:: python + + from typing typevar, Sequence + + T = typevar('T') # Declare type variable + + def first(seq: Sequence[T]) -> T: + return seq[0] + + def last(seq: Sequence[T]) -> T: + return seq[-1] + +You can also define generic methods — just use a type variable in the method signature that is different from class type variables. diff --git a/docs/source/index.rst b/docs/source/index.rst index 50ee205a4af1..5dba09b71725 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -8,13 +8,26 @@ Welcome to Mypy's documentation! .. toctree:: :maxdepth: 2 - + tutorial + basics + builtin_types + type_inference_and_annotations + kinds_of_types + class_basics + dynamic_typing + function_overloading + casts + common_issues + generics + supported_python_features + additional_features + planned_features faq + revision_history Indices and tables ================== * :ref:`genindex` * :ref:`search` - diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst new file mode 100644 index 000000000000..d89fef29fdd6 --- /dev/null +++ b/docs/source/kinds_of_types.rst @@ -0,0 +1,108 @@ +Kinds of types +============== + +User-defined types +****************** + +Each class is also a type. Any instance of a subclass is also compatible with all superclasses. All values are compatible with the object type (and also the Any type). + +.. code-block:: python + + class A: + def f(self) -> int: # Type of self inferred (A) + return 2 + + class B(A): + def f(self) -> int: + return 3 + def g(self) -> int: + return 4 + + a = B() # type: A # OK (explicit type for a; override type inference) + print(a.f()) # 3 + a.g() # Type check error: A has no method g + +The Any type +************ + +A value with the Any type is dynamically typed. Any operations are permitted on the value, and the operations are checked at runtime, similar to normal Python code. If you do not define a function return value or argument types, these default to Any. Also, a function without an explicit return type is dynamically typed. The body of a dynamically typed function is not checked statically. + +Any is compatible with every other type, and vice versa. No implicit type check is inserted when assigning a value of type Any to a variable with a more precise type: + +.. code-block:: python + + a, s = Undefined(Any), Undefined(str) + a = 2 # OK + s = a # OK + +Declared (and inferred) types are erased at runtime (they are basically treated as comments), and thus the above code does not generate a runtime error. + +Tuple types +*********** + +The type Tuple[t, ...] represents a tuple with the item types t, ...: + +.. code-block:: python + + def f(t: Tuple[int, str]) -> None: + t = 1, 'foo' # OK + t = 'foo', 1 # Type check error + +Class name forward references +***************************** + +Python does not allow references to a class object before the class is defined. Thus this code is does not work as expected: + +.. code-block:: python + + def f(x: A) -> None: # Error: Name A not defined + .... + + class A: + ... + +In cases like these you can enter the type as a string literal — this is a *forward reference*: + +.. code-block:: python + + def f(x: 'A') -> None: # OK + ... + + class A: + ... + +Of course, instead of using a string literal type, you could move the function definition after the class definition. This is not always desirable or even possible, though. + +Any type can be entered as a string literal, and youn can combine string-literal types with non-string-literal types freely: + +.. code-block:: python + + a = Undefined(List['A']) # OK + n = Undefined('int') # OK, though not useful + + class A: pass + +String literal types are never needed in # type comments. + +Callable types and lambdas +************************** + +You can pass around function objects and bound methods in statically typed code. The type of a function that accepts arguments A1, ..., An and returns Rt is Function[[A1, ..., An], Rt]. Example: + +.. code-block:: python + + def twice(i: int, next: Function[[int], int]) -> int: + return next(next(i)) + + def add(i: int) -> int: + return i + 1 + + print(twice(3, add)) # 5 + +Lambdas are also supported. The lambda argument and return value types cannot be given explicitly; they are always inferred based on context using bidirectional type inference: + +.. code-block:: python + + l = map(lambda x: x + 1, [1, 2, 3]) # infer x as int and l as List[int] + +If you want to give the argument or return value types explicitly, use an ordinary, perhaps nested function definition. diff --git a/docs/source/planned_features.rst b/docs/source/planned_features.rst new file mode 100644 index 000000000000..bef22cfc35a9 --- /dev/null +++ b/docs/source/planned_features.rst @@ -0,0 +1,76 @@ +Planned features +================ + +This section introduces some language features that are still work in progress. + +None +---- + +Currently, None is a valid value for each type, similar to null or NULL in many languages. However, it is likely that this decision will be reversed, and types do not include None default. The Optional type modifier can be used to define a type variant that includes None, such as Optional(int): + +.. code-block:: python + + def f() -> Optional[int]: + return None # OK + + def g() -> int: + ... + return None # Error: None not compatible with int + +Also, most operations would not be supported on None values: + +.. code-block:: python + + def f(x: Optional[int]) -> int: + return x + 1 # Error: Cannot add None and int + +Instead, an explicit None check would be required. This would benefit from more powerful type inference: + +.. code-block:: python + + def f(x: Optional[int]) -> int: + if x is None: + return 0 + else: + # The inferred type of x is just int here. + return x + 1 + +We would infer the type of x to be int in the else block due to the check against None in the if condition. + +Union types +----------- + +Python functions often accept values of two or more different types. You can use overloading to model this in statically typed code, but union types can make code like this easier to write. + +Use the Union[...] type constructor to construct a union type. For example, the type Union[int, str] is compatible with both integers and strings. You can use an isinstance check to narrow down the type to a specific type: + +.. code-block:: python + + from typing import Union + + def f(x: Union[int, str]) -> None: + x + 1 # Error: str + int is not valid + if isinstance(x, int): + # Here type of x is int. + x + 1 # OK + else: + # Here type of x is str. + x + 'a' # OK + + f(1) # OK + f('x') # OK + f(1.1) # Error + +More general type inference +--------------------------- + +It may be useful to support type inference also for variables defined in multiple locations in an if/else statement, even if the initializer types are different: + +.. code-block:: python + + if x: + y = None # First definition of y + else: + y = 'a' # Second definition of y + +In the above example, both of the assignments would be used in type inference, and the type of y would be str. However, it is not obvious whether this would be generally desirable in more complex cases. diff --git a/docs/source/revision_history.rst b/docs/source/revision_history.rst new file mode 100644 index 000000000000..7c71fb655f95 --- /dev/null +++ b/docs/source/revision_history.rst @@ -0,0 +1,14 @@ +Revision history +================ + +List of major changes to this document: + +- Sep 15 2014: Migrated docs to Sphinx + +- Aug 25 2014: Don't discuss native semantics. There is only Python semantics. + +- Jul 2 2013: Rewrite to use new syntax. Shift focus to discussing Python semantics. Add more content, including short discussions of `generic functions `_ and `union types `_. + +- Dec 20 2012: Add new sections on explicit types for collections, declaring multiple variables, callable types, casts, generic classes and translation to Python. Add notes about writing statically typed code and links to the wiki. Also add a table of contents. Various other, more minor updates. + +- Dec 2 2012: Use new syntax for `list types `_ and `interfaces `_. Discuss `runtime redefinition of methods and functions `. Also did minor restructuring. diff --git a/docs/source/supported_python_features.rst b/docs/source/supported_python_features.rst new file mode 100644 index 000000000000..c16e5aceee60 --- /dev/null +++ b/docs/source/supported_python_features.rst @@ -0,0 +1,12 @@ +Supported Python features and modules +===================================== + +Lists of supported Python features and standard library modules are maintained in the mypy wiki: + +- `Supported Python features `_ +- `Supported Python modules `_ + +Runtime definition of methods and functions +******************************************* + +By default, mypy will not let you redefine functions or methods, and you can't add functions to a class or module outside its definition -- but only if this is visible to the type checker. This only affects static checking, as mypy performs no additional type checking at runtime. You can easily work around this. For example, you can use dynamically typed code or values with Any types, or you can use setattr or other introspection features. However, you need to be careful if you decide to do this. If used indiscriminately, you may have difficulty using static typing effectively, since the type checker cannot see functions defined at runtime. diff --git a/docs/source/type_inference_and_annotations.rst b/docs/source/type_inference_and_annotations.rst new file mode 100644 index 000000000000..734a73feaddf --- /dev/null +++ b/docs/source/type_inference_and_annotations.rst @@ -0,0 +1,107 @@ +Type inference and type annotations +=================================== + +Type inference +************** + +The initial assignment defines a variable. If you do not explicitly specify the type of the variable, mypy infers the type based on the static type of the value expression: + +.. code-block:: python + + i = 1 # Infer type int for i + l = [1, 2] # Infer type List[int] for l + +Type inference is bidirectional and takes context into account. For example, the following is valid: + +.. code-block:: python + + def f(l: List[object]) -> None: + l = [1, 2] # Infer type List[object] for [1, 2] + +In an assignment, the type context is determined by the assignment target. In this case this is l, which has the type List[object]. The value expression [1, 2] is type checked in this context and given the type List[object]. In the previous example we introduced a new variable l, and here the type context was empty. + +Note that the following is not valid, since List[int] is not compatible with List[object]: + +.. code-block:: python + + def f(l: List[object], k: List[int]) -> None: + l = k # Type check error: incompatible types in assignment + +The reason why the above assignment is disallowed is that allowing the assignment could result in non-int values stored in a list of int: + +.. code-block:: python + + def f(l: List[object], k: List[int]) -> None: + l = k + l.append('x') + print(k[-1]) # Ouch; a string in List[int] + +You can still run the above program; it prints x. This illustrates the fact that static types are used during type checking, but they do not affect the runtime behavior of programs. You can run programs with type check failures, which is often very handy when performing a large refactoring. Thus you can always 'work around' the type system, and it doesn't really limit what you can do in your program. + +Type inference is not used in dynamically typed functions (those without an explicit return type) — every local variable type defaults to Any, which is discussed below. + +Explicit types for collections +****************************** + +The type checker cannot always infer the type of a list or a dictionary. This often arises when creating an empty list or dictionary and assigning it to a new variable without an explicit variable type. In these cases you can give the type explicitly using the type name as a constructor: + +.. code-block:: python + + l = List[int]() # Create empty list with type List[int] + d = Dict[str, int]() # Create empty dictionary (str -> int) + +Similarly, you can also give an explicit type when creating an empty set: + +.. code-block:: python + + s = Set[int]() + +Explicit types for variables +**************************** + +.. code-block:: python + + s = Undefined(str) # Declare type of x to be str. + s = 'x' # OK + s = 1 # Type check error + +The Undefined call evaluates to a special "Undefined" object that raises an exception on any operation: + +.. code-block:: python + + s = Undefined(str) + if s: # Runtime error: undefined value + print('hello') + +You can also override the inferred type of a variable by using a special comment after an assignment statement: + +.. code-block:: python + + x = [] # type: List[int] + +Here the # type comment applies both to the assignment target, in this case x, and also the initializer expression, via context. The above code is equivalent to this: + +.. code-block:: python + + x = List[int]() + +The type checker infers the value of a variable from the initializer, and if it is an empty collection such as [], the type is not well-defined. You can declare the collection type using one of the above syntax alternatives. + +Declaring multiple variable types on a line +******************************************* + +You can declare more than a single variable at a time. In order to nicely work with multiple assignment, you must give each variable a type separately: + +.. code-block:: python + + n, s = Undefined(int), Undefined(str) # Declare an integer and a string + i, found = 0, False # type: int, bool + +When using the latter form, you can optinally use parentheses around the types, assignment targets and assigned expression: + +.. code-block:: python + + i, found = 0, False # type: (int, bool) # OK + (i, found) = 0, False # type: int, bool # OK + i, found = (0, False) # type: int, bool # OK + (i, found) = (0, False) # type: (int, bool) # OK From 3da37af59c44ea8454d8ff45309cf0bc32f60bca Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 14:51:17 -0700 Subject: [PATCH 108/144] Remove single-page tutorial --- docs/source/index.rst | 1 - docs/source/tutorial.rst | 867 --------------------------------------- 2 files changed, 868 deletions(-) delete mode 100644 docs/source/tutorial.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 5dba09b71725..38779f173ec3 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,7 +9,6 @@ Welcome to Mypy's documentation! .. toctree:: :maxdepth: 2 - tutorial basics builtin_types type_inference_and_annotations diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst deleted file mode 100644 index de858ccef7a9..000000000000 --- a/docs/source/tutorial.rst +++ /dev/null @@ -1,867 +0,0 @@ -Tutorial -======== - -Function Signatures -******************* - -A function without a type signature is dynamically typed. You can declare the signature of a function using the Python 3 annotation syntax This makes the function statically typed (the type checker reports type errors within the function): - -.. code-block:: python - - # Dynamically typed (identical to Python) - - def greeting(name): - return 'Hello, {}'.format(name) - -.. code-block:: python - - # Statically typed (still valid Python) - - def greeting(name: str) -> str: - return 'Hello, {}'.format(name) - -A None return type indicates a function that does not explicitly return a value. Using a None result in a statically typed context results in a type check error: - -.. code-block:: python - - def p() -> None: - print('hello') - - a = p() # Type check error: p has None return value - -The typing module -***************** - -We cheated a bit in the above examples: a module is type checked only if it imports the module typing. Here is a complete statically typed example from the previous section: - -.. code-block:: python - - import typing - - def greeting(name: str) -> str: - return 'Hello, {}'.format(name) - -The typing module contains many definitions that are useful in statically typed code. You can also use from ... import to import them (we'll explain Iterable later in this document): - -.. code-block:: python - - from typing import Iterable - - def greet_all(names: Iterable[str]) -> None: - for name in names: - print('Hello, {}'.format(name)) - -For brevity, we often omit the typing import in code examples, but you should always include it in modules that contain statically typed code. - -You can still have dynamically typed functions in modules that import typing: - -.. code-block:: python - - import typing - - def f(): - 1 + 'x' # No static type error (dynamically typed) - - def g() -> None: - 1 + 'x' # Type check error (statically typed) - -Mixing dynamic and static typing within a single file is often useful. For example, if you are migrating existing Python code to static typing, it may be easiest to do this incrementally, such as by migrating a few functions at a time. Also, when prototyping a new feature, you may decide to first implement the relevant code using dynamic typing and only add type signatures later, when the code is more stable. - -.. note:: - - Currently the type checker checks the top levels and annotated functions of all modules, even those that don't import typing. However, you should not rely on this, as this will change in the future. - -Type checking and running programs -********************************** - -You can type check a program by using the mypy tool, which is basically a linter — it checks you program for errors without actually running it:: - - $ mypy program.py - -You can always run a mypy program as a Python program, without type checking, even it it has type errors:: - - $ python3 program.py - -All errors reported by mypy are essentially warnings that you are free to ignore, if you so wish. - -The `README `_ explains how to download and install mypy. - -.. note:: - - Depending on how mypy is configured, you may have to explicitly use the Python interpreter to run mypy. The mypy tool is an ordinary mypy (and so also Python) program. - -Built-in types -************** - -These are examples of some of the most common built-in types: - -.. code-block:: python - - int # integer objects of arbitrary size - float # floating point number - bool # boolean value - str # unicode string - bytes # 8-bit string - object # the common base class - List[str] # list of str objects - Dict[str, int] # dictionary from str to int - Iterable[int] # iterable object containing ints - Sequence[bool] # sequence of booleans - Any # dynamically typed value - -The type Any and type constructors List, Dict, Iterable and Sequence are defined in the typing module. - -The type Dict is a *generic* class, signified by type arguments within [...]. For example, Dict[int, str] is a dictionary from integers to strings and and Dict[Any, Any] is a dictionary of dynamically typed (arbitrary) values and keys. List is another generic class. Dict and List are aliases for the built-ins dict and list, respectively. - -Iterable and Sequence are generic abstract base classes that correspond to Python protocols. For example, a str object is valid when Iterable[str] or Sequence[str] is expected. Note that even though they are similar to abstract base classes defined in abc.collections (formerly collections), they are not identical, since the built-in collection type objects do not support indexing. - -Type inference -************** - -The initial assignment defines a variable. If you do not explicitly specify the type of the variable, mypy infers the type based on the static type of the value expression: - -.. code-block:: python - - i = 1 # Infer type int for i - l = [1, 2] # Infer type List[int] for l - -Type inference is bidirectional and takes context into account. For example, the following is valid: - -.. code-block:: python - - def f(l: List[object]) -> None: - l = [1, 2] # Infer type List[object] for [1, 2] - -In an assignment, the type context is determined by the assignment target. In this case this is l, which has the type List[object]. The value expression [1, 2] is type checked in this context and given the type List[object]. In the previous example we introduced a new variable l, and here the type context was empty. - -Note that the following is not valid, since List[int] is not compatible with List[object]: - -.. code-block:: python - - def f(l: List[object], k: List[int]) -> None: - l = k # Type check error: incompatible types in assignment - -The reason why the above assignment is disallowed is that allowing the assignment could result in non-int values stored in a list of int: - -.. code-block:: python - - def f(l: List[object], k: List[int]) -> None: - l = k - l.append('x') - print(k[-1]) # Ouch; a string in List[int] - -You can still run the above program; it prints x. This illustrates the fact that static types are used during type checking, but they do not affect the runtime behavior of programs. You can run programs with type check failures, which is often very handy when performing a large refactoring. Thus you can always 'work around' the type system, and it doesn't really limit what you can do in your program. - -Type inference is not used in dynamically typed functions (those without an explicit return type) — every local variable type defaults to Any, which is discussed below. - -Explicit types for collections -****************************** - -The type checker cannot always infer the type of a list or a dictionary. This often arises when creating an empty list or dictionary and assigning it to a new variable without an explicit variable type. In these cases you can give the type explicitly using the type name as a constructor: - -.. code-block:: python - - l = List[int]() # Create empty list with type List[int] - d = Dict[str, int]() # Create empty dictionary (str -> int) - -Similarly, you can also give an explicit type when creating an empty set: - -.. code-block:: python - - s = Set[int]() - -Explicit types for variables -**************************** - -.. code-block:: python - - s = Undefined(str) # Declare type of x to be str. - s = 'x' # OK - s = 1 # Type check error - -The Undefined call evaluates to a special "Undefined" object that raises an exception on any operation: - -.. code-block:: python - - s = Undefined(str) - if s: # Runtime error: undefined value - print('hello') - -You can also override the inferred type of a variable by using a special comment after an assignment statement: - -.. code-block:: python - - x = [] # type: List[int] - -Here the # type comment applies both to the assignment target, in this case x, and also the initializer expression, via context. The above code is equivalent to this: - -.. code-block:: python - - x = List[int]() - -The type checker infers the value of a variable from the initializer, and if it is an empty collection such as [], the type is not well-defined. You can declare the collection type using one of the above syntax alternatives. - -User-defined types -****************** - -Each class is also a type. Any instance of a subclass is also compatible with all superclasses. All values are compatible with the object type (and also the Any type). - -.. code-block:: python - - class A: - def f(self) -> int: # Type of self inferred (A) - return 2 - - class B(A): - def f(self) -> int: - return 3 - def g(self) -> int: - return 4 - - a = B() # type: A # OK (explicit type for a; override type inference) - print(a.f()) # 3 - a.g() # Type check error: A has no method g - -The Any type -************ - -A value with the Any type is dynamically typed. Any operations are permitted on the value, and the operations are checked at runtime, similar to normal Python code. If you do not define a function return value or argument types, these default to Any. Also, a function without an explicit return type is dynamically typed. The body of a dynamically typed function is not checked statically. - -Any is compatible with every other type, and vice versa. No implicit type check is inserted when assigning a value of type Any to a variable with a more precise type: - -.. code-block:: python - - a, s = Undefined(Any), Undefined(str) - a = 2 # OK - s = a # OK - -Declared (and inferred) types are erased at runtime (they are basically treated as comments), and thus the above code does not generate a runtime error. - -Tuple types -*********** - -The type Tuple[t, ...] represents a tuple with the item types t, ...: - -.. code-block:: python - - def f(t: Tuple[int, str]) -> None: - t = 1, 'foo' # OK - t = 'foo', 1 # Type check error - -Class name forward references -***************************** - -Python does not allow references to a class object before the class is defined. Thus this code is does not work as expected: - -.. code-block:: python - - def f(x: A) -> None: # Error: Name A not defined - .... - - class A: - ... - -In cases like these you can enter the type as a string literal — this is a *forward reference*: - -.. code-block:: python - - def f(x: 'A') -> None: # OK - ... - - class A: - ... - -Of course, instead of using a string literal type, you could move the function definition after the class definition. This is not always desirable or even possible, though. - -Any type can be entered as a string literal, and youn can combine string-literal types with non-string-literal types freely: - -.. code-block:: python - - a = Undefined(List['A']) # OK - n = Undefined('int') # OK, though not useful - - class A: pass - -String literal types are never needed in # type comments. - -Instance and class attributes -***************************** - -Mypy type checker detects if you are trying to access a missing attribute, which is a very common programming error. For this to work correctly, instance and class attributes must be defined or initialized within the class. Mypy infers the types of attributes: - -.. code-block:: python - - class A: - def __init__(self, x: int) -> None: - self.x = x # Attribute x of type int - - a = A(1) - a.x = 2 # OK - a.y = 3 # Error: A has no attribute y - -This is a bit like each class having an implicitly defined __slots__ attribute. In Python semantics this is only enforced during type checking: at runtime we use standard Python semantics. You can selectively define a class as *dynamic*; dynamic classes have Python-like compile-time semantics, and they allow you to assign to arbitrary attributes anywhere in a program without the type checker complaining: - -.. code-block:: python - - from typing import Dynamic - - class A(Dynamic): - pass - - a = A() - a.x = 2 # OK, no need to define x explicitly. - -Mypy also lets you read arbitrary attributes of dynamic class instances. This limits type checking effectiveness, so you should only use dynamic classes when you really need them. - -.. note:: - - Dynamic classes are not implemented in the current mypy version. - -You can declare variables in the class body explicitly using Undefined or a type comment: - -.. code-block:: python - - class A: - x = Undefined(List[int]) # Declare attribute y of type List[int] - y = 0 # type: Any # Declare attribute x of type Any - - a = A() - a.x = [1] # OK - -As in Python, a variable defined in the class body can used as a class or an instance variable. - -Similarly, you can give explicit types to instance variables defined in a method: - -.. code-block:: python - - class A: - def __init__(self) -> None: - self.x = Undefined(List[int]) # OK - - def f(self) -> None: - self.y = 0 # type: Any # OK - -You can only define an instance variable within a method if you assign to it explicitly using self: - -.. code-block:: python - - class A: - def __init__(self) -> None: - self.y = 1 # Define y - a = self - a.x = 1 # Error: x not defined - -Overriding statically typed methods -*********************************** - -When overriding a statically typed method, mypy checks that the override has a compatible signature: - -.. code-block:: python - - class A: - def f(self, x: int) -> None: - ... - - class B(A): - def f(self, x: str) -> None: # Error: type of x incompatible - ... - - class C(A): - def f(self, x: int, y: int) -> None: # Error: too many arguments - ... - - class D(A): - def f(self, x: int) -> None: # OK - ... - -.. note:: - - You can also vary return types **covariantly** in overriding. For example, you could override the return type 'object' with a subtype such as 'int'. - -You can also override a statically typed method with a dynamically typed one. This allows dynamically typed code to override methods defined in library classes without worrying about their type signatures, similar to Python. - -There is no runtime enforcement that the method override returns a value that is compatible with the original return type, since types are erased in the Python semantics: - -.. code-block:: python - - class A: - def inc(self, x: int) -> int: - return x + 1 - - class B(A): - def inc(self, x): # Override, dynamically typed - return 'hello' - - b = B() - print(b.inc(1)) # hello - a = b # type: A - print(a.inc(1)) # hello - -Declaring multiple variable types on a line -******************************************* - -You can declare more than a single variable at a time. In order to nicely work with multiple assignment, you must give each variable a type separately: - -.. code-block:: python - - n, s = Undefined(int), Undefined(str) # Declare an integer and a string - i, found = 0, False # type: int, bool - -When using the latter form, you can optinally use parentheses around the types, assignment targets and assigned expression: - -.. code-block:: python - - i, found = 0, False # type: (int, bool) # OK - (i, found) = 0, False # type: int, bool # OK - i, found = (0, False) # type: int, bool # OK - (i, found) = (0, False) # type: (int, bool) # OK - -Dynamically typed code -********************** - -As mentioned earlier, bodies of functions that don't have have an explicit return type are dynamically typed (operations are checked at runtime). Code outside functions is statically typed by default, and types of variables are inferred. This does usually the right thing, but you can also make any variable dynamically typed by defining it explicitly with the type Any: - -.. code-block:: python - - from typing import Any - - s = 1 # Statically typed (type int) - d = 1 # type: Any # Dynamically typed (type Any) - s = 'x' # Type check error - d = 'x' # OK - -Alternatively, you can use the Undefined construct to define dynamically typed variables, as Any can be used anywhere any other type is valid: - -.. code-block:: python - - from typing import Undefined, Any - - d = Undefined(Any) - d = 1 # OK - d = 'x' # OK - -Additionally, if you don't import the typing module in a file, all code outside functions will be dynamically typed by default, and the file is not type checked at all. This mode makes it easy to include existing Python code that is not trivially compatible with static typing. - -.. note:: - - The current mypy version type checks all modules, even those that don't import typing. This will change in a future version. - -Abstract base classes and multiple inheritance -********************************************** - -Mypy uses Python abstract base classes for protocol types. There are several built-in abstract base classes types (for example, Sequence, Iterable and Iterator). You can define abstract base classes using the abc.ABCMeta metaclass and the abc.abstractmethod function decorator. - -.. code-block:: python - - from abc import ABCMeta, abstractmethod - import typing - - class A(metaclass=ABCMeta): - @abstractmethod - def foo(self, x: int) -> None: pass - - @abstractmethod - def bar(self) -> str: pass - - class B(A): - def foo(self, x: int) -> None: ... - def bar(self -> str: - return 'x' - - a = A() # Error: A is abstract - b = B() # OK - -Unlike most Python code, abstract base classes are likely to play a significant role in many complex mypy programs. - -A class can inherit any number of classes, both abstract and concrete. As with normal overrides, a dynamically typed method can implement a statically typed abstract method defined in an abstract base class. - -.. note:: - - There are also plans to support more Python-style "duck typing" in the type system. The details are still open. - -Function overloading -******************** - -You can define multiple instances of a function with the same name but different signatures. The first matching signature is selected at runtime when evaluating each individual call. This enables also a form of multiple dispatch. - -.. code-block:: python - - from typing import overload - - @overload - def abs(n: int) -> int: - return n if n >= 0 else -n - - @overload - def abs(n: float) -> float: - return n if n >= 0.0 else -n - - abs(-2) # 2 (int) - abs(-1.5) # 1.5 (float) - -Overloaded function variants still define a single runtime object; the following code is valid: - -.. code-block:: python - - my_abs = abs - my_abs(-2) # 2 (int) - my_abs(-1.5) # 1.5 (float) - -The overload variants must be adjacent in the code. This makes code clearer, and otherwise there would be awkward corner cases such as partially defined overloaded functions that could surprise the unwary programmer. - -.. note:: - - As generic type variables are erased at runtime, an overloaded function cannot dispatch based on a generic type argument, e.g. List[int] versus List[str]. - -Callable types and lambdas -************************** - -You can pass around function objects and bound methods in statically typed code. The type of a function that accepts arguments A1, ..., An and returns Rt is Function[[A1, ..., An], Rt]. Example: - -.. code-block:: python - - def twice(i: int, next: Function[[int], int]) -> int: - return next(next(i)) - - def add(i: int) -> int: - return i + 1 - - print(twice(3, add)) # 5 - -Lambdas are also supported. The lambda argument and return value types cannot be given explicitly; they are always inferred based on context using bidirectional type inference: - -.. code-block:: python - - l = map(lambda x: x + 1, [1, 2, 3]) # infer x as int and l as List[int] - -If you want to give the argument or return value types explicitly, use an ordinary, perhaps nested function definition. - -Casts -***** - -Mypy supports type casts that are usually used to coerce a statically typed value to a subtype. Unlike languages such as Java or C#, however, mypy casts are only used as hints for the type checker when using Python semantics, and they have no runtime effect. Use the function cast to perform a cast: - -.. code-block:: python - - from typing import cast - - o = [1] # type: object - x = cast(List[int], o) # OK - y = cast(List[str], o) # OK (cast performs no actual runtime check) - -Supporting runtime checking of casts such as the above when using Python semantics would require emulating reified generics and this would be difficult to do and would likely degrade performance and make code more difficult to read. You should not rely in your programs on casts being checked at runtime. Use an assertion if you want to perform an actual runtime check. Casts are used to silence spurious type checker warnings. - -You don't need a cast for expressions with type Any, of when assigning to a variable with type Any, as was explained earlier. - -You can cast to a dynamically typed value by just calling Any: - -.. code-block:: python - - from typing import Any - - def f(x: object) -> None: - Any(x).foo() # OK - -Notes about writing statically typed code -***************************************** - -Statically typed function bodies are often identical to normal Python code, but sometimes you need to do things slightly differently. This section introduces some of the most common cases which require different conventions in statically typed code. - -First, you need to specify the type when creating an empty list or dict and when you assign to a new variable, as mentioned earlier: - -.. code-block:: python - - a = List[int]() # Explicit type required in statically typed code - a = [] # Fine in a dynamically typed function, or if type - # of a has been declared or inferred before - -Sometimes you can avoid the explicit list item type by using a list comprehension. Here a type annotation is needed: - -.. code-block:: python - - l = List[int]() - for i in range(n): - l.append(i * i) - -.. note:: - - A future mypy version may be able to deal with cases such as the above without type annotations. - -No type annotation needed if using a list comprehension: - -.. code-block:: python - - l = [i * i for i in range(n)] - -However, in more complex cases the explicit type annotation can improve the clarity of your code, whereas a complex list comprehension can make your code difficult to understand. - -Second, each name within a function only has a single type. You can reuse for loop indices etc., but if you want to use a variable with multiple types within a single function, you may need to declare it with the Any type. - -.. code-block:: python - - def f() -> None: - n = 1 - ... - n = x # Type error: n has type int - -.. note:: - - This is another limitation that could be lifted in a future mypy version. - -Third, sometimes the inferred type is a subtype of the desired type. The type inference uses the first assignment to infer the type of a name: - -.. code-block:: python - - # Assume Shape is the base class of both Circle and Triangle. - shape = Circle() # Infer shape to be Circle - ... - shape = Triangle() # Type error: Triangle is not a Circle - -You can just give an explicit type for the variable in cases such the above example: - -.. code-block:: python - - shape = Circle() # type: Shape # The variable s can be any Shape, - # not just Circle - ... - shape = Triangle() # OK - -Fourth, if you use isinstance tests or other kinds of runtime type tests, you may have to add casts (this is similar to instanceof tests in Java): - -.. code-block:: python - - def f(o: object) -> None: - if isinstance(o, int): - n = cast(int, o) - n += 1 # o += 1 would be an error - ... - -Note that the object type used in the above example is similar to Object in Java: it only supports operations defined for all objects, such as equality and isinstance(). The type Any, in contrast, supports all operations, even if they may fail at runtime. The cast above would have been unnecessary if the type of o was Any. - -Some consider casual use of isinstance tests a sign of bad programming style. Often a method override or an overloaded function is a cleaner way of implementing functionality that depends on the runtime types of values. However, use whatever techniques that work for you. Sometimes isinstance tests *are* the cleanest way of implementing a piece of functionality. - -Type inference in mypy is designed to work well in common cases, to be predictable and to let the type checker give useful error messages. More powerful type inference strategies often have complex and difficult-to-prefict failure modes and could result in very confusing error messages. - -Defining generic classes -************************ - -The built-in collection classes are generic classes. Generic types have one or more type parameters, which can be arbitrary types. For example, Dict]int, str] has the type parameters int and str, and List[int] has a type parameter int. - -Programs can also define new generic classes. Here is a very simple generic class that represents a stack: - -.. code-block:: python - - from typing import typevar, Generic - - T = typevar('T') - - class Stack(Generic[T]): - def __init__(self) -> None: - self.items = List[T]() # Create an empty list with items of type T - - def push(self, item: T) -> None: - self.items.append(item) - - def pop(self) -> T: - return self.items.pop() - - def empty(self) -> bool: - return not self.items - -The Stack class can be used to represent a stack of any type: Stack[int], Stack[Tuple[int, str]], etc. - -Using Stack is similar to built-in container types: - -.. code-block:: python - - stack = Stack[int]() # Construct an empty Stack[int] instance - stack.push(2) - stack.pop() - stack.push('x') # Type error - -Type inference works for user-defined generic types as well: - -.. code-block:: python - - def process(stack: Stack[int]) -> None: ... - - process(Stack()) # Argument has inferred type Stack[int] - -Generic class internals -*********************** - -You may wonder what happens at runtime when you index Stack. Actually, indexing Stack just returns Stack: - ->>> print(Stack) - ->>> print(Stack[int]) - - -Note that built-in types list, dict and so on do not support indexing in Python. This is why we have the aliases List, Dict and so on in the typing module. Indexing these aliases just gives you the target class in Python, similar to Stack: - ->>> from typing import List ->>> List[int] - - -The above examples illustrate that type variables are erased at runtime when running in a Python VM. Generic Stack or list instances are just ordinary Python objects, and they have no extra runtime overhead or magic due to being generic, other than a metaclass that overloads the indexing operator. If you worry about the overhead introduced by the type indexing operation when constructing instances, you can often rewrite such code using a # type annotation, which has no runtime impact: - -.. code-block:: python - - x = List[int]() - x = [] # type: List[int] # Like the above but faster. - -The savings are rarely significant, but it could make a difference in a performance-critical loop or function. Function annotations, on the other hand, are only evaluated during the defintion of the function, not during every call. Constructing type objects in function signatures rarely has any noticeable performance impact. - -Generic functions -***************** - -Generic type variables can also be used to define generic functions: - -.. code-block:: python - - from typing import typevar, Sequence - - T = typevar('T') # Declare type variable - - def first(seq: Sequence[T]) -> T: # Generic function - return seq[0] - -As with generic classes, the type variable can be replaced with any type. That means first can we used with any sequence type, and the return type is derived from the sequence item type. For example: - -.. code-block:: python - - # Assume first defined as above. - - s = first('foo') # s has type str. - n = first([1, 2, 3]) # n has type int. - -Note also that a single definition of a type variable (such as T above) can be used in multiple generic functions or classes. In this example we use the same type variable in two generic functions: - -.. code-block:: python - - from typing typevar, Sequence - - T = typevar('T') # Declare type variable - - def first(seq: Sequence[T]) -> T: - return seq[0] - - def last(seq: Sequence[T]) -> T: - return seq[-1] - -You can also define generic methods — just use a type variable in the method signature that is different from class type variables. - -Supported Python features and modules -************************************* - -Lists of supported Python features and standard library modules are maintained in the mypy wiki: - -- `Supported Python features `_ -- `Supported Python modules `_ - -Runtime definition of methods and functions -******************************************* - -By default, mypy will not let you redefine functions or methods, and you can't add functions to a class or module outside its definition -- but only if this is visible to the type checker. This only affects static checking, as mypy performs no additional type checking at runtime. You can easily work around this. For example, you can use dynamically typed code or values with Any types, or you can use setattr or other introspection features. However, you need to be careful if you decide to do this. If used indiscriminately, you may have difficulty using static typing effectively, since the type checker cannot see functions defined at runtime. - -Additional features - -Several mypy features are not currently covered by this tutorial, including the following: - -- inheritance between generic classes - -- compatibility and subtyping of generic types, including covariance of generic types - -- super() - -Planned features -**************** - -This section introduces some language features that are still work in progress. - -None ----- - -Currently, None is a valid value for each type, similar to null or NULL in many languages. However, it is likely that this decision will be reversed, and types do not include None default. The Optional type modifier can be used to define a type variant that includes None, such as Optional(int): - -.. code-block:: python - - def f() -> Optional[int]: - return None # OK - - def g() -> int: - ... - return None # Error: None not compatible with int - -Also, most operations would not be supported on None values: - -.. code-block:: python - - def f(x: Optional[int]) -> int: - return x + 1 # Error: Cannot add None and int - -Instead, an explicit None check would be required. This would benefit from more powerful type inference: - -.. code-block:: python - - def f(x: Optional[int]) -> int: - if x is None: - return 0 - else: - # The inferred type of x is just int here. - return x + 1 - -We would infer the type of x to be int in the else block due to the check against None in the if condition. - -Union types ------------ - -Python functions often accept values of two or more different types. You can use overloading to model this in statically typed code, but union types can make code like this easier to write. - -Use the Union[...] type constructor to construct a union type. For example, the type Union[int, str] is compatible with both integers and strings. You can use an isinstance check to narrow down the type to a specific type: - -.. code-block:: python - - from typing import Union - - def f(x: Union[int, str]) -> None: - x + 1 # Error: str + int is not valid - if isinstance(x, int): - # Here type of x is int. - x + 1 # OK - else: - # Here type of x is str. - x + 'a' # OK - - f(1) # OK - f('x') # OK - f(1.1) # Error - -More general type inference ---------------------------- - -It may be useful to support type inference also for variables defined in multiple locations in an if/else statement, even if the initializer types are different: - -.. code-block:: python - - if x: - y = None # First definition of y - else: - y = 'a' # Second definition of y - -In the above example, both of the assignments would be used in type inference, and the type of y would be str. However, it is not obvious whether this would be generally desirable in more complex cases. - -Revision history -**************** - -List of major changes to this document: - -- Sep 15 2014: Migrated docs to Sphinx - -- Aug 25 2014: Don't discuss native semantics. There is only Python semantics. - -- Jul 2 2013: Rewrite to use new syntax. Shift focus to discussing Python semantics. Add more content, including short discussions of `generic functions `_ and `union types `_. - -- Dec 20 2012: Add new sections on explicit types for collections, declaring multiple variables, callable types, casts, generic classes and translation to Python. Add notes about writing statically typed code and links to the wiki. Also add a table of contents. Various other, more minor updates. - -- Dec 2 2012: Use new syntax for `list types `_ and `interfaces `_. Discuss `runtime redefinition of methods and functions `. Also did minor restructuring. From 15c3d08b9a9bd5d8bbb22d4ad44c9b1e796a65a4 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 14:53:11 -0700 Subject: [PATCH 109/144] Minor tweaks --- docs/source/basics.rst | 2 +- docs/source/index.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/basics.rst b/docs/source/basics.rst index 0c9e94318884..7b8c2e2d6267 100644 --- a/docs/source/basics.rst +++ b/docs/source/basics.rst @@ -1,7 +1,7 @@ Basics ====== -Function Signatures +Function signatures ******************* A function without a type signature is dynamically typed. You can declare the signature of a function using the Python 3 annotation syntax This makes the function statically typed (the type checker reports type errors within the function): diff --git a/docs/source/index.rst b/docs/source/index.rst index 38779f173ec3..a5f86c689ddd 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,8 +3,8 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to Mypy's documentation! -================================ +Welcome to Mypy documentation! +============================== .. toctree:: :maxdepth: 2 From faf0fad58c7186e8d1fb7a946b26ac9967734d8e Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 14:54:12 -0700 Subject: [PATCH 110/144] Move stuff around a bit --- docs/source/kinds_of_types.rst | 46 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index d89fef29fdd6..9425ca5d033f 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -48,6 +48,29 @@ The type Tuple[t, ...] represents a tuple with the item types t, ...: t = 1, 'foo' # OK t = 'foo', 1 # Type check error +Callable types and lambdas +************************** + +You can pass around function objects and bound methods in statically typed code. The type of a function that accepts arguments A1, ..., An and returns Rt is Function[[A1, ..., An], Rt]. Example: + +.. code-block:: python + + def twice(i: int, next: Function[[int], int]) -> int: + return next(next(i)) + + def add(i: int) -> int: + return i + 1 + + print(twice(3, add)) # 5 + +Lambdas are also supported. The lambda argument and return value types cannot be given explicitly; they are always inferred based on context using bidirectional type inference: + +.. code-block:: python + + l = map(lambda x: x + 1, [1, 2, 3]) # infer x as int and l as List[int] + +If you want to give the argument or return value types explicitly, use an ordinary, perhaps nested function definition. + Class name forward references ***************************** @@ -83,26 +106,3 @@ Any type can be entered as a string literal, and youn can combine string-literal class A: pass String literal types are never needed in # type comments. - -Callable types and lambdas -************************** - -You can pass around function objects and bound methods in statically typed code. The type of a function that accepts arguments A1, ..., An and returns Rt is Function[[A1, ..., An], Rt]. Example: - -.. code-block:: python - - def twice(i: int, next: Function[[int], int]) -> int: - return next(next(i)) - - def add(i: int) -> int: - return i + 1 - - print(twice(3, add)) # 5 - -Lambdas are also supported. The lambda argument and return value types cannot be given explicitly; they are always inferred based on context using bidirectional type inference: - -.. code-block:: python - - l = map(lambda x: x + 1, [1, 2, 3]) # infer x as int and l as List[int] - -If you want to give the argument or return value types explicitly, use an ordinary, perhaps nested function definition. From cab8078396a3d7d162c06465701072722326f0e5 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 15:02:58 -0700 Subject: [PATCH 111/144] Fix cross references and update vision history --- docs/source/common_issues.rst | 4 ++-- docs/source/generics.rst | 2 ++ docs/source/planned_features.rst | 2 ++ docs/source/revision_history.rst | 22 +++++++++++++++++----- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 2195d81809e7..e4b6b006d246 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -1,5 +1,5 @@ -Common issues -============= +Dealing with common issues +========================== Statically typed function bodies are often identical to normal Python code, but sometimes you need to do things slightly differently. This section introduces some of the most common cases which require different conventions in statically typed code. diff --git a/docs/source/generics.rst b/docs/source/generics.rst index 0f216137b375..f356a289aac6 100644 --- a/docs/source/generics.rst +++ b/docs/source/generics.rst @@ -71,6 +71,8 @@ The above examples illustrate that type variables are erased at runtime when run The savings are rarely significant, but it could make a difference in a performance-critical loop or function. Function annotations, on the other hand, are only evaluated during the defintion of the function, not during every call. Constructing type objects in function signatures rarely has any noticeable performance impact. +.. _generic-functions: + Generic functions ***************** diff --git a/docs/source/planned_features.rst b/docs/source/planned_features.rst index bef22cfc35a9..95e17531ec62 100644 --- a/docs/source/planned_features.rst +++ b/docs/source/planned_features.rst @@ -37,6 +37,8 @@ Instead, an explicit None check would be required. This would benefit from more We would infer the type of x to be int in the else block due to the check against None in the if condition. +.. _union-types: + Union types ----------- diff --git a/docs/source/revision_history.rst b/docs/source/revision_history.rst index 7c71fb655f95..ac1520651920 100644 --- a/docs/source/revision_history.rst +++ b/docs/source/revision_history.rst @@ -3,12 +3,24 @@ Revision history List of major changes to this document: -- Sep 15 2014: Migrated docs to Sphinx +- Oct 26 2014: Major restructuring. Split the HTML documentation into + multiple pages. -- Aug 25 2014: Don't discuss native semantics. There is only Python semantics. +- Sep 15 2014: Migrated docs to Sphinx. -- Jul 2 2013: Rewrite to use new syntax. Shift focus to discussing Python semantics. Add more content, including short discussions of `generic functions `_ and `union types `_. +- Aug 25 2014: Don't discuss native semantics. There is only Python + semantics. -- Dec 20 2012: Add new sections on explicit types for collections, declaring multiple variables, callable types, casts, generic classes and translation to Python. Add notes about writing statically typed code and links to the wiki. Also add a table of contents. Various other, more minor updates. +- Jul 2 2013: Rewrite to use new syntax. Shift focus to discussing + Python semantics. Add more content, including short discussions of + :ref:`generic-functions` and :ref:`union-types`. -- Dec 2 2012: Use new syntax for `list types `_ and `interfaces `_. Discuss `runtime redefinition of methods and functions `. Also did minor restructuring. +- Dec 20 2012: Add new sections on explicit types for collections, + declaring multiple variables, callable types, casts, generic classes + and translation to Python. Add notes about writing statically typed + code and links to the wiki. Also add a table of contents. Various + other, more minor updates. + +- Dec 2 2012: Use new syntax for list types and interfaces (which were + replaced with abstract base classes). Discuss runtime redefinition + of methods and functions. Also did minor restructuring. From 99a38217384fd5326fdc07807a13b9ff9e24c250 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 15:03:15 -0700 Subject: [PATCH 112/144] Remove old entries from revision history These don't make much sense any more, as mypy has changed a lot since these were written. --- docs/source/revision_history.rst | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/source/revision_history.rst b/docs/source/revision_history.rst index ac1520651920..889daa5ef985 100644 --- a/docs/source/revision_history.rst +++ b/docs/source/revision_history.rst @@ -14,13 +14,3 @@ List of major changes to this document: - Jul 2 2013: Rewrite to use new syntax. Shift focus to discussing Python semantics. Add more content, including short discussions of :ref:`generic-functions` and :ref:`union-types`. - -- Dec 20 2012: Add new sections on explicit types for collections, - declaring multiple variables, callable types, casts, generic classes - and translation to Python. Add notes about writing statically typed - code and links to the wiki. Also add a table of contents. Various - other, more minor updates. - -- Dec 2 2012: Use new syntax for list types and interfaces (which were - replaced with abstract base classes). Discuss runtime redefinition - of methods and functions. Also did minor restructuring. From 3ae57de1926d83bc685211713837983a7ce650fe Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 15:07:16 -0700 Subject: [PATCH 113/144] Move union types to section 'Kinds of types' --- docs/source/kinds_of_types.rst | 35 ++++++++++++++++++++++++++++++-- docs/source/planned_features.rst | 26 ------------------------ 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 9425ca5d033f..10cfe697b7ca 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -48,8 +48,8 @@ The type Tuple[t, ...] represents a tuple with the item types t, ...: t = 1, 'foo' # OK t = 'foo', 1 # Type check error -Callable types and lambdas -************************** +Callable types (and lambdas) +**************************** You can pass around function objects and bound methods in statically typed code. The type of a function that accepts arguments A1, ..., An and returns Rt is Function[[A1, ..., An], Rt]. Example: @@ -71,6 +71,37 @@ Lambdas are also supported. The lambda argument and return value types cannot be If you want to give the argument or return value types explicitly, use an ordinary, perhaps nested function definition. +.. _union-types: + +Union types +*********** + +Python functions often accept values of two or more different +types. You can use overloading to model this in statically typed code, +but union types can make code like this easier to write. + +Use the Union[...] type constructor to construct a union type. For +example, the type Union[int, str] is compatible with both integers and +strings. You can use an isinstance check to narrow down the type to a +specific type: + +.. code-block:: python + + from typing import Union + + def f(x: Union[int, str]) -> None: + x + 1 # Error: str + int is not valid + if isinstance(x, int): + # Here type of x is int. + x + 1 # OK + else: + # Here type of x is str. + x + 'a' # OK + + f(1) # OK + f('x') # OK + f(1.1) # Error + Class name forward references ***************************** diff --git a/docs/source/planned_features.rst b/docs/source/planned_features.rst index 95e17531ec62..6c96cc2839c2 100644 --- a/docs/source/planned_features.rst +++ b/docs/source/planned_features.rst @@ -37,32 +37,6 @@ Instead, an explicit None check would be required. This would benefit from more We would infer the type of x to be int in the else block due to the check against None in the if condition. -.. _union-types: - -Union types ------------ - -Python functions often accept values of two or more different types. You can use overloading to model this in statically typed code, but union types can make code like this easier to write. - -Use the Union[...] type constructor to construct a union type. For example, the type Union[int, str] is compatible with both integers and strings. You can use an isinstance check to narrow down the type to a specific type: - -.. code-block:: python - - from typing import Union - - def f(x: Union[int, str]) -> None: - x + 1 # Error: str + int is not valid - if isinstance(x, int): - # Here type of x is int. - x + 1 # OK - else: - # Here type of x is str. - x + 'a' # OK - - f(1) # OK - f('x') # OK - f(1.1) # Error - More general type inference --------------------------- From a3cf5c9e5241ea01165ac5ff99c55c5027cf4eaf Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 15:14:04 -0700 Subject: [PATCH 114/144] Wrap long lines --- docs/source/additional_features.rst | 3 +- docs/source/basics.rst | 51 ++++- docs/source/builtin_types.rst | 16 +- docs/source/casts.rst | 17 +- docs/source/class_basics.rst | 61 +++-- docs/source/common_issues.rst | 60 +++-- docs/source/dynamic_typing.rst | 20 +- docs/source/faq.rst | 216 ++++++++++++++---- docs/source/function_overloading.rst | 17 +- docs/source/generics.rst | 45 +++- docs/source/kinds_of_types.rst | 43 +++- docs/source/planned_features.rst | 23 +- docs/source/supported_python_features.rst | 14 +- .../source/type_inference_and_annotations.rst | 58 +++-- 14 files changed, 499 insertions(+), 145 deletions(-) diff --git a/docs/source/additional_features.rst b/docs/source/additional_features.rst index 19b22ec74d37..110514528026 100644 --- a/docs/source/additional_features.rst +++ b/docs/source/additional_features.rst @@ -1,7 +1,8 @@ Additional features ------------------- -Several mypy features are not currently covered by this tutorial, including the following: +Several mypy features are not currently covered by this tutorial, +including the following: - inheritance between generic classes - compatibility and subtyping of generic types, including covariance of generic types diff --git a/docs/source/basics.rst b/docs/source/basics.rst index 7b8c2e2d6267..6146ee4a781f 100644 --- a/docs/source/basics.rst +++ b/docs/source/basics.rst @@ -4,7 +4,10 @@ Basics Function signatures ******************* -A function without a type signature is dynamically typed. You can declare the signature of a function using the Python 3 annotation syntax This makes the function statically typed (the type checker reports type errors within the function): +A function without a type signature is dynamically typed. You can +declare the signature of a function using the Python 3 annotation +syntax This makes the function statically typed (the type checker +reports type errors within the function): .. code-block:: python @@ -20,7 +23,9 @@ A function without a type signature is dynamically typed. You can declare the si def greeting(name: str) -> str: return 'Hello, {}'.format(name) -A None return type indicates a function that does not explicitly return a value. Using a None result in a statically typed context results in a type check error: +A None return type indicates a function that does not explicitly +return a value. Using a None result in a statically typed context +results in a type check error: .. code-block:: python @@ -32,7 +37,9 @@ A None return type indicates a function that does not explicitly return a value. The typing module ***************** -We cheated a bit in the above examples: a module is type checked only if it imports the module typing. Here is a complete statically typed example from the previous section: +We cheated a bit in the above examples: a module is type checked only +if it imports the module typing. Here is a complete statically typed +example from the previous section: .. code-block:: python @@ -41,7 +48,9 @@ We cheated a bit in the above examples: a module is type checked only if it impo def greeting(name: str) -> str: return 'Hello, {}'.format(name) -The typing module contains many definitions that are useful in statically typed code. You can also use from ... import to import them (we'll explain Iterable later in this document): +The typing module contains many definitions that are useful in +statically typed code. You can also use from ... import to import them +(we'll explain Iterable later in this document): .. code-block:: python @@ -51,7 +60,9 @@ The typing module contains many definitions that are useful in statically typed for name in names: print('Hello, {}'.format(name)) -For brevity, we often omit the typing import in code examples, but you should always include it in modules that contain statically typed code. +For brevity, we often omit the typing import in code examples, but you +should always include it in modules that contain statically typed +code. You can still have dynamically typed functions in modules that import typing: @@ -65,27 +76,43 @@ You can still have dynamically typed functions in modules that import typing: def g() -> None: 1 + 'x' # Type check error (statically typed) -Mixing dynamic and static typing within a single file is often useful. For example, if you are migrating existing Python code to static typing, it may be easiest to do this incrementally, such as by migrating a few functions at a time. Also, when prototyping a new feature, you may decide to first implement the relevant code using dynamic typing and only add type signatures later, when the code is more stable. +Mixing dynamic and static typing within a single file is often +useful. For example, if you are migrating existing Python code to +static typing, it may be easiest to do this incrementally, such as by +migrating a few functions at a time. Also, when prototyping a new +feature, you may decide to first implement the relevant code using +dynamic typing and only add type signatures later, when the code is +more stable. .. note:: - Currently the type checker checks the top levels and annotated functions of all modules, even those that don't import typing. However, you should not rely on this, as this will change in the future. + Currently the type checker checks the top levels and annotated + functions of all modules, even those that don't import + typing. However, you should not rely on this, as this will change + in the future. Type checking and running programs ********************************** -You can type check a program by using the mypy tool, which is basically a linter — it checks you program for errors without actually running it:: +You can type check a program by using the mypy tool, which is +basically a linter — it checks you program for errors without actually +running it:: $ mypy program.py -You can always run a mypy program as a Python program, without type checking, even it it has type errors:: +You can always run a mypy program as a Python program, without type +checking, even it it has type errors:: $ python3 program.py -All errors reported by mypy are essentially warnings that you are free to ignore, if you so wish. +All errors reported by mypy are essentially warnings that you are free +to ignore, if you so wish. -The `README `_ explains how to download and install mypy. +The `README `_ +explains how to download and install mypy. .. note:: - Depending on how mypy is configured, you may have to explicitly use the Python interpreter to run mypy. The mypy tool is an ordinary mypy (and so also Python) program. + Depending on how mypy is configured, you may have to explicitly use + the Python interpreter to run mypy. The mypy tool is an ordinary + mypy (and so also Python) program. diff --git a/docs/source/builtin_types.rst b/docs/source/builtin_types.rst index 792e1996920c..74b58d55af7e 100644 --- a/docs/source/builtin_types.rst +++ b/docs/source/builtin_types.rst @@ -17,8 +17,18 @@ These are examples of some of the most common built-in types: Sequence[bool] # sequence of booleans Any # dynamically typed value -The type Any and type constructors List, Dict, Iterable and Sequence are defined in the typing module. +The type Any and type constructors List, Dict, Iterable and Sequence +are defined in the typing module. -The type Dict is a *generic* class, signified by type arguments within [...]. For example, Dict[int, str] is a dictionary from integers to strings and and Dict[Any, Any] is a dictionary of dynamically typed (arbitrary) values and keys. List is another generic class. Dict and List are aliases for the built-ins dict and list, respectively. +The type Dict is a *generic* class, signified by type arguments within +[...]. For example, Dict[int, str] is a dictionary from integers to +strings and and Dict[Any, Any] is a dictionary of dynamically typed +(arbitrary) values and keys. List is another generic class. Dict and +List are aliases for the built-ins dict and list, respectively. -Iterable and Sequence are generic abstract base classes that correspond to Python protocols. For example, a str object is valid when Iterable[str] or Sequence[str] is expected. Note that even though they are similar to abstract base classes defined in abc.collections (formerly collections), they are not identical, since the built-in collection type objects do not support indexing. +Iterable and Sequence are generic abstract base classes that +correspond to Python protocols. For example, a str object is valid +when Iterable[str] or Sequence[str] is expected. Note that even though +they are similar to abstract base classes defined in abc.collections +(formerly collections), they are not identical, since the built-in +collection type objects do not support indexing. diff --git a/docs/source/casts.rst b/docs/source/casts.rst index 8f4c0a2477d3..ad6548159649 100644 --- a/docs/source/casts.rst +++ b/docs/source/casts.rst @@ -1,7 +1,11 @@ Casts ===== -Mypy supports type casts that are usually used to coerce a statically typed value to a subtype. Unlike languages such as Java or C#, however, mypy casts are only used as hints for the type checker when using Python semantics, and they have no runtime effect. Use the function cast to perform a cast: +Mypy supports type casts that are usually used to coerce a statically +typed value to a subtype. Unlike languages such as Java or C#, +however, mypy casts are only used as hints for the type checker when +using Python semantics, and they have no runtime effect. Use the +function cast to perform a cast: .. code-block:: python @@ -11,9 +15,16 @@ Mypy supports type casts that are usually used to coerce a statically typed valu x = cast(List[int], o) # OK y = cast(List[str], o) # OK (cast performs no actual runtime check) -Supporting runtime checking of casts such as the above when using Python semantics would require emulating reified generics and this would be difficult to do and would likely degrade performance and make code more difficult to read. You should not rely in your programs on casts being checked at runtime. Use an assertion if you want to perform an actual runtime check. Casts are used to silence spurious type checker warnings. +Supporting runtime checking of casts such as the above when using +Python semantics would require emulating reified generics and this +would be difficult to do and would likely degrade performance and make +code more difficult to read. You should not rely in your programs on +casts being checked at runtime. Use an assertion if you want to +perform an actual runtime check. Casts are used to silence spurious +type checker warnings. -You don't need a cast for expressions with type Any, of when assigning to a variable with type Any, as was explained earlier. +You don't need a cast for expressions with type Any, of when assigning +to a variable with type Any, as was explained earlier. You can cast to a dynamically typed value by just calling Any: diff --git a/docs/source/class_basics.rst b/docs/source/class_basics.rst index f5fab4ba1f94..b50e05b70968 100644 --- a/docs/source/class_basics.rst +++ b/docs/source/class_basics.rst @@ -4,7 +4,10 @@ Class basics Instance and class attributes ***************************** -Mypy type checker detects if you are trying to access a missing attribute, which is a very common programming error. For this to work correctly, instance and class attributes must be defined or initialized within the class. Mypy infers the types of attributes: +Mypy type checker detects if you are trying to access a missing +attribute, which is a very common programming error. For this to work +correctly, instance and class attributes must be defined or +initialized within the class. Mypy infers the types of attributes: .. code-block:: python @@ -16,7 +19,13 @@ Mypy type checker detects if you are trying to access a missing attribute, which a.x = 2 # OK a.y = 3 # Error: A has no attribute y -This is a bit like each class having an implicitly defined __slots__ attribute. In Python semantics this is only enforced during type checking: at runtime we use standard Python semantics. You can selectively define a class as *dynamic*; dynamic classes have Python-like compile-time semantics, and they allow you to assign to arbitrary attributes anywhere in a program without the type checker complaining: +This is a bit like each class having an implicitly defined __slots__ +attribute. In Python semantics this is only enforced during type +checking: at runtime we use standard Python semantics. You can +selectively define a class as *dynamic*; dynamic classes have +Python-like compile-time semantics, and they allow you to assign to +arbitrary attributes anywhere in a program without the type checker +complaining: .. code-block:: python @@ -28,13 +37,16 @@ This is a bit like each class having an implicitly defined __slots__ attribute. a = A() a.x = 2 # OK, no need to define x explicitly. -Mypy also lets you read arbitrary attributes of dynamic class instances. This limits type checking effectiveness, so you should only use dynamic classes when you really need them. +Mypy also lets you read arbitrary attributes of dynamic class +instances. This limits type checking effectiveness, so you should only +use dynamic classes when you really need them. .. note:: Dynamic classes are not implemented in the current mypy version. -You can declare variables in the class body explicitly using Undefined or a type comment: +You can declare variables in the class body explicitly using Undefined +or a type comment: .. code-block:: python @@ -45,9 +57,11 @@ You can declare variables in the class body explicitly using Undefined or a type a = A() a.x = [1] # OK -As in Python, a variable defined in the class body can used as a class or an instance variable. +As in Python, a variable defined in the class body can used as a class +or an instance variable. -Similarly, you can give explicit types to instance variables defined in a method: +Similarly, you can give explicit types to instance variables defined +in a method: .. code-block:: python @@ -58,7 +72,8 @@ Similarly, you can give explicit types to instance variables defined in a method def f(self) -> None: self.y = 0 # type: Any # OK -You can only define an instance variable within a method if you assign to it explicitly using self: +You can only define an instance variable within a method if you assign +to it explicitly using self: .. code-block:: python @@ -71,7 +86,8 @@ You can only define an instance variable within a method if you assign to it exp Overriding statically typed methods *********************************** -When overriding a statically typed method, mypy checks that the override has a compatible signature: +When overriding a statically typed method, mypy checks that the +override has a compatible signature: .. code-block:: python @@ -93,11 +109,18 @@ When overriding a statically typed method, mypy checks that the override has a c .. note:: - You can also vary return types **covariantly** in overriding. For example, you could override the return type 'object' with a subtype such as 'int'. + You can also vary return types **covariantly** in overriding. For + example, you could override the return type 'object' with a subtype + such as 'int'. -You can also override a statically typed method with a dynamically typed one. This allows dynamically typed code to override methods defined in library classes without worrying about their type signatures, similar to Python. +You can also override a statically typed method with a dynamically +typed one. This allows dynamically typed code to override methods +defined in library classes without worrying about their type +signatures, similar to Python. -There is no runtime enforcement that the method override returns a value that is compatible with the original return type, since types are erased in the Python semantics: +There is no runtime enforcement that the method override returns a +value that is compatible with the original return type, since types +are erased in the Python semantics: .. code-block:: python @@ -117,7 +140,10 @@ There is no runtime enforcement that the method override returns a value that is Abstract base classes and multiple inheritance ********************************************** -Mypy uses Python abstract base classes for protocol types. There are several built-in abstract base classes types (for example, Sequence, Iterable and Iterator). You can define abstract base classes using the abc.ABCMeta metaclass and the abc.abstractmethod function decorator. +Mypy uses Python abstract base classes for protocol types. There are +several built-in abstract base classes types (for example, Sequence, +Iterable and Iterator). You can define abstract base classes using the +abc.ABCMeta metaclass and the abc.abstractmethod function decorator. .. code-block:: python @@ -139,10 +165,15 @@ Mypy uses Python abstract base classes for protocol types. There are several bui a = A() # Error: A is abstract b = B() # OK -Unlike most Python code, abstract base classes are likely to play a significant role in many complex mypy programs. +Unlike most Python code, abstract base classes are likely to play a +significant role in many complex mypy programs. -A class can inherit any number of classes, both abstract and concrete. As with normal overrides, a dynamically typed method can implement a statically typed abstract method defined in an abstract base class. +A class can inherit any number of classes, both abstract and +concrete. As with normal overrides, a dynamically typed method can +implement a statically typed abstract method defined in an abstract +base class. .. note:: - There are also plans to support more Python-style "duck typing" in the type system. The details are still open. + There are also plans to support more Python-style "duck typing" in + the type system. The details are still open. diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index e4b6b006d246..9e3249314ea8 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -1,9 +1,13 @@ Dealing with common issues ========================== -Statically typed function bodies are often identical to normal Python code, but sometimes you need to do things slightly differently. This section introduces some of the most common cases which require different conventions in statically typed code. +Statically typed function bodies are often identical to normal Python +code, but sometimes you need to do things slightly differently. This +section introduces some of the most common cases which require +different conventions in statically typed code. -First, you need to specify the type when creating an empty list or dict and when you assign to a new variable, as mentioned earlier: +First, you need to specify the type when creating an empty list or +dict and when you assign to a new variable, as mentioned earlier: .. code-block:: python @@ -11,7 +15,8 @@ First, you need to specify the type when creating an empty list or dict and when a = [] # Fine in a dynamically typed function, or if type # of a has been declared or inferred before -Sometimes you can avoid the explicit list item type by using a list comprehension. Here a type annotation is needed: +Sometimes you can avoid the explicit list item type by using a list +comprehension. Here a type annotation is needed: .. code-block:: python @@ -21,7 +26,8 @@ Sometimes you can avoid the explicit list item type by using a list comprehensio .. note:: - A future mypy version may be able to deal with cases such as the above without type annotations. + A future mypy version may be able to deal with cases such as the + above without type annotations. No type annotation needed if using a list comprehension: @@ -29,9 +35,14 @@ No type annotation needed if using a list comprehension: l = [i * i for i in range(n)] -However, in more complex cases the explicit type annotation can improve the clarity of your code, whereas a complex list comprehension can make your code difficult to understand. +However, in more complex cases the explicit type annotation can +improve the clarity of your code, whereas a complex list comprehension +can make your code difficult to understand. -Second, each name within a function only has a single type. You can reuse for loop indices etc., but if you want to use a variable with multiple types within a single function, you may need to declare it with the Any type. +Second, each name within a function only has a single type. You can +reuse for loop indices etc., but if you want to use a variable with +multiple types within a single function, you may need to declare it +with the Any type. .. code-block:: python @@ -42,9 +53,12 @@ Second, each name within a function only has a single type. You can reuse for lo .. note:: - This is another limitation that could be lifted in a future mypy version. + This is another limitation that could be lifted in a future mypy + version. -Third, sometimes the inferred type is a subtype of the desired type. The type inference uses the first assignment to infer the type of a name: +Third, sometimes the inferred type is a subtype of the desired +type. The type inference uses the first assignment to infer the type +of a name: .. code-block:: python @@ -53,7 +67,8 @@ Third, sometimes the inferred type is a subtype of the desired type. The type in ... shape = Triangle() # Type error: Triangle is not a Circle -You can just give an explicit type for the variable in cases such the above example: +You can just give an explicit type for the variable in cases such the +above example: .. code-block:: python @@ -62,7 +77,9 @@ You can just give an explicit type for the variable in cases such the above exam ... shape = Triangle() # OK -Fourth, if you use isinstance tests or other kinds of runtime type tests, you may have to add casts (this is similar to instanceof tests in Java): +Fourth, if you use isinstance tests or other kinds of runtime type +tests, you may have to add casts (this is similar to instanceof tests +in Java): .. code-block:: python @@ -72,8 +89,21 @@ Fourth, if you use isinstance tests or other kinds of runtime type tests, you ma n += 1 # o += 1 would be an error ... -Note that the object type used in the above example is similar to Object in Java: it only supports operations defined for all objects, such as equality and isinstance(). The type Any, in contrast, supports all operations, even if they may fail at runtime. The cast above would have been unnecessary if the type of o was Any. - -Some consider casual use of isinstance tests a sign of bad programming style. Often a method override or an overloaded function is a cleaner way of implementing functionality that depends on the runtime types of values. However, use whatever techniques that work for you. Sometimes isinstance tests *are* the cleanest way of implementing a piece of functionality. - -Type inference in mypy is designed to work well in common cases, to be predictable and to let the type checker give useful error messages. More powerful type inference strategies often have complex and difficult-to-prefict failure modes and could result in very confusing error messages. +Note that the object type used in the above example is similar to +Object in Java: it only supports operations defined for all objects, +such as equality and isinstance(). The type Any, in contrast, supports +all operations, even if they may fail at runtime. The cast above would +have been unnecessary if the type of o was Any. + +Some consider casual use of isinstance tests a sign of bad programming +style. Often a method override or an overloaded function is a cleaner +way of implementing functionality that depends on the runtime types of +values. However, use whatever techniques that work for you. Sometimes +isinstance tests *are* the cleanest way of implementing a piece of +functionality. + +Type inference in mypy is designed to work well in common cases, to be +predictable and to let the type checker give useful error +messages. More powerful type inference strategies often have complex +and difficult-to-prefict failure modes and could result in very +confusing error messages. diff --git a/docs/source/dynamic_typing.rst b/docs/source/dynamic_typing.rst index e047da365c29..f417dbe5efd2 100644 --- a/docs/source/dynamic_typing.rst +++ b/docs/source/dynamic_typing.rst @@ -1,7 +1,12 @@ Dynamically typed code ====================== -As mentioned earlier, bodies of functions that don't have have an explicit return type are dynamically typed (operations are checked at runtime). Code outside functions is statically typed by default, and types of variables are inferred. This does usually the right thing, but you can also make any variable dynamically typed by defining it explicitly with the type Any: +As mentioned earlier, bodies of functions that don't have have an +explicit return type are dynamically typed (operations are checked at +runtime). Code outside functions is statically typed by default, and +types of variables are inferred. This does usually the right thing, +but you can also make any variable dynamically typed by defining it +explicitly with the type Any: .. code-block:: python @@ -12,7 +17,9 @@ As mentioned earlier, bodies of functions that don't have have an explicit retur s = 'x' # Type check error d = 'x' # OK -Alternatively, you can use the Undefined construct to define dynamically typed variables, as Any can be used anywhere any other type is valid: +Alternatively, you can use the Undefined construct to define +dynamically typed variables, as Any can be used anywhere any other +type is valid: .. code-block:: python @@ -22,8 +29,13 @@ Alternatively, you can use the Undefined construct to define dynamically typed v d = 1 # OK d = 'x' # OK -Additionally, if you don't import the typing module in a file, all code outside functions will be dynamically typed by default, and the file is not type checked at all. This mode makes it easy to include existing Python code that is not trivially compatible with static typing. +Additionally, if you don't import the typing module in a file, all +code outside functions will be dynamically typed by default, and the +file is not type checked at all. This mode makes it easy to include +existing Python code that is not trivially compatible with static +typing. .. note:: - The current mypy version type checks all modules, even those that don't import typing. This will change in a future version. + The current mypy version type checks all modules, even those that + don't import typing. This will change in a future version. diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 1953cf04fb41..5838e4204d9d 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -4,28 +4,48 @@ Frequently Asked Questions Why have both dynamic and static typing? **************************************** -Dynamic typing can be flexible, powerful, convenient and easy. But it's not always the best approach; there are good reasons why many developers choose to use staticaly typed languages. +Dynamic typing can be flexible, powerful, convenient and easy. But +it's not always the best approach; there are good reasons why many +developers choose to use staticaly typed languages. Here are some potential benefits of mypy-style static typing: -- Static typing can make programs easier to understand and maintain. Type declarations can serve as machine-checked documentation. This is important as code is typically read much more often than modified, and this is especially important for large and complex programs. +- Static typing can make programs easier to understand and + maintain. Type declarations can serve as machine-checked + documentation. This is important as code is typically read much more + often than modified, and this is especially important for large and + complex programs. -- Static typing can help you find bugs earlier and with less testing and debugging. Especially in large and complex projects this can be a major time-saver. +- Static typing can help you find bugs earlier and with less testing + and debugging. Especially in large and complex projects this can be + a major time-saver. -- Static typing can help you find difficult-to-find bugs before your code goes into production. This can improve reliability and reduce the number of security issues. +- Static typing can help you find difficult-to-find bugs before your + code goes into production. This can improve reliability and reduce + the number of security issues. -- Static typing makes it practical to build very useful development tools that can improve programming productivity or software quality, including IDEs with precise and reliable code completion, static analysis tools, etc. +- Static typing makes it practical to build very useful development + tools that can improve programming productivity or software quality, + including IDEs with precise and reliable code completion, static + analysis tools, etc. -- You can get the benefits of both dynamic and static typing in a single language. Dynamic typing can be perfect for a small project or for writing the UI of your program, for example. As your program grows, you can adapt tricky application logic to static typing to help maintenance. +- You can get the benefits of both dynamic and static typing in a + single language. Dynamic typing can be perfect for a small project + or for writing the UI of your program, for example. As your program + grows, you can adapt tricky application logic to static typing to + help maintenance. See also the `front page `_. Would my project benefit from static typing? ******************************************** -For many projects dynamic typing is perfectly fine (we think that Python is a great language). But sometimes your projects demand bigger guns, and that's when mypy may come in handy. +For many projects dynamic typing is perfectly fine (we think that +Python is a great language). But sometimes your projects demand bigger +guns, and that's when mypy may come in handy. -If some of these ring true for your projects, mypy (and static typing) may be useful: +If some of these ring true for your projects, mypy (and static typing) +may be useful: - Your project is large or complex. @@ -33,108 +53,214 @@ If some of these ring true for your projects, mypy (and static typing) may be us - Multiple developers are working on the same code. -- Running tests takes a lot of time or work (type checking may help you find errors early in development, reducing the number of testing iterations). +- Running tests takes a lot of time or work (type checking may help + you find errors early in development, reducing the number of testing + iterations). -- Some project members (devs or management) don't like dynamic typing, but others prefer dynamic typing and Python syntax. Mypy could be a solution that everybody finds easy to accept. +- Some project members (devs or management) don't like dynamic typing, + but others prefer dynamic typing and Python syntax. Mypy could be a + solution that everybody finds easy to accept. -- You want to future-proof your project even if currently none of the above really apply. +- You want to future-proof your project even if currently none of the + above really apply. Can I use mypy to type check my existing Python code? ***************************************************** -It depends. Compatibility is pretty good, but several Python features are not yet implemented. The ultimate goal is to make using mypy practical for most Python code. Code that uses complex introspection or metaprogramming may be impractical to type check, but it should still be possible to use static typing in other parts of a program. +It depends. Compatibility is pretty good, but several Python features +are not yet implemented. The ultimate goal is to make using mypy +practical for most Python code. Code that uses complex introspection +or metaprogramming may be impractical to type check, but it should +still be possible to use static typing in other parts of a program. Will static typing make my programs run faster? *********************************************** -Mypy only does static type checking and it does not improve performance. It has a minimal performance impact. In the future, there could be other tools that can compile statically typed mypy code to C modules or to efficient JVM bytecode, for example, but this outside the scope of the mypy project. It may also be possible to modify existing Python VMs to take advantage of static type information, but whether this is feasible is still unknown. This is nontrivial since the runtime types do not necessarily correspond to the static types. +Mypy only does static type checking and it does not improve +performance. It has a minimal performance impact. In the future, there +could be other tools that can compile statically typed mypy code to C +modules or to efficient JVM bytecode, for example, but this outside +the scope of the mypy project. It may also be possible to modify +existing Python VMs to take advantage of static type information, but +whether this is feasible is still unknown. This is nontrivial since +the runtime types do not necessarily correspond to the static types. All of my code is still in Python 2. What are my options? ********************************************************* -Mypy currently supports Python 3 syntax. Python 2 support is still in early stages of development. However, Python 2 support will be improving. Mypy includes a custom codec that lets you use Python 3 function annotations in Python 2 code. The codec just removes the annotations before bytecode compilation. +Mypy currently supports Python 3 syntax. Python 2 support is still in +early stages of development. However, Python 2 support will be +improving. Mypy includes a custom codec that lets you use Python 3 +function annotations in Python 2 code. The codec just removes the +annotations before bytecode compilation. Is mypy free? ************* -Yes. Mypy is free software, and it can also be used for commercial and proprietary projects. Mypy is available under the MIT license. +Yes. Mypy is free software, and it can also be used for commercial and +proprietary projects. Mypy is available under the MIT license. Why not use structural subtyping? ********************************* -Mypy primarily uses `nominal subtyping `_ instead of `structural subtyping `_. Some argue that structural subtyping is better suited for languages with duck typing such as Python. +Mypy primarily uses `nominal subtyping +`_ instead of +`structural subtyping +`_. Some argue +that structural subtyping is better suited for languages with duck +typing such as Python. Here are some reasons why mypy uses nominal subtyping: -1. It is easy to generate short and informative error messages when using a nominal type system. This is especially important when using type inference. +1. It is easy to generate short and informative error messages when + using a nominal type system. This is especially important when + using type inference. -2. Python supports basically nominal isinstance tests and they are widely used in programs. It is not clear how to support isinstance in a purely structural type system while remaining compatible with Python idioms. +2. Python supports basically nominal isinstance tests and they are + widely used in programs. It is not clear how to support isinstance + in a purely structural type system while remaining compatible with + Python idioms. -3. Many programmers are already familiar with nominal subtyping and it has been successfully used in languages such as Java, C++ and C#. Only few languages use structural subtyping. +3. Many programmers are already familiar with nominal subtyping and it + has been successfully used in languages such as Java, C++ and + C#. Only few languages use structural subtyping. -However, structural subtyping can also be useful. Structural subtyping is a likely feature to be added to mypy in the future, even though we expect that most mypy programs will still primarily use nominal subtyping. +However, structural subtyping can also be useful. Structural subtyping +is a likely feature to be added to mypy in the future, even though we +expect that most mypy programs will still primarily use nominal +subtyping. I like Python as it is. I don't need static typing. *************************************************** -That wasn't really a question, was it? Mypy is not aimed at replacing Python. The goal is to give more options for Python programmers, to make Python a more competitive alternative to other statically typed languages in large projects, to improve programmer productivity and to improve software quality. +That wasn't really a question, was it? Mypy is not aimed at replacing +Python. The goal is to give more options for Python programmers, to +make Python a more competitive alternative to other statically typed +languages in large projects, to improve programmer productivity and to +improve software quality. How are mypy programs different from normal Python? *************************************************** -Since you use a vanilla Python implementation to run mypy programs, mypy programs are also Python programs. The type checker may give warnings for some valid Python code, but the code is still always runnable. Also, some Python features and syntax are still not supported by mypy, but this is gradually improving. - -The obvious difference is the availability of static type checking. The :doc:`mypy tutorial ` mentions some modifications to Python code that may be required to make code type check without errors, such as the need to make attributes explicit and more explicit protocol representation. - -Mypy will support modular, efficient type checking, and this seems to rule out type checking some language features, such as arbitrary runtime addition of methods. However, it is likely that many of these features will be supported in a restricted form (for example, runtime modification is only supported for classes or methods registered as dynamic or 'patchable'). +Since you use a vanilla Python implementation to run mypy programs, +mypy programs are also Python programs. The type checker may give +warnings for some valid Python code, but the code is still always +runnable. Also, some Python features and syntax are still not +supported by mypy, but this is gradually improving. + +The obvious difference is the availability of static type +checking. The :doc:`mypy tutorial ` mentions some +modifications to Python code that may be required to make code type +check without errors, such as the need to make attributes explicit and +more explicit protocol representation. + +Mypy will support modular, efficient type checking, and this seems to +rule out type checking some language features, such as arbitrary +runtime addition of methods. However, it is likely that many of these +features will be supported in a restricted form (for example, runtime +modification is only supported for classes or methods registered as +dynamic or 'patchable'). How is mypy different from PyPy? ******************************** -*This answer relates to PyPy as a Python implementation. See also the answer related to RPython below.* +*This answer relates to PyPy as a Python implementation. See also the + answer related to RPython below.* -Mypy and PyPy are orthogonal. Mypy does static type checking, i.e. it is basically a linter, but static typing has no runtime effect, whereas the PyPy is an Python implementation. You can use PyPy to run mypy programs. +Mypy and PyPy are orthogonal. Mypy does static type checking, i.e. it +is basically a linter, but static typing has no runtime effect, +whereas the PyPy is an Python implementation. You can use PyPy to run +mypy programs. How is mypy different from Cython? ********************************** -`Cython `_ is a variant of Python that supports compilation to CPython C modules. It can give major speedups to certain classes of programs compared to CPython, and it provides static typing (though this is different from mypy). Mypy differs in the following aspects, among others: +`Cython `_ is a variant of Python that supports +compilation to CPython C modules. It can give major speedups to +certain classes of programs compared to CPython, and it provides +static typing (though this is different from mypy). Mypy differs in +the following aspects, among others: -- Cython is much more focused on performance than mypy. Mypy is only about static type checking, and increasing performance is not a direct goal. +- Cython is much more focused on performance than mypy. Mypy is only + about static type checking, and increasing performance is not a + direct goal. - The mypy syntax is arguably simpler and more "Pythonic" (no cdef/cpdef, etc.) for statically typed code. -- The mypy syntax is compatible with Python. Mypy programs are normal Python programs that can be run using any Python implementation. Cython has many incompatible extensions to Python syntax, and Cython programs generally cannot be run without first compiling them to CPython extension modules via C. Cython also has a pure Python mode, but it seems to support only a subset of Cython functionality, and the syntax is quite verbose. - -- Mypy has a different set of type system features. For example, mypy has genericity (parametric polymorphism), function types and bidirectional type inference, which are not supported by Cython. (Cython has fused types that are different but related to mypy generics. Mypy also has a similar feature as an extension of generics.) - -- The mypy type checker knows about the static types of many Python stdlib modules and can effectively type check code that uses them. - -- Cython supports accessing C functions directly and many features are defined in terms of translating them to C or C++. Mypy just uses Python semantics, and mypy does not deal with accessing C library functionality. +- The mypy syntax is compatible with Python. Mypy programs are normal + Python programs that can be run using any Python + implementation. Cython has many incompatible extensions to Python + syntax, and Cython programs generally cannot be run without first + compiling them to CPython extension modules via C. Cython also has a + pure Python mode, but it seems to support only a subset of Cython + functionality, and the syntax is quite verbose. + +- Mypy has a different set of type system features. For example, mypy + has genericity (parametric polymorphism), function types and + bidirectional type inference, which are not supported by + Cython. (Cython has fused types that are different but related to + mypy generics. Mypy also has a similar feature as an extension of + generics.) + +- The mypy type checker knows about the static types of many Python + stdlib modules and can effectively type check code that uses them. + +- Cython supports accessing C functions directly and many features are + defined in terms of translating them to C or C++. Mypy just uses + Python semantics, and mypy does not deal with accessing C library + functionality. How is mypy different from Nuitka? ********************************** -`Nuitka `_ is a static compiler that can translate Python programs to C++. Nuitka integrates with the CPython runtime. Nuitka has additional future goals, such as using type inference and whole-program analysis to further speed up code. Here are some differences: +`Nuitka `_ is a static compiler that can translate +Python programs to C++. Nuitka integrates with the CPython +runtime. Nuitka has additional future goals, such as using type +inference and whole-program analysis to further speed up code. Here +are some differences: -- Nuitka is primarily focused on speeding up Python code. Mypy focuses on static type checking and facilitating better tools. +- Nuitka is primarily focused on speeding up Python code. Mypy focuses + on static type checking and facilitating better tools. -- Whole-program analysis tends to be slow and scale poorly to large or complex programs. It is still unclear if Nuitka can solve these issues. Mypy does not use whole-program analysis and will support modular type checking (though this has not been implemented yet). +- Whole-program analysis tends to be slow and scale poorly to large or + complex programs. It is still unclear if Nuitka can solve these + issues. Mypy does not use whole-program analysis and will support + modular type checking (though this has not been implemented yet). How is mypy different from RPython or Shed Skin? ************************************************ -`RPython `_ and `Shed Skin `_ are basically statically typed subsets of Python. Mypy does the following important things differently: +`RPython `_ and `Shed +Skin `_ are basically statically +typed subsets of Python. Mypy does the following important things +differently: -- Mypy supports both static and dynamic typing. Dynamically typed and statically typed code can be freely mixed and can interact seamlessly. +- Mypy supports both static and dynamic typing. Dynamically typed and + statically typed code can be freely mixed and can interact + seamlessly. -- Mypy aims to support (in the future) fast and modular type checking. Both RPython and Shed Skin use whole-program type inference which is very slow, does not scale well to large programs and often produces confusing error messages. Mypy can support modularity since it only uses local type inference; static type checking depends on having type annotatations for functions signatures. +- Mypy aims to support (in the future) fast and modular type + checking. Both RPython and Shed Skin use whole-program type + inference which is very slow, does not scale well to large programs + and often produces confusing error messages. Mypy can support + modularity since it only uses local type inference; static type + checking depends on having type annotatations for functions + signatures. -- Mypy will support introspection, dynamic loading of code and many other dynamic language features (though using these may make static typing less effective). RPython and Shed Skin only support a restricted Python subset without several of these features. +- Mypy will support introspection, dynamic loading of code and many + other dynamic language features (though using these may make static + typing less effective). RPython and Shed Skin only support a + restricted Python subset without several of these features. - Mypy supports user-defined generic types. Mypy is a cool project. Can I help? *********************************** -Any help is much appreciated! `Contact `_ the developers if you would like to contribute. Any help related to development, design, publicity, documentation, testing, web site maintenance, financing, etc. can be helpful. You can learn a lot by contributing, and anybody can help, even beginners! However, some knowledge of compilers and/or type systems is essential if you want to work on mypy internals. +Any help is much appreciated! `Contact +`_ the developers if you would +like to contribute. Any help related to development, design, +publicity, documentation, testing, web site maintenance, financing, +etc. can be helpful. You can learn a lot by contributing, and anybody +can help, even beginners! However, some knowledge of compilers and/or +type systems is essential if you want to work on mypy internals. diff --git a/docs/source/function_overloading.rst b/docs/source/function_overloading.rst index 8e6ac8b18154..eb5e5a8d5350 100644 --- a/docs/source/function_overloading.rst +++ b/docs/source/function_overloading.rst @@ -1,7 +1,10 @@ Function overloading ==================== -You can define multiple instances of a function with the same name but different signatures. The first matching signature is selected at runtime when evaluating each individual call. This enables also a form of multiple dispatch. +You can define multiple instances of a function with the same name but +different signatures. The first matching signature is selected at +runtime when evaluating each individual call. This enables also a form +of multiple dispatch. .. code-block:: python @@ -18,7 +21,8 @@ You can define multiple instances of a function with the same name but different abs(-2) # 2 (int) abs(-1.5) # 1.5 (float) -Overloaded function variants still define a single runtime object; the following code is valid: +Overloaded function variants still define a single runtime object; the +following code is valid: .. code-block:: python @@ -26,8 +30,13 @@ Overloaded function variants still define a single runtime object; the following my_abs(-2) # 2 (int) my_abs(-1.5) # 1.5 (float) -The overload variants must be adjacent in the code. This makes code clearer, and otherwise there would be awkward corner cases such as partially defined overloaded functions that could surprise the unwary programmer. +The overload variants must be adjacent in the code. This makes code +clearer, and otherwise there would be awkward corner cases such as +partially defined overloaded functions that could surprise the unwary +programmer. .. note:: - As generic type variables are erased at runtime, an overloaded function cannot dispatch based on a generic type argument, e.g. List[int] versus List[str]. + As generic type variables are erased at runtime, an overloaded + function cannot dispatch based on a generic type argument, + e.g. List[int] versus List[str]. diff --git a/docs/source/generics.rst b/docs/source/generics.rst index f356a289aac6..319df5064f83 100644 --- a/docs/source/generics.rst +++ b/docs/source/generics.rst @@ -4,9 +4,13 @@ Generics Defining generic classes ************************ -The built-in collection classes are generic classes. Generic types have one or more type parameters, which can be arbitrary types. For example, Dict]int, str] has the type parameters int and str, and List[int] has a type parameter int. +The built-in collection classes are generic classes. Generic types +have one or more type parameters, which can be arbitrary types. For +example, Dict]int, str] has the type parameters int and str, and +List[int] has a type parameter int. -Programs can also define new generic classes. Here is a very simple generic class that represents a stack: +Programs can also define new generic classes. Here is a very simple +generic class that represents a stack: .. code-block:: python @@ -27,7 +31,8 @@ Programs can also define new generic classes. Here is a very simple generic clas def empty(self) -> bool: return not self.items -The Stack class can be used to represent a stack of any type: Stack[int], Stack[Tuple[int, str]], etc. +The Stack class can be used to represent a stack of any type: +Stack[int], Stack[Tuple[int, str]], etc. Using Stack is similar to built-in container types: @@ -49,27 +54,42 @@ Type inference works for user-defined generic types as well: Generic class internals *********************** -You may wonder what happens at runtime when you index Stack. Actually, indexing Stack just returns Stack: +You may wonder what happens at runtime when you index Stack. Actually, +indexing Stack just returns Stack: >>> print(Stack) >>> print(Stack[int]) -Note that built-in types list, dict and so on do not support indexing in Python. This is why we have the aliases List, Dict and so on in the typing module. Indexing these aliases just gives you the target class in Python, similar to Stack: +Note that built-in types list, dict and so on do not support indexing +in Python. This is why we have the aliases List, Dict and so on in the +typing module. Indexing these aliases just gives you the target class +in Python, similar to Stack: >>> from typing import List >>> List[int] -The above examples illustrate that type variables are erased at runtime when running in a Python VM. Generic Stack or list instances are just ordinary Python objects, and they have no extra runtime overhead or magic due to being generic, other than a metaclass that overloads the indexing operator. If you worry about the overhead introduced by the type indexing operation when constructing instances, you can often rewrite such code using a # type annotation, which has no runtime impact: +The above examples illustrate that type variables are erased at +runtime when running in a Python VM. Generic Stack or list instances +are just ordinary Python objects, and they have no extra runtime +overhead or magic due to being generic, other than a metaclass that +overloads the indexing operator. If you worry about the overhead +introduced by the type indexing operation when constructing instances, +you can often rewrite such code using a # type annotation, which has +no runtime impact: .. code-block:: python x = List[int]() x = [] # type: List[int] # Like the above but faster. -The savings are rarely significant, but it could make a difference in a performance-critical loop or function. Function annotations, on the other hand, are only evaluated during the defintion of the function, not during every call. Constructing type objects in function signatures rarely has any noticeable performance impact. +The savings are rarely significant, but it could make a difference in +a performance-critical loop or function. Function annotations, on the +other hand, are only evaluated during the defintion of the function, +not during every call. Constructing type objects in function +signatures rarely has any noticeable performance impact. .. _generic-functions: @@ -87,7 +107,9 @@ Generic type variables can also be used to define generic functions: def first(seq: Sequence[T]) -> T: # Generic function return seq[0] -As with generic classes, the type variable can be replaced with any type. That means first can we used with any sequence type, and the return type is derived from the sequence item type. For example: +As with generic classes, the type variable can be replaced with any +type. That means first can we used with any sequence type, and the +return type is derived from the sequence item type. For example: .. code-block:: python @@ -96,7 +118,9 @@ As with generic classes, the type variable can be replaced with any type. That m s = first('foo') # s has type str. n = first([1, 2, 3]) # n has type int. -Note also that a single definition of a type variable (such as T above) can be used in multiple generic functions or classes. In this example we use the same type variable in two generic functions: +Note also that a single definition of a type variable (such as T +above) can be used in multiple generic functions or classes. In this +example we use the same type variable in two generic functions: .. code-block:: python @@ -110,4 +134,5 @@ Note also that a single definition of a type variable (such as T above) can be u def last(seq: Sequence[T]) -> T: return seq[-1] -You can also define generic methods — just use a type variable in the method signature that is different from class type variables. +You can also define generic methods — just use a type variable in the +method signature that is different from class type variables. diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 10cfe697b7ca..d4400f7552a9 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -4,7 +4,9 @@ Kinds of types User-defined types ****************** -Each class is also a type. Any instance of a subclass is also compatible with all superclasses. All values are compatible with the object type (and also the Any type). +Each class is also a type. Any instance of a subclass is also +compatible with all superclasses. All values are compatible with the +object type (and also the Any type). .. code-block:: python @@ -25,9 +27,16 @@ Each class is also a type. Any instance of a subclass is also compatible with al The Any type ************ -A value with the Any type is dynamically typed. Any operations are permitted on the value, and the operations are checked at runtime, similar to normal Python code. If you do not define a function return value or argument types, these default to Any. Also, a function without an explicit return type is dynamically typed. The body of a dynamically typed function is not checked statically. +A value with the Any type is dynamically typed. Any operations are +permitted on the value, and the operations are checked at runtime, +similar to normal Python code. If you do not define a function return +value or argument types, these default to Any. Also, a function +without an explicit return type is dynamically typed. The body of a +dynamically typed function is not checked statically. -Any is compatible with every other type, and vice versa. No implicit type check is inserted when assigning a value of type Any to a variable with a more precise type: +Any is compatible with every other type, and vice versa. No implicit +type check is inserted when assigning a value of type Any to a +variable with a more precise type: .. code-block:: python @@ -35,7 +44,9 @@ Any is compatible with every other type, and vice versa. No implicit type check a = 2 # OK s = a # OK -Declared (and inferred) types are erased at runtime (they are basically treated as comments), and thus the above code does not generate a runtime error. +Declared (and inferred) types are erased at runtime (they are +basically treated as comments), and thus the above code does not +generate a runtime error. Tuple types *********** @@ -51,7 +62,9 @@ The type Tuple[t, ...] represents a tuple with the item types t, ...: Callable types (and lambdas) **************************** -You can pass around function objects and bound methods in statically typed code. The type of a function that accepts arguments A1, ..., An and returns Rt is Function[[A1, ..., An], Rt]. Example: +You can pass around function objects and bound methods in statically +typed code. The type of a function that accepts arguments A1, ..., An +and returns Rt is Function[[A1, ..., An], Rt]. Example: .. code-block:: python @@ -63,13 +76,16 @@ You can pass around function objects and bound methods in statically typed code. print(twice(3, add)) # 5 -Lambdas are also supported. The lambda argument and return value types cannot be given explicitly; they are always inferred based on context using bidirectional type inference: +Lambdas are also supported. The lambda argument and return value types +cannot be given explicitly; they are always inferred based on context +using bidirectional type inference: .. code-block:: python l = map(lambda x: x + 1, [1, 2, 3]) # infer x as int and l as List[int] -If you want to give the argument or return value types explicitly, use an ordinary, perhaps nested function definition. +If you want to give the argument or return value types explicitly, use +an ordinary, perhaps nested function definition. .. _union-types: @@ -105,7 +121,8 @@ specific type: Class name forward references ***************************** -Python does not allow references to a class object before the class is defined. Thus this code is does not work as expected: +Python does not allow references to a class object before the class is +defined. Thus this code is does not work as expected: .. code-block:: python @@ -115,7 +132,8 @@ Python does not allow references to a class object before the class is defined. class A: ... -In cases like these you can enter the type as a string literal — this is a *forward reference*: +In cases like these you can enter the type as a string literal — this +is a *forward reference*: .. code-block:: python @@ -125,9 +143,12 @@ In cases like these you can enter the type as a string literal — this is a *fo class A: ... -Of course, instead of using a string literal type, you could move the function definition after the class definition. This is not always desirable or even possible, though. +Of course, instead of using a string literal type, you could move the +function definition after the class definition. This is not always +desirable or even possible, though. -Any type can be entered as a string literal, and youn can combine string-literal types with non-string-literal types freely: +Any type can be entered as a string literal, and youn can combine +string-literal types with non-string-literal types freely: .. code-block:: python diff --git a/docs/source/planned_features.rst b/docs/source/planned_features.rst index 6c96cc2839c2..b298dd744693 100644 --- a/docs/source/planned_features.rst +++ b/docs/source/planned_features.rst @@ -1,12 +1,17 @@ Planned features ================ -This section introduces some language features that are still work in progress. +This section introduces some language features that are still work in +progress. None ---- -Currently, None is a valid value for each type, similar to null or NULL in many languages. However, it is likely that this decision will be reversed, and types do not include None default. The Optional type modifier can be used to define a type variant that includes None, such as Optional(int): +Currently, None is a valid value for each type, similar to null or +NULL in many languages. However, it is likely that this decision will +be reversed, and types do not include None default. The Optional type +modifier can be used to define a type variant that includes None, such +as Optional(int): .. code-block:: python @@ -24,7 +29,8 @@ Also, most operations would not be supported on None values: def f(x: Optional[int]) -> int: return x + 1 # Error: Cannot add None and int -Instead, an explicit None check would be required. This would benefit from more powerful type inference: +Instead, an explicit None check would be required. This would benefit +from more powerful type inference: .. code-block:: python @@ -35,12 +41,15 @@ Instead, an explicit None check would be required. This would benefit from more # The inferred type of x is just int here. return x + 1 -We would infer the type of x to be int in the else block due to the check against None in the if condition. +We would infer the type of x to be int in the else block due to the +check against None in the if condition. More general type inference --------------------------- -It may be useful to support type inference also for variables defined in multiple locations in an if/else statement, even if the initializer types are different: +It may be useful to support type inference also for variables defined +in multiple locations in an if/else statement, even if the initializer +types are different: .. code-block:: python @@ -49,4 +58,6 @@ It may be useful to support type inference also for variables defined in multipl else: y = 'a' # Second definition of y -In the above example, both of the assignments would be used in type inference, and the type of y would be str. However, it is not obvious whether this would be generally desirable in more complex cases. +In the above example, both of the assignments would be used in type +inference, and the type of y would be str. However, it is not obvious +whether this would be generally desirable in more complex cases. diff --git a/docs/source/supported_python_features.rst b/docs/source/supported_python_features.rst index c16e5aceee60..fc7cba38460f 100644 --- a/docs/source/supported_python_features.rst +++ b/docs/source/supported_python_features.rst @@ -1,7 +1,8 @@ Supported Python features and modules ===================================== -Lists of supported Python features and standard library modules are maintained in the mypy wiki: +Lists of supported Python features and standard library modules are +maintained in the mypy wiki: - `Supported Python features `_ - `Supported Python modules `_ @@ -9,4 +10,13 @@ Lists of supported Python features and standard library modules are maintained i Runtime definition of methods and functions ******************************************* -By default, mypy will not let you redefine functions or methods, and you can't add functions to a class or module outside its definition -- but only if this is visible to the type checker. This only affects static checking, as mypy performs no additional type checking at runtime. You can easily work around this. For example, you can use dynamically typed code or values with Any types, or you can use setattr or other introspection features. However, you need to be careful if you decide to do this. If used indiscriminately, you may have difficulty using static typing effectively, since the type checker cannot see functions defined at runtime. +By default, mypy will not let you redefine functions or methods, and +you can't add functions to a class or module outside its definition -- +but only if this is visible to the type checker. This only affects +static checking, as mypy performs no additional type checking at +runtime. You can easily work around this. For example, you can use +dynamically typed code or values with Any types, or you can use +setattr or other introspection features. However, you need to be +careful if you decide to do this. If used indiscriminately, you may +have difficulty using static typing effectively, since the type +checker cannot see functions defined at runtime. diff --git a/docs/source/type_inference_and_annotations.rst b/docs/source/type_inference_and_annotations.rst index 734a73feaddf..d9f234fdb9b5 100644 --- a/docs/source/type_inference_and_annotations.rst +++ b/docs/source/type_inference_and_annotations.rst @@ -4,30 +4,39 @@ Type inference and type annotations Type inference ************** -The initial assignment defines a variable. If you do not explicitly specify the type of the variable, mypy infers the type based on the static type of the value expression: +The initial assignment defines a variable. If you do not explicitly +specify the type of the variable, mypy infers the type based on the +static type of the value expression: .. code-block:: python i = 1 # Infer type int for i l = [1, 2] # Infer type List[int] for l -Type inference is bidirectional and takes context into account. For example, the following is valid: +Type inference is bidirectional and takes context into account. For +example, the following is valid: .. code-block:: python def f(l: List[object]) -> None: l = [1, 2] # Infer type List[object] for [1, 2] -In an assignment, the type context is determined by the assignment target. In this case this is l, which has the type List[object]. The value expression [1, 2] is type checked in this context and given the type List[object]. In the previous example we introduced a new variable l, and here the type context was empty. +In an assignment, the type context is determined by the assignment +target. In this case this is l, which has the type List[object]. The +value expression [1, 2] is type checked in this context and given the +type List[object]. In the previous example we introduced a new +variable l, and here the type context was empty. -Note that the following is not valid, since List[int] is not compatible with List[object]: +Note that the following is not valid, since List[int] is not +compatible with List[object]: .. code-block:: python def f(l: List[object], k: List[int]) -> None: l = k # Type check error: incompatible types in assignment -The reason why the above assignment is disallowed is that allowing the assignment could result in non-int values stored in a list of int: +The reason why the above assignment is disallowed is that allowing the +assignment could result in non-int values stored in a list of int: .. code-block:: python @@ -36,14 +45,25 @@ The reason why the above assignment is disallowed is that allowing the assignmen l.append('x') print(k[-1]) # Ouch; a string in List[int] -You can still run the above program; it prints x. This illustrates the fact that static types are used during type checking, but they do not affect the runtime behavior of programs. You can run programs with type check failures, which is often very handy when performing a large refactoring. Thus you can always 'work around' the type system, and it doesn't really limit what you can do in your program. +You can still run the above program; it prints x. This illustrates the +fact that static types are used during type checking, but they do not +affect the runtime behavior of programs. You can run programs with +type check failures, which is often very handy when performing a large +refactoring. Thus you can always 'work around' the type system, and it +doesn't really limit what you can do in your program. -Type inference is not used in dynamically typed functions (those without an explicit return type) — every local variable type defaults to Any, which is discussed below. +Type inference is not used in dynamically typed functions (those +without an explicit return type) — every local variable type defaults +to Any, which is discussed below. Explicit types for collections ****************************** -The type checker cannot always infer the type of a list or a dictionary. This often arises when creating an empty list or dictionary and assigning it to a new variable without an explicit variable type. In these cases you can give the type explicitly using the type name as a constructor: +The type checker cannot always infer the type of a list or a +dictionary. This often arises when creating an empty list or +dictionary and assigning it to a new variable without an explicit +variable type. In these cases you can give the type explicitly using +the type name as a constructor: .. code-block:: python @@ -65,7 +85,8 @@ Explicit types for variables s = 'x' # OK s = 1 # Type check error -The Undefined call evaluates to a special "Undefined" object that raises an exception on any operation: +The Undefined call evaluates to a special "Undefined" object that +raises an exception on any operation: .. code-block:: python @@ -73,31 +94,40 @@ The Undefined call evaluates to a special "Undefined" object that raises an exce if s: # Runtime error: undefined value print('hello') -You can also override the inferred type of a variable by using a special comment after an assignment statement: +You can also override the inferred type of a variable by using a +special comment after an assignment statement: .. code-block:: python x = [] # type: List[int] -Here the # type comment applies both to the assignment target, in this case x, and also the initializer expression, via context. The above code is equivalent to this: +Here the # type comment applies both to the assignment target, in this +case x, and also the initializer expression, via context. The above +code is equivalent to this: .. code-block:: python x = List[int]() -The type checker infers the value of a variable from the initializer, and if it is an empty collection such as [], the type is not well-defined. You can declare the collection type using one of the above syntax alternatives. +The type checker infers the value of a variable from the initializer, +and if it is an empty collection such as [], the type is not +well-defined. You can declare the collection type using one of the +above syntax alternatives. Declaring multiple variable types on a line ******************************************* -You can declare more than a single variable at a time. In order to nicely work with multiple assignment, you must give each variable a type separately: +You can declare more than a single variable at a time. In order to +nicely work with multiple assignment, you must give each variable a +type separately: .. code-block:: python n, s = Undefined(int), Undefined(str) # Declare an integer and a string i, found = 0, False # type: int, bool -When using the latter form, you can optinally use parentheses around the types, assignment targets and assigned expression: +When using the latter form, you can optinally use parentheses around +the types, assignment targets and assigned expression: .. code-block:: python From f8d73046c59b080b5fa7b7ec3e2aa80a83df8435 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 15:41:58 -0700 Subject: [PATCH 115/144] Use ``...`` to format code within normal text --- docs/source/additional_features.rst | 2 +- docs/source/basics.rst | 18 ++++----- docs/source/builtin_types.rst | 24 ++++++------ docs/source/casts.rst | 12 ++++-- docs/source/class_basics.rst | 25 ++++++------ docs/source/common_issues.rst | 31 +++++++-------- docs/source/conf.py | 2 + docs/source/dynamic_typing.rst | 10 ++--- docs/source/faq.rst | 3 +- docs/source/function_overloading.rst | 2 +- docs/source/generics.rst | 38 +++++++++---------- docs/source/kinds_of_types.rst | 28 +++++++------- docs/source/planned_features.rst | 25 ++++++------ docs/source/supported_python_features.rst | 4 +- .../source/type_inference_and_annotations.rst | 35 ++++++++--------- 15 files changed, 134 insertions(+), 125 deletions(-) diff --git a/docs/source/additional_features.rst b/docs/source/additional_features.rst index 110514528026..b9dd07f9bc3c 100644 --- a/docs/source/additional_features.rst +++ b/docs/source/additional_features.rst @@ -6,4 +6,4 @@ including the following: - inheritance between generic classes - compatibility and subtyping of generic types, including covariance of generic types -- super() +- ``super()`` diff --git a/docs/source/basics.rst b/docs/source/basics.rst index 6146ee4a781f..35b0d9318852 100644 --- a/docs/source/basics.rst +++ b/docs/source/basics.rst @@ -23,8 +23,8 @@ reports type errors within the function): def greeting(name: str) -> str: return 'Hello, {}'.format(name) -A None return type indicates a function that does not explicitly -return a value. Using a None result in a statically typed context +A ``None`` return type indicates a function that does not explicitly +return a value. Using a ``None`` result in a statically typed context results in a type check error: .. code-block:: python @@ -38,7 +38,7 @@ The typing module ***************** We cheated a bit in the above examples: a module is type checked only -if it imports the module typing. Here is a complete statically typed +if it imports the module ``typing``. Here is a complete statically typed example from the previous section: .. code-block:: python @@ -48,9 +48,9 @@ example from the previous section: def greeting(name: str) -> str: return 'Hello, {}'.format(name) -The typing module contains many definitions that are useful in -statically typed code. You can also use from ... import to import them -(we'll explain Iterable later in this document): +The ``typing`` module contains many definitions that are useful in +statically typed code. You can also use ``from ... import`` to import +them (we'll explain Iterable later in this document): .. code-block:: python @@ -60,11 +60,11 @@ statically typed code. You can also use from ... import to import them for name in names: print('Hello, {}'.format(name)) -For brevity, we often omit the typing import in code examples, but you -should always include it in modules that contain statically typed +For brevity, we often omit the ``typing`` import in code examples, but +you should always include it in modules that contain statically typed code. -You can still have dynamically typed functions in modules that import typing: +You can still have dynamically typed functions in modules that import ``typing``: .. code-block:: python diff --git a/docs/source/builtin_types.rst b/docs/source/builtin_types.rst index 74b58d55af7e..4587e3f2e6df 100644 --- a/docs/source/builtin_types.rst +++ b/docs/source/builtin_types.rst @@ -17,18 +17,18 @@ These are examples of some of the most common built-in types: Sequence[bool] # sequence of booleans Any # dynamically typed value -The type Any and type constructors List, Dict, Iterable and Sequence -are defined in the typing module. +The type ``Any`` and type constructors ``List``, ``Dict``, +``Iterable`` and ``Sequence`` are defined in the ``typing`` module. -The type Dict is a *generic* class, signified by type arguments within -[...]. For example, Dict[int, str] is a dictionary from integers to -strings and and Dict[Any, Any] is a dictionary of dynamically typed -(arbitrary) values and keys. List is another generic class. Dict and -List are aliases for the built-ins dict and list, respectively. +The type ``Dict`` is a *generic* class, signified by type arguments within +``[...]``. For example, ``Dict[int, str]`` is a dictionary from integers to +strings and and ``Dict[Any, Any]`` is a dictionary of dynamically typed +(arbitrary) values and keys. ``List`` is another generic class. ``Dict`` and +``List`` are aliases for the built-ins ``dict`` and ``list``, respectively. -Iterable and Sequence are generic abstract base classes that -correspond to Python protocols. For example, a str object is valid -when Iterable[str] or Sequence[str] is expected. Note that even though -they are similar to abstract base classes defined in abc.collections -(formerly collections), they are not identical, since the built-in +``Iterable`` and ``Sequence`` are generic abstract base classes that +correspond to Python protocols. For example, a ``str`` object is valid +when ``Iterable[str]`` or ``Sequence[str]`` is expected. Note that even though +they are similar to abstract base classes defined in ``abc.collections`` +(formerly ``collections``), they are not identical, since the built-in collection type objects do not support indexing. diff --git a/docs/source/casts.rst b/docs/source/casts.rst index ad6548159649..efd8c412959b 100644 --- a/docs/source/casts.rst +++ b/docs/source/casts.rst @@ -5,7 +5,7 @@ Mypy supports type casts that are usually used to coerce a statically typed value to a subtype. Unlike languages such as Java or C#, however, mypy casts are only used as hints for the type checker when using Python semantics, and they have no runtime effect. Use the -function cast to perform a cast: +function ``cast`` to perform a cast: .. code-block:: python @@ -23,10 +23,14 @@ casts being checked at runtime. Use an assertion if you want to perform an actual runtime check. Casts are used to silence spurious type checker warnings. -You don't need a cast for expressions with type Any, of when assigning -to a variable with type Any, as was explained earlier. +You don't need a cast for expressions with type ``Any``, of when +assigning to a variable with type ``Any``, as was explained earlier. -You can cast to a dynamically typed value by just calling Any: +Any() as a cast +*************** + +You can cast to a dynamically typed value by just calling ``Any``; +this is equivalent to ``cast(Any, ...)`` but shorter: .. code-block:: python diff --git a/docs/source/class_basics.rst b/docs/source/class_basics.rst index b50e05b70968..8b23d89e10a1 100644 --- a/docs/source/class_basics.rst +++ b/docs/source/class_basics.rst @@ -19,10 +19,10 @@ initialized within the class. Mypy infers the types of attributes: a.x = 2 # OK a.y = 3 # Error: A has no attribute y -This is a bit like each class having an implicitly defined __slots__ -attribute. In Python semantics this is only enforced during type -checking: at runtime we use standard Python semantics. You can -selectively define a class as *dynamic*; dynamic classes have +This is a bit like each class having an implicitly defined +``__slots__`` attribute. In Python semantics this is only enforced +during type checking: at runtime we use standard Python semantics. You +can selectively define a class as *dynamic*; dynamic classes have Python-like compile-time semantics, and they allow you to assign to arbitrary attributes anywhere in a program without the type checker complaining: @@ -45,8 +45,8 @@ use dynamic classes when you really need them. Dynamic classes are not implemented in the current mypy version. -You can declare variables in the class body explicitly using Undefined -or a type comment: +You can declare variables in the class body explicitly using +``Undefined`` or a type comment: .. code-block:: python @@ -73,7 +73,7 @@ in a method: self.y = 0 # type: Any # OK You can only define an instance variable within a method if you assign -to it explicitly using self: +to it explicitly using ``self``: .. code-block:: python @@ -110,8 +110,8 @@ override has a compatible signature: .. note:: You can also vary return types **covariantly** in overriding. For - example, you could override the return type 'object' with a subtype - such as 'int'. + example, you could override the return type ``object`` with a subtype + such as ``int``. You can also override a statically typed method with a dynamically typed one. This allows dynamically typed code to override methods @@ -141,9 +141,10 @@ Abstract base classes and multiple inheritance ********************************************** Mypy uses Python abstract base classes for protocol types. There are -several built-in abstract base classes types (for example, Sequence, -Iterable and Iterator). You can define abstract base classes using the -abc.ABCMeta metaclass and the abc.abstractmethod function decorator. +several built-in abstract base classes types (for example, +``Sequence``, ``Iterable`` and ``Iterator``). You can define abstract +base classes using the ``abc.ABCMeta`` metaclass and the +``abc.abstractmethod`` function decorator. .. code-block:: python diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 9e3249314ea8..318163850fd2 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -42,7 +42,7 @@ can make your code difficult to understand. Second, each name within a function only has a single type. You can reuse for loop indices etc., but if you want to use a variable with multiple types within a single function, you may need to declare it -with the Any type. +with the ``Any`` type. .. code-block:: python @@ -77,8 +77,8 @@ above example: ... shape = Triangle() # OK -Fourth, if you use isinstance tests or other kinds of runtime type -tests, you may have to add casts (this is similar to instanceof tests +Fourth, if you use ``isinstance()`` tests or other kinds of runtime type +tests, you may have to add casts (this is similar to ``instanceof`` tests in Java): .. code-block:: python @@ -89,18 +89,19 @@ in Java): n += 1 # o += 1 would be an error ... -Note that the object type used in the above example is similar to -Object in Java: it only supports operations defined for all objects, -such as equality and isinstance(). The type Any, in contrast, supports -all operations, even if they may fail at runtime. The cast above would -have been unnecessary if the type of o was Any. - -Some consider casual use of isinstance tests a sign of bad programming -style. Often a method override or an overloaded function is a cleaner -way of implementing functionality that depends on the runtime types of -values. However, use whatever techniques that work for you. Sometimes -isinstance tests *are* the cleanest way of implementing a piece of -functionality. +Note that the ``object`` type used in the above example is similar to +``Object`` in Java: it only supports operations defined for all +objects, such as equality and ``isinstance()``. The type ``Any``, in +contrast, supports all operations, even if they may fail at +runtime. The cast above would have been unnecessary if the type of +``o`` was ``Any``. + +Some consider casual use of ``isinstance()`` tests a sign of bad +programming style. Often a method override or an overloaded function +is a cleaner way of implementing functionality that depends on the +runtime types of values. However, use whatever techniques that work +for you. Sometimes isinstance tests *are* the cleanest way of +implementing a piece of functionality. Type inference in mypy is designed to work well in common cases, to be predictable and to let the type checker give useful error diff --git a/docs/source/conf.py b/docs/source/conf.py index 6d160cce044b..49cd56403c9f 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -262,3 +262,5 @@ # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False + +rst_prolog = '.. |...| unicode:: U+2026 .. ellipsis\n' diff --git a/docs/source/dynamic_typing.rst b/docs/source/dynamic_typing.rst index f417dbe5efd2..8d754f511ef6 100644 --- a/docs/source/dynamic_typing.rst +++ b/docs/source/dynamic_typing.rst @@ -6,7 +6,7 @@ explicit return type are dynamically typed (operations are checked at runtime). Code outside functions is statically typed by default, and types of variables are inferred. This does usually the right thing, but you can also make any variable dynamically typed by defining it -explicitly with the type Any: +explicitly with the type ``Any``: .. code-block:: python @@ -17,8 +17,8 @@ explicitly with the type Any: s = 'x' # Type check error d = 'x' # OK -Alternatively, you can use the Undefined construct to define -dynamically typed variables, as Any can be used anywhere any other +Alternatively, you can use the ``Undefined`` construct to define +dynamically typed variables, as ``Any`` can be used anywhere any other type is valid: .. code-block:: python @@ -29,7 +29,7 @@ type is valid: d = 1 # OK d = 'x' # OK -Additionally, if you don't import the typing module in a file, all +Additionally, if you don't import the ``typing`` module in a file, all code outside functions will be dynamically typed by default, and the file is not type checked at all. This mode makes it easy to include existing Python code that is not trivially compatible with static @@ -38,4 +38,4 @@ typing. .. note:: The current mypy version type checks all modules, even those that - don't import typing. This will change in a future version. + don't import ``typing``. This will change in a future version. diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 5838e4204d9d..5ca68626fbe5 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -164,8 +164,7 @@ dynamic or 'patchable'). How is mypy different from PyPy? ******************************** -*This answer relates to PyPy as a Python implementation. See also the - answer related to RPython below.* +*This answer relates to PyPy as a Python implementation. See also the answer related to RPython below.* Mypy and PyPy are orthogonal. Mypy does static type checking, i.e. it is basically a linter, but static typing has no runtime effect, diff --git a/docs/source/function_overloading.rst b/docs/source/function_overloading.rst index eb5e5a8d5350..e47ed7e8d4ba 100644 --- a/docs/source/function_overloading.rst +++ b/docs/source/function_overloading.rst @@ -39,4 +39,4 @@ programmer. As generic type variables are erased at runtime, an overloaded function cannot dispatch based on a generic type argument, - e.g. List[int] versus List[str]. + e.g. ``List[int]`` versus ``List[str]``. diff --git a/docs/source/generics.rst b/docs/source/generics.rst index 319df5064f83..f740ae25c270 100644 --- a/docs/source/generics.rst +++ b/docs/source/generics.rst @@ -6,8 +6,8 @@ Defining generic classes The built-in collection classes are generic classes. Generic types have one or more type parameters, which can be arbitrary types. For -example, Dict]int, str] has the type parameters int and str, and -List[int] has a type parameter int. +example, ``Dict[int, str]`` has the type parameters ``int`` and +``str``, and ``List[int]`` has a type parameter ``int``. Programs can also define new generic classes. Here is a very simple generic class that represents a stack: @@ -31,10 +31,10 @@ generic class that represents a stack: def empty(self) -> bool: return not self.items -The Stack class can be used to represent a stack of any type: -Stack[int], Stack[Tuple[int, str]], etc. +The ``Stack`` class can be used to represent a stack of any type: +``Stack[int]``, ``Stack[Tuple[int, str]]``, etc. -Using Stack is similar to built-in container types: +Using ``Stack`` is similar to built-in container types: .. code-block:: python @@ -54,31 +54,31 @@ Type inference works for user-defined generic types as well: Generic class internals *********************** -You may wonder what happens at runtime when you index Stack. Actually, -indexing Stack just returns Stack: +You may wonder what happens at runtime when you index +``Stack``. Actually, indexing ``Stack`` just returns ``Stack``: >>> print(Stack) >>> print(Stack[int]) -Note that built-in types list, dict and so on do not support indexing -in Python. This is why we have the aliases List, Dict and so on in the -typing module. Indexing these aliases just gives you the target class -in Python, similar to Stack: +Note that built-in types ``list``, ``dict`` and so on do not support +indexing in Python. This is why we have the aliases ``List``, ``Dict`` +and so on in the ``typing`` module. Indexing these aliases just gives +you the target class in Python, similar to ``Stack``: >>> from typing import List >>> List[int] The above examples illustrate that type variables are erased at -runtime when running in a Python VM. Generic Stack or list instances -are just ordinary Python objects, and they have no extra runtime -overhead or magic due to being generic, other than a metaclass that -overloads the indexing operator. If you worry about the overhead -introduced by the type indexing operation when constructing instances, -you can often rewrite such code using a # type annotation, which has -no runtime impact: +runtime. Generic ``Stack`` or ``list`` instances are just ordinary +Python objects, and they have no extra runtime overhead or magic due +to being generic, other than a metaclass that overloads the indexing +operator. If you worry about the overhead introduced by the type +indexing operation when constructing instances, you can usually +rewrite such code using a ``# type:`` annotation, which has no runtime +impact: .. code-block:: python @@ -118,7 +118,7 @@ return type is derived from the sequence item type. For example: s = first('foo') # s has type str. n = first([1, 2, 3]) # n has type int. -Note also that a single definition of a type variable (such as T +Note also that a single definition of a type variable (such as ``T`` above) can be used in multiple generic functions or classes. In this example we use the same type variable in two generic functions: diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index d4400f7552a9..692055e5e5ef 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -6,7 +6,7 @@ User-defined types Each class is also a type. Any instance of a subclass is also compatible with all superclasses. All values are compatible with the -object type (and also the Any type). +``object`` type (and also the ``Any`` type). .. code-block:: python @@ -27,16 +27,16 @@ object type (and also the Any type). The Any type ************ -A value with the Any type is dynamically typed. Any operations are +A value with the ``Any`` type is dynamically typed. Any operations are permitted on the value, and the operations are checked at runtime, similar to normal Python code. If you do not define a function return -value or argument types, these default to Any. Also, a function +value or argument types, these default to ``Any``. Also, a function without an explicit return type is dynamically typed. The body of a dynamically typed function is not checked statically. -Any is compatible with every other type, and vice versa. No implicit -type check is inserted when assigning a value of type Any to a -variable with a more precise type: +``Any`` is compatible with every other type, and vice versa. No +implicit type check is inserted when assigning a value of type ``Any`` +to a variable with a more precise type: .. code-block:: python @@ -51,7 +51,7 @@ generate a runtime error. Tuple types *********** -The type Tuple[t, ...] represents a tuple with the item types t, ...: +The type ``Tuple[T1, ..., Tn]`` represents a tuple with the item types ``T1``, |...|, ``Tn``: .. code-block:: python @@ -63,8 +63,8 @@ Callable types (and lambdas) **************************** You can pass around function objects and bound methods in statically -typed code. The type of a function that accepts arguments A1, ..., An -and returns Rt is Function[[A1, ..., An], Rt]. Example: +typed code. The type of a function that accepts arguments ``A1``, |...|, ``An`` +and returns ``Rt`` is ``Function[[A1, ..., An], Rt]``. Example: .. code-block:: python @@ -96,10 +96,10 @@ Python functions often accept values of two or more different types. You can use overloading to model this in statically typed code, but union types can make code like this easier to write. -Use the Union[...] type constructor to construct a union type. For -example, the type Union[int, str] is compatible with both integers and -strings. You can use an isinstance check to narrow down the type to a -specific type: +Use the ``Union[T1, ..., Tn]`` type constructor to construct a union +type. For example, the type ``Union[int, str]`` is compatible with +both integers and strings. You can use an ``isinstance()`` check to +narrow down the type to a specific type: .. code-block:: python @@ -157,4 +157,4 @@ string-literal types with non-string-literal types freely: class A: pass -String literal types are never needed in # type comments. +String literal types are never needed in ``# type:`` comments. diff --git a/docs/source/planned_features.rst b/docs/source/planned_features.rst index b298dd744693..ae7927ce9b41 100644 --- a/docs/source/planned_features.rst +++ b/docs/source/planned_features.rst @@ -7,11 +7,11 @@ progress. None ---- -Currently, None is a valid value for each type, similar to null or -NULL in many languages. However, it is likely that this decision will -be reversed, and types do not include None default. The Optional type -modifier can be used to define a type variant that includes None, such -as Optional(int): +Currently, ``None`` is a valid value for each type, similar to +``null`` or ``NULL`` in many languages. However, it is likely that +this decision will be reversed, and types do not include ``None`` +default. The ``Optional`` type modifier would be used to define a type +variant that includes ``None``, such as ``Optional[int]``: .. code-block:: python @@ -22,15 +22,15 @@ as Optional(int): ... return None # Error: None not compatible with int -Also, most operations would not be supported on None values: +Also, most operations would not be supported on ``None`` values: .. code-block:: python def f(x: Optional[int]) -> int: return x + 1 # Error: Cannot add None and int -Instead, an explicit None check would be required. This would benefit -from more powerful type inference: +Instead, an explicit ``None`` check would be required. This would +benefit from more powerful type inference: .. code-block:: python @@ -41,8 +41,8 @@ from more powerful type inference: # The inferred type of x is just int here. return x + 1 -We would infer the type of x to be int in the else block due to the -check against None in the if condition. +We would infer the type of ``x`` to be int in the else block due to the +check against ``None`` in the if condition. More general type inference --------------------------- @@ -59,5 +59,6 @@ types are different: y = 'a' # Second definition of y In the above example, both of the assignments would be used in type -inference, and the type of y would be str. However, it is not obvious -whether this would be generally desirable in more complex cases. +inference, and the type of ``y`` would be ``str``. However, it is not +obvious whether this would be generally desirable in more complex +cases. diff --git a/docs/source/supported_python_features.rst b/docs/source/supported_python_features.rst index fc7cba38460f..068dd058881a 100644 --- a/docs/source/supported_python_features.rst +++ b/docs/source/supported_python_features.rst @@ -15,8 +15,8 @@ you can't add functions to a class or module outside its definition -- but only if this is visible to the type checker. This only affects static checking, as mypy performs no additional type checking at runtime. You can easily work around this. For example, you can use -dynamically typed code or values with Any types, or you can use -setattr or other introspection features. However, you need to be +dynamically typed code or values with ``Any`` types, or you can use +``setattr`` or other introspection features. However, you need to be careful if you decide to do this. If used indiscriminately, you may have difficulty using static typing effectively, since the type checker cannot see functions defined at runtime. diff --git a/docs/source/type_inference_and_annotations.rst b/docs/source/type_inference_and_annotations.rst index d9f234fdb9b5..b367b97196bc 100644 --- a/docs/source/type_inference_and_annotations.rst +++ b/docs/source/type_inference_and_annotations.rst @@ -22,13 +22,14 @@ example, the following is valid: l = [1, 2] # Infer type List[object] for [1, 2] In an assignment, the type context is determined by the assignment -target. In this case this is l, which has the type List[object]. The -value expression [1, 2] is type checked in this context and given the -type List[object]. In the previous example we introduced a new -variable l, and here the type context was empty. +target. In this case this is ``l``, which has the type +``List[object]``. The value expression ``[1, 2]`` is type checked in +this context and given the type ``List[object]``. In the previous +example we introduced a new variable ``l``, and here the type context +was empty. -Note that the following is not valid, since List[int] is not -compatible with List[object]: +Note that the following is not valid, since ``List[int]`` is not +compatible with ``List[object]``: .. code-block:: python @@ -36,7 +37,7 @@ compatible with List[object]: l = k # Type check error: incompatible types in assignment The reason why the above assignment is disallowed is that allowing the -assignment could result in non-int values stored in a list of int: +assignment could result in non-int values stored in a list of ``int``: .. code-block:: python @@ -45,16 +46,16 @@ assignment could result in non-int values stored in a list of int: l.append('x') print(k[-1]) # Ouch; a string in List[int] -You can still run the above program; it prints x. This illustrates the -fact that static types are used during type checking, but they do not -affect the runtime behavior of programs. You can run programs with +You can still run the above program; it prints ``x``. This illustrates +the fact that static types are used during type checking, but they do +not affect the runtime behavior of programs. You can run programs with type check failures, which is often very handy when performing a large refactoring. Thus you can always 'work around' the type system, and it doesn't really limit what you can do in your program. Type inference is not used in dynamically typed functions (those without an explicit return type) — every local variable type defaults -to Any, which is discussed below. +to ``Any``, which is discussed later. Explicit types for collections ****************************** @@ -85,7 +86,7 @@ Explicit types for variables s = 'x' # OK s = 1 # Type check error -The Undefined call evaluates to a special "Undefined" object that +The Undefined call evaluates to a special ``Undefined`` object that raises an exception on any operation: .. code-block:: python @@ -101,20 +102,20 @@ special comment after an assignment statement: x = [] # type: List[int] -Here the # type comment applies both to the assignment target, in this -case x, and also the initializer expression, via context. The above -code is equivalent to this: +Here the ``# type:`` comment applies both to the assignment target, in +this case ``x``, and also the initializer expression, via context. The +above code is equivalent to this: .. code-block:: python x = List[int]() The type checker infers the value of a variable from the initializer, -and if it is an empty collection such as [], the type is not +and if it is an empty collection such as ``[]``, the type is not well-defined. You can declare the collection type using one of the above syntax alternatives. -Declaring multiple variable types on a line +Declaring multiple variable types at a time ******************************************* You can declare more than a single variable at a time. In order to From 96e154dda13241336d77ecce57b2365fb61a0261 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 15:43:36 -0700 Subject: [PATCH 116/144] Minor update to faq --- docs/source/faq.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 5ca68626fbe5..8872f0ca849c 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -234,6 +234,9 @@ Skin `_ are basically statically typed subsets of Python. Mypy does the following important things differently: +- RPython is primarily designed for implementing virtual machines; + mypy is a general-purpose language. + - Mypy supports both static and dynamic typing. Dynamically typed and statically typed code can be freely mixed and can interact seamlessly. From a8348cd91dbbf90dedbae6659ce917213b644335 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 15:55:11 -0700 Subject: [PATCH 117/144] Improvements to built-in type docs --- docs/source/builtin_types.rst | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/docs/source/builtin_types.rst b/docs/source/builtin_types.rst index 4587e3f2e6df..4426df73868c 100644 --- a/docs/source/builtin_types.rst +++ b/docs/source/builtin_types.rst @@ -3,19 +3,21 @@ Built-in types These are examples of some of the most common built-in types: -.. code-block:: python - - int # integer objects of arbitrary size - float # floating point number - bool # boolean value - str # unicode string - bytes # 8-bit string - object # the common base class - List[str] # list of str objects - Dict[str, int] # dictionary from str to int - Iterable[int] # iterable object containing ints - Sequence[bool] # sequence of booleans - Any # dynamically typed value +=================== =============================== +Type Description +=================== =============================== +``int`` integer of arbitrary size +``float`` floating point number +``bool`` boolean value +``str`` unicode string +``bytes`` 8-bit string +``object`` an arbitrary object (``object`` is the common base class) +``List[str]`` list of ``str`` objects +``Dict[str, int]`` dictionary from ``str`` keys to ``int`` values +``Iterable[int]`` iterable object containing ints +``Sequence[bool]`` sequence of booleans +``Any`` dynamically typed value with an arbitrary type +=================== =============================== The type ``Any`` and type constructors ``List``, ``Dict``, ``Iterable`` and ``Sequence`` are defined in the ``typing`` module. @@ -27,7 +29,8 @@ strings and and ``Dict[Any, Any]`` is a dictionary of dynamically typed ``List`` are aliases for the built-ins ``dict`` and ``list``, respectively. ``Iterable`` and ``Sequence`` are generic abstract base classes that -correspond to Python protocols. For example, a ``str`` object is valid +correspond to Python protocols. For example, a ``str`` object or a +``List[str]`` object is valid when ``Iterable[str]`` or ``Sequence[str]`` is expected. Note that even though they are similar to abstract base classes defined in ``abc.collections`` (formerly ``collections``), they are not identical, since the built-in From 6850b7ff2f834e3b5fa1f5abb8aa9964603d8662 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 26 Oct 2014 16:02:53 -0700 Subject: [PATCH 118/144] Minor documentation changes --- docs/source/basics.rst | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/source/basics.rst b/docs/source/basics.rst index 35b0d9318852..a00e61cef41f 100644 --- a/docs/source/basics.rst +++ b/docs/source/basics.rst @@ -6,19 +6,20 @@ Function signatures A function without a type signature is dynamically typed. You can declare the signature of a function using the Python 3 annotation -syntax This makes the function statically typed (the type checker -reports type errors within the function): +syntax. This makes the function statically typed (the type checker +reports type errors within the function). A function without a +type annotation is dynamically typed, and identical to ordinary +Python: .. code-block:: python - # Dynamically typed (identical to Python) - def greeting(name): return 'Hello, {}'.format(name) -.. code-block:: python +This version of the above function is statically typed (but it's still +valid Python): - # Statically typed (still valid Python) +.. code-block:: python def greeting(name: str) -> str: return 'Hello, {}'.format(name) @@ -50,7 +51,7 @@ example from the previous section: The ``typing`` module contains many definitions that are useful in statically typed code. You can also use ``from ... import`` to import -them (we'll explain Iterable later in this document): +them (we'll explain ``Iterable`` later in this document): .. code-block:: python @@ -88,13 +89,13 @@ more stable. Currently the type checker checks the top levels and annotated functions of all modules, even those that don't import - typing. However, you should not rely on this, as this will change + ``typing``. However, you should not rely on this, as this will change in the future. Type checking and running programs ********************************** -You can type check a program by using the mypy tool, which is +You can type check a program by using the ``mypy`` tool, which is basically a linter — it checks you program for errors without actually running it:: From b6081dda7f82d84c601a2100d93d56ef33f76b93 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 13:15:38 -0800 Subject: [PATCH 119/144] Update common issues documentation --- docs/source/common_issues.rst | 99 +++++++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 22 deletions(-) diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 318163850fd2..92c0ed456095 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -6,14 +6,32 @@ code, but sometimes you need to do things slightly differently. This section introduces some of the most common cases which require different conventions in statically typed code. -First, you need to specify the type when creating an empty list or -dict and when you assign to a new variable, as mentioned earlier: +Types of empty collections +-------------------------- + +You need to specify the type when you assign an empty list or +dict to a new variable, as mentioned earlier: .. code-block:: python a = List[int]() # Explicit type required in statically typed code - a = [] # Fine in a dynamically typed function, or if type - # of a has been declared or inferred before + +Alternatively, you can use a type annotation in a comment: + +.. code-block:: python + + a = [] # type: List[int] + +Without the annotation the type checker has no way of figuring out the +precise type of ``a``. + +You can use a simple empty list literal in a dynamically typed function (as the +type of ``a`` would be implicitly ``Any`` and need not be inferred), or if type +of the variable has been declared or inferred before: + +.. code-block:: python + + a = [] # Okay if type of a known Sometimes you can avoid the explicit list item type by using a list comprehension. Here a type annotation is needed: @@ -39,7 +57,10 @@ However, in more complex cases the explicit type annotation can improve the clarity of your code, whereas a complex list comprehension can make your code difficult to understand. -Second, each name within a function only has a single type. You can +Redefinitions with incompatible types +------------------------------------- + +Each name within a function only has a single 'declared' type. You can reuse for loop indices etc., but if you want to use a variable with multiple types within a single function, you may need to declare it with the ``Any`` type. @@ -49,20 +70,35 @@ with the ``Any`` type. def f() -> None: n = 1 ... - n = x # Type error: n has type int + n = 'x' # Type error: n has type int .. note:: This is another limitation that could be lifted in a future mypy version. -Third, sometimes the inferred type is a subtype of the desired +Note that you can redefine a variable with a more *precise* or a more +concrete type. For example, you can redefine a sequence (which does +not support ``sort()``) as a list and sort it in-place: + +.. code-block:: python + + def f(x: Sequence[int]) -> None: + # Type of x is Sequence[int] here; we don't know the concrete type. + x = list(x) + # Type of x is List[int] here. + x.sort() # Okay! + +Declaring a supertype as variable type +-------------------------------------- + +Sometimes the inferred type is a subtype (subclass) of the desired type. The type inference uses the first assignment to infer the type -of a name: +of a name (assume here that ``Shape`` is the base class of both +``Circle`` and ``Triangle``): .. code-block:: python - # Assume Shape is the base class of both Circle and Triangle. shape = Circle() # Infer shape to be Circle ... shape = Triangle() # Type error: Triangle is not a Circle @@ -77,34 +113,53 @@ above example: ... shape = Triangle() # OK -Fourth, if you use ``isinstance()`` tests or other kinds of runtime type +Complex isinstance tests +------------------------ + +If you use ``isinstance()`` tests or other kinds of runtime type tests, you may have to add casts (this is similar to ``instanceof`` tests in Java): .. code-block:: python - def f(o: object) -> None: - if isinstance(o, int): + def f(o: object, x: int) -> None: + if isinstance(o, int) and x > 1: n = cast(int, o) - n += 1 # o += 1 would be an error + g(n + 1) # o + 1 would be an error ... -Note that the ``object`` type used in the above example is similar to -``Object`` in Java: it only supports operations defined for all -objects, such as equality and ``isinstance()``. The type ``Any``, in -contrast, supports all operations, even if they may fail at -runtime. The cast above would have been unnecessary if the type of -``o`` was ``Any``. +.. note:: + + Note that the ``object`` type used in the above example is similar + to ``Object`` in Java: it only supports operations defined for *all* + objects, such as equality and ``isinstance()``. The type ``Any``, + in contrast, supports all operations, even if they may fail at + runtime. The cast above would have been unnecessary if the type of + ``o`` was ``Any``. + +Mypy can't infer the type of ``o`` after the ``isinstance()`` check +because of the ``and`` operator (this limitation will likely be lifted +in the future). We can write the above code without a cast by using a +nested if statemet: + +.. code-block:: python + + def f(o: object, x: int) -> None: + if isinstance(o, int): # Mypy understands a lone isinstance check + if x > 1: + g(o + 1) # Okay; type of o is inferred as int here + ... Some consider casual use of ``isinstance()`` tests a sign of bad -programming style. Often a method override or an overloaded function +programming style. Often a method override or a ``hasattr`` check is a cleaner way of implementing functionality that depends on the runtime types of values. However, use whatever techniques that work -for you. Sometimes isinstance tests *are* the cleanest way of +for you. Sometimes ``isinstance`` tests *are* the cleanest way of implementing a piece of functionality. Type inference in mypy is designed to work well in common cases, to be predictable and to let the type checker give useful error messages. More powerful type inference strategies often have complex and difficult-to-prefict failure modes and could result in very -confusing error messages. +confusing error messages. The tradeoff is that you as a programmer +sometimes have to give the type checker a little help. From 3cbca1f145f329cc17a9b4fd8c1411ea9d62706b Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 13:21:34 -0800 Subject: [PATCH 120/144] Some FAQ fixes --- docs/source/common_issues.rst | 2 ++ docs/source/faq.rst | 14 ++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index 92c0ed456095..5a6ed4786ad5 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -1,3 +1,5 @@ +.. _common_issues: + Dealing with common issues ========================== diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 8872f0ca849c..f73ecedfd1ed 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -35,7 +35,8 @@ Here are some potential benefits of mypy-style static typing: grows, you can adapt tricky application logic to static typing to help maintenance. -See also the `front page `_. +See also the `front page `_ of the mypy web +site. Would my project benefit from static typing? ******************************************** @@ -130,8 +131,8 @@ is a likely feature to be added to mypy in the future, even though we expect that most mypy programs will still primarily use nominal subtyping. -I like Python as it is. I don't need static typing. -*************************************************** +I like Python and I have no need for static typing +************************************************** That wasn't really a question, was it? Mypy is not aimed at replacing Python. The goal is to give more options for Python programmers, to @@ -149,10 +150,11 @@ runnable. Also, some Python features and syntax are still not supported by mypy, but this is gradually improving. The obvious difference is the availability of static type -checking. The :doc:`mypy tutorial ` mentions some +checking. The section :ref:`common_issues` mentions some modifications to Python code that may be required to make code type -check without errors, such as the need to make attributes explicit and -more explicit protocol representation. +check without errors. Also, your code must make attributes explicit and +use a explicit protocol representation. For example, you may want to +subclass an Abstract Base Class such as ``typing.Iterable``. Mypy will support modular, efficient type checking, and this seems to rule out type checking some language features, such as arbitrary From 6b3250d0ef159ef0f756425f542f18781bd0052e Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 13:36:53 -0800 Subject: [PATCH 121/144] Add introduction to library stubs --- docs/source/basics.rst | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/docs/source/basics.rst b/docs/source/basics.rst index a00e61cef41f..40f628743378 100644 --- a/docs/source/basics.rst +++ b/docs/source/basics.rst @@ -117,3 +117,50 @@ explains how to download and install mypy. Depending on how mypy is configured, you may have to explicitly use the Python interpreter to run mypy. The mypy tool is an ordinary mypy (and so also Python) program. + +Library stubs +************* + +In order to type check code that uses library modules such those +included in the Python standard library, you need to have library +*stubs*. A library stub defines a skeleton of the public interface +of the library, including classes, variables and functions, and +their types. + +For example, consider this code: + +.. code-block:: python + + x = chr(4) + +Without a library stub, the type checker has no way of inferring the +type of ``x`` and checking that the argument to ``chr`` has a valid +type. Mypy comes with a library stub for Python builtins that contains +a definition like this for ``chr``: + +.. code-block:: python + + def chr(code: int) -> str: pass + +Mypy complains if it can't find a stub for a library module that you +import. You can create a stub easily; here is an overview: + +* Write a stub file for the library and store it as a ``.py`` file in + a directory reserved for stubs (e.g., ``myproject/stubs``). +* Set the environment variable ``MYPYPATH`` to refer to the above directory. + For example:: + + $ export MYPYPATH=~/work/myproject/stubs + +Use the normal Python file name conventions for modules, e.g. ``csv.py`` +for module ``csv``, and use a subdirectory with ``__init__.py`` for packages. + +That's it! Now you can access the module in mypy programs and type check +code that uses the library. If you write a stub for a library module, +consider making it available for other programmers that use mypy or +contributing it to mypy. + +There is more information about creating stubs in the +`mypy wiki `_. +The following sections explain the kinds of type annotations you can use +in your programs and stub files. From fda3747bc3ea346f029300c62bcee12ab69064e1 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 14:05:00 -0800 Subject: [PATCH 122/144] Add short introduction --- docs/source/index.rst | 1 + docs/source/introduction.rst | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 docs/source/introduction.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index a5f86c689ddd..dfb7798ac897 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,6 +9,7 @@ Welcome to Mypy documentation! .. toctree:: :maxdepth: 2 + introduction basics builtin_types type_inference_and_annotations diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst new file mode 100644 index 000000000000..1b4f7132a058 --- /dev/null +++ b/docs/source/introduction.rst @@ -0,0 +1,22 @@ +Introduction +============ + +Mypy is a static type checker for Python. If you sprinkle your code +with type annotations using the Python 3 function annotation syntax, +mypy can type check you code and find common bugs. Mypy is a static +analyzer, or a lint-like tool: type annotations are just hints and are +not enforced when running you program. You run your program with a +standard Python interpreter, and the annotations are treated basically +as comments. + +Mypy has a powerful but easy-to-use type system with modern features +such as type inference, generics, function types, tuple types and +union types. You can also always escape to dynamic typing -- mypy's +take on static typing doesn't really restrict what you can do in your +programs, but it will make your programs easier to debug, maintain and +understand. + +This document is a short introduction to mypy. It will get you started +writing statically typed code. Knowledge of Python 3.x and some kind +of a statically typed object-oriented language such as Java are +assumed. From 5495021830a093603ec7a753dbda27f6d4518a19 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 14:11:36 -0800 Subject: [PATCH 123/144] Add introduction to the chapter Basics --- docs/source/basics.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/basics.rst b/docs/source/basics.rst index 40f628743378..e460ac26bea9 100644 --- a/docs/source/basics.rst +++ b/docs/source/basics.rst @@ -1,6 +1,10 @@ Basics ====== +This chapter introduces some core concepts of mypy, including function +annotations, the ``typing`` module and library stubs. Read it carefully, +as the rest of documentation may not make much sense otherwise. + Function signatures ******************* From 87e8d5794a87f99ecacb8ee2ce0b2d59da4be023 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 14:26:29 -0800 Subject: [PATCH 124/144] Add more discussion of dynamic typing --- docs/source/casts.rst | 2 ++ docs/source/dynamic_typing.rst | 61 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/docs/source/casts.rst b/docs/source/casts.rst index efd8c412959b..525829012265 100644 --- a/docs/source/casts.rst +++ b/docs/source/casts.rst @@ -1,3 +1,5 @@ +.. _casts: + Casts ===== diff --git a/docs/source/dynamic_typing.rst b/docs/source/dynamic_typing.rst index 8d754f511ef6..538d0ab37eda 100644 --- a/docs/source/dynamic_typing.rst +++ b/docs/source/dynamic_typing.rst @@ -39,3 +39,64 @@ typing. The current mypy version type checks all modules, even those that don't import ``typing``. This will change in a future version. + +Operations on Any values +------------------------ + +You can do anything using a value with type ``Any``, and type checker +does not complain: + +.. code-block:: python + + def f(x: Any) -> int: + # All of these are valid! + x.foobar(1, y=2) + print(x[3] + 'f') + if x: + x.z = x(2) + open(x).read() + return x + +Values derived from an ``Any`` value also have the value ``Any`` +implicitly. For example, if you get the attribute of an ``Any`` +value or call a ``Any`` value the result is ``Any``: + +.. code-block:: python + + def f(x: Any) -> None: + y = x.foo() # y has type Any + y.bar() # Okay as well! + +Any vs. object +-------------- + +The type ``object`` is another type that can have an instance of arbitrary +type as a value. Unlike ``Any``, ``object`` is an ordinary static type (it +is similar to ``Object`` in Java), and only operations valid for *all* +types are accepted for ``object`` values. These are all valid: + +.. code-block:: python + + def f(o: object) -> None: + if o: + print(o) + print(isinstance(o, int)) + o = 2 + o = 'foo' + +These are, however, flagged as errors, since not all objects support these +operations: + +.. code-block:: python + + def f(o: object) -> None: + o.foo() # Error! + o + 2 # Error! + open(o) # Error! + n = Undefined(int) + n = o # Error! + +You can use ``cast()`` (see chapter :ref:`casts`) to go from a general +type such as ``object`` to a more specific type (subtype) such as +``int``. ``cast()`` is not needed with dynamically typed values +(values with type ``Any``). From ead13b44c5a656a4c52e4dbc1fb7829eb3b7f8ec Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 14:46:50 -0800 Subject: [PATCH 125/144] Discuss typevar value in the documentation --- docs/source/generics.rst | 89 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/docs/source/generics.rst b/docs/source/generics.rst index f740ae25c270..83940c3c792b 100644 --- a/docs/source/generics.rst +++ b/docs/source/generics.rst @@ -136,3 +136,92 @@ example we use the same type variable in two generic functions: You can also define generic methods — just use a type variable in the method signature that is different from class type variables. + +Type variables with value restriction +------------------------------------- + +By default, a type variable can be replaced with any type. However, sometimes +it's useful to have a type variable that can only have some specific types +as its value. A typical example is a type variable that can only have values +``str`` and ``bytes``: + +.. code-block:: python + + from typing import typevar + + AnyStr = typevar('AnyStr', values=(str, bytes)) + +This is actually such a common type variable that ``AnyStr`` is +defined in ``typing`` and we don't need to define it ourselves. + +We can use ``AnyStr`` to define a function that can concatenate +two strings or bytes objects, but it can't be called with other +argument types: + +.. code-block:: python + + from typing import AnyStr + + def concat(x: AnyStr, y: AnyStr) -> AnyStr: + return x + y + + concat('a', 'b') # Okay + concat(b'a', b'b') # Okay + concat(1, 2) # Error! + +Note that this is different from a union type, since combinations +of ``str`` and ``bytes`` are not accepted: + +.. code-block:: python + + concat('string', b'bytes') # Error! + +In this case, this is exactly what we want, since it's not possible +to concatenate a string and a bytes object! The type checker +will reject this function: + +.. code-block:: python + + def union_concat(x: Union[str, bytes], y: Union[str, bytes]) -> Union[str, bytes]: + return x + y # Error: can't concatenate str and bytes + +The original, valid definition of ``concat`` is more or less +equivalent to this overloaded function, but it's much shorter, +cleaner and more efficient: + +.. code-block:: python + + @overload + def overload_concat(x: str, y: str) -> str: + return x + y + + @overload + def overload_concat(x: bytes, y: bytes) -> bytes: + return x + y + +Another interesting special case is calling ``concat()`` with a +subtype of ``str``: + +.. code-block:: python + + class S(str): pass + + ss = concat(S('foo'), S('bar'))) + +You may expect that the type of ``ss`` is ``S``, but the type is +actually ``str``: a subtype gets promoted to one of the valid values +for the type variable, which in this case is ``str``. This is thus +subtly different from *bounded quantification* in languages such as +Java, where the return type would be ``S``. The way mypy implements +this is correct for ``concat``, since ``concat`` actually returns a +``str`` instance in the above example: + +.. code-block:: python + + >>> prin(type(ss)) + + +You can also use a ``typevar`` with ``values`` when defining a generic +class. For example, mypy uses the type ``typing.Pattern[AnyStr]`` for the +return value of ``re.compile``, since regular expressions can be based +on a string or a bytes pattern. From 78fa0d087f1881f0a86077067574162eab5acbcb Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 15:04:43 -0800 Subject: [PATCH 126/144] Add documentation on ducktype decorator --- docs/source/duck_type_compatibility.rst | 56 +++++++++++++++++++++++++ docs/source/index.rst | 1 + 2 files changed, 57 insertions(+) create mode 100644 docs/source/duck_type_compatibility.rst diff --git a/docs/source/duck_type_compatibility.rst b/docs/source/duck_type_compatibility.rst new file mode 100644 index 000000000000..b49aa3c536c1 --- /dev/null +++ b/docs/source/duck_type_compatibility.rst @@ -0,0 +1,56 @@ +Duck type compatibility +----------------------- + +In Python, certain types are compatible even though they aren't subclasses of +each other. For example, ``int`` objects are valid whenever ``float`` objects +are expected. Mypy supports this idiom via *duck type compatibility*. You can +specify a type to be a valid substitute for another type using the ``ducktype`` +class decorator: + +.. code-block:: python + + from typing import ducktype + + @ducktype(str) + class MyString: + def __init__(self, ...): ... + ... + +Now mypy considers a ``MyString`` instance to be valid whenever a +``str`` object is expected, independent of whether ``MyString`` +actually is a perfect substitute for strings. You can think of this as +a class-level cast as opposed to a value-level cast. This is a powerful +feature but you can easily abuse it and make it easy to write programs +that pass type checking but will crash and burn when run! + +The most common case where ``ducktype`` is useful is for certain +well-known standard library classes: + +* ``int`` is duck type compatible with ``float`` +* ``float`` is duck type compatible with ``complex``. + +Thus code like this is nice and clean and also behaves as expected: + +.. code-block:: python + + def degrees_to_radians(x: float) -> float: + return math.pi * degrees / 180 + + n = 90 # Inferred type 'int' + print(degrees_to_radians(n)) # Okay! + +Also, in Python 2 ``str`` would be duck type compatible with ``unicode``. + +.. note:: + + Note that in Python 2 a ``str`` object with non-ASCII characters is + often *not valid* when a unicode string is expected. The mypy type + system does not consider a string with non-ASCII values as a + separate type so some programs with this kind of error will + silently pass type checking. In Python 3 ``str`` and ``bytes`` are + separate, unrelated types and this kind of error is easy to + detect. This a good reason for preferring Python 3 over Python 2! + +.. note:: + + Mypy support for Python 2 is still work in progress. diff --git a/docs/source/index.rst b/docs/source/index.rst index dfb7798ac897..342dc7daa970 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -18,6 +18,7 @@ Welcome to Mypy documentation! dynamic_typing function_overloading casts + duck_type_compatibility common_issues generics supported_python_features From 9d2a594fa8516d2720a88318bf4790fafcdc6280 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sun, 2 Nov 2014 15:07:57 -0800 Subject: [PATCH 127/144] Minor documentation tweaks --- docs/source/generics.rst | 2 +- docs/source/planned_features.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/generics.rst b/docs/source/generics.rst index 83940c3c792b..3b3543615fde 100644 --- a/docs/source/generics.rst +++ b/docs/source/generics.rst @@ -138,7 +138,7 @@ You can also define generic methods — just use a type variable in the method signature that is different from class type variables. Type variables with value restriction -------------------------------------- +************************************* By default, a type variable can be replaced with any type. However, sometimes it's useful to have a type variable that can only have some specific types diff --git a/docs/source/planned_features.rst b/docs/source/planned_features.rst index ae7927ce9b41..5a4c59d547d0 100644 --- a/docs/source/planned_features.rst +++ b/docs/source/planned_features.rst @@ -4,8 +4,8 @@ Planned features This section introduces some language features that are still work in progress. -None ----- +Type checking of None +--------------------- Currently, ``None`` is a valid value for each type, similar to ``null`` or ``NULL`` in many languages. However, it is likely that From 58006c59e72fe5a9f762c735e52fe26712b69858 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 11:20:52 -0800 Subject: [PATCH 128/144] Fix overload resolution with ducktype compatibility Ignore ducktype compatibility (e.g. int being compatible with float) when determining which overload variant to pick, since ducktype declarations are not honored by isinstance(). Fixes #410. --- mypy/checkexpr.py | 16 ++++++++++------ mypy/subtypes.py | 3 +++ mypy/test/data/check-overloading.test | 20 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0b512aea065d..0c3615adbe96 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -26,7 +26,7 @@ from mypy.infer import infer_type_arguments, infer_function_type_arguments from mypy import join from mypy.expandtype import expand_type, expand_caller_var_args -from mypy.subtypes import is_subtype +from mypy.subtypes import is_subtype, is_more_precise from mypy import applytype from mypy import erasetype from mypy.checkmember import analyse_member_access, type_object_type @@ -602,15 +602,19 @@ def matches_signature_erased(self, arg_types: List[Type], is_var_arg: bool, # Fixed function arguments. func_fixed = callee.max_fixed_args() for i in range(min(len(arg_types), func_fixed)): - if not is_subtype(self.erase(arg_types[i]), - self.erase( - callee.arg_types[i])): + # Use is_more_precise rather than is_subtype because it ignores ducktype + # declarations. This is important since ducktype declarations are ignored + # when performing runtime type checking. + if not is_more_precise(self.erase(arg_types[i]), + self.erase( + callee.arg_types[i])): return False # Function varargs. if callee.is_var_arg: for i in range(func_fixed, len(arg_types)): - if not is_subtype(self.erase(arg_types[i]), - self.erase(callee.arg_types[func_fixed])): + # See above for why we use is_more_precise. + if not is_more_precise(self.erase(arg_types[i]), + self.erase(callee.arg_types[func_fixed])): return False return True diff --git a/mypy/subtypes.py b/mypy/subtypes.py index b94c3ffea732..24daf54b2832 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -287,5 +287,8 @@ def is_more_precise(t: Type, s: Type) -> bool: if isinstance(s, AnyType): return True if isinstance(s, Instance): + if isinstance(t, Callable): + # Fall back to subclass check and ignore other properties of the callable. + return is_proper_subtype(t.fallback, s) return is_proper_subtype(t, s) return sametypes.is_same_type(t, s) diff --git a/mypy/test/data/check-overloading.test b/mypy/test/data/check-overloading.test index 66f4e0a7915d..24af07a4f61d 100644 --- a/mypy/test/data/check-overloading.test +++ b/mypy/test/data/check-overloading.test @@ -476,3 +476,23 @@ main: In class "B": main, line 12: Signature of "f" incompatible with supertype "A" main: In class "C": main, line 17: Signature of "f" incompatible with supertype "A" + +[case testOverloadingAndDucktypeCompatibility] +from typing import overload, ducktype, builtinclass + +@builtinclass +class A: pass + +@builtinclass +@ducktype(A) +class B: pass + +@overload +def f(n: B) -> B: + return n +@overload +def f(n: A) -> A: + return n + +f(B()) + 'x' # E: Unsupported left operand type for + ("B") +f(A()) + 'x' # E: Unsupported left operand type for + ("A") From 0ab7e9fbdf328d6f365311cd3ff31011dcbaeadb Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 14:20:34 -0800 Subject: [PATCH 129/144] Update docstring --- mypy/checkexpr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0c3615adbe96..bb568fc1cc8a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -365,12 +365,12 @@ def infer_function_type_arguments_pass2( context: Context) -> Tuple[Callable, List[Type]]: """Perform second pass of generic function type argument inference. - The second pass is needed for arguments with types such as func, - where both s and t are type variables, when the actual argument is a - lambda with inferred types. The idea is to infer the type variable t + The second pass is needed for arguments with types such as Function[[T], S], + where both T and S are type variables, when the actual argument is a + lambda with inferred types. The idea is to infer the type variable T in the first pass (based on the types of other arguments). This lets us infer the argument and return type of the lambda expression and - thus also the type variable s in this second pass. + thus also the type variable S in this second pass. Return (the callee with type vars applied, inferred actual arg types). """ From 9ab39abd4e4aaae87b761e013ee1611bd31e0c29 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 14:36:29 -0800 Subject: [PATCH 130/144] Fix overload resolution --- mypy/checkexpr.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index bb568fc1cc8a..d414ca10f539 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -605,16 +605,14 @@ def matches_signature_erased(self, arg_types: List[Type], is_var_arg: bool, # Use is_more_precise rather than is_subtype because it ignores ducktype # declarations. This is important since ducktype declarations are ignored # when performing runtime type checking. - if not is_more_precise(self.erase(arg_types[i]), - self.erase( - callee.arg_types[i])): + if not is_compatible_overload_arg(arg_types[i], callee.arg_types[i]): return False # Function varargs. if callee.is_var_arg: for i in range(func_fixed, len(arg_types)): # See above for why we use is_more_precise. - if not is_more_precise(self.erase(arg_types[i]), - self.erase(callee.arg_types[func_fixed])): + if not is_compatible_overload_arg(arg_types[i], + callee.arg_types[func_fixed]): return False return True @@ -1250,10 +1248,6 @@ def unwrap_list(self, a: List[Node]) -> List[Node]: r.append(self.strip_parens(n)) return r - def erase(self, type: Type) -> Type: - """Replace type variable types in type with Any.""" - return erasetype.erase_type(type) - def is_valid_argc(nargs: int, is_var_arg: bool, callable: Callable) -> bool: """Return a boolean indicating whether a call expression has a @@ -1401,3 +1395,8 @@ def __init__(self) -> None: def visit_erased_type(self, t: ErasedType) -> bool: return True + + +def is_compatible_overload_arg(actual: Type, formal: Type) -> bool: + actual = erasetype.erase_type(actual) + return isinstance(actual, AnyType) or is_more_precise(actual, erasetype.erase_type(formal)) From d4ef62044fbdf5db8cba4c91e36be98911ed5e69 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 14:44:32 -0800 Subject: [PATCH 131/144] Fix travis.sh to run Python evaluation tests Ouch, we haven't been running these for a while. --- travis.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/travis.sh b/travis.sh index aeaf08132181..2104cbe54670 100644 --- a/travis.sh +++ b/travis.sh @@ -24,6 +24,7 @@ echo tests.py for t in mypy.test.testpythoneval; do echo $t "$PYTHON" "$DRIVER" -m $t || fail + "$PYTHON" -m $t || fail done # Stub checks From a25325ac255015b756530adc4f804a8f3f84efa3 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 14:45:11 -0800 Subject: [PATCH 132/144] Fix overload resolution bug --- mypy/subtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 24daf54b2832..61b6b57e9251 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -284,7 +284,7 @@ def is_more_precise(t: Type, s: Type) -> bool: type as s, t is more precise than s. """ # TODO Should List[int] be more precise than List[Any]? - if isinstance(s, AnyType): + if isinstance(s, AnyType) or isinstance(t, NoneTyp): return True if isinstance(s, Instance): if isinstance(t, Callable): From 0826da22a5068302569b021be8c368f8f8c06fb4 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 15:07:24 -0800 Subject: [PATCH 133/144] Fixes to overload resolution Still not quite fixed yet. --- mypy/checkexpr.py | 17 +++++++++++++++-- mypy/subtypes.py | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index d414ca10f539..7bc990f6f69c 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1398,5 +1398,18 @@ def visit_erased_type(self, t: ErasedType) -> bool: def is_compatible_overload_arg(actual: Type, formal: Type) -> bool: - actual = erasetype.erase_type(actual) - return isinstance(actual, AnyType) or is_more_precise(actual, erasetype.erase_type(formal)) + if (isinstance(actual, NoneTyp) or isinstance(actual, AnyType) or + isinstance(formal, AnyType) or isinstance(formal, TypeVar)): + return True + if isinstance(formal, Instance): + if isinstance(actual, Callable): + actual = actual.fallback + if isinstance(actual, Overloaded): + actual = actual.items()[0].fallback + if isinstance(actual, TupleType): + actual = actual.fallback + if isinstance(actual, Instance): + return formal.type in actual.type.mro + else: + return False + return is_same_type(erasetype.erase_type(actual), erasetype.erase_type(formal)) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 61b6b57e9251..24daf54b2832 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -284,7 +284,7 @@ def is_more_precise(t: Type, s: Type) -> bool: type as s, t is more precise than s. """ # TODO Should List[int] be more precise than List[Any]? - if isinstance(s, AnyType) or isinstance(t, NoneTyp): + if isinstance(s, AnyType): return True if isinstance(s, Instance): if isinstance(t, Callable): From f0ecf00a4dcde2bf7e73c9e86a53285de20cf80b Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 15:13:10 -0800 Subject: [PATCH 134/144] Fix to overload resolution --- mypy/checkexpr.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 7bc990f6f69c..8e2fc7131b8e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1398,9 +1398,13 @@ def visit_erased_type(self, t: ErasedType) -> bool: def is_compatible_overload_arg(actual: Type, formal: Type) -> bool: + # TODO: Union type as the formal type. if (isinstance(actual, NoneTyp) or isinstance(actual, AnyType) or isinstance(formal, AnyType) or isinstance(formal, TypeVar)): return True + if isinstance(actual, UnionType): + return any(is_compatible_overload_arg(item, formal) + for item in actual.items) if isinstance(formal, Instance): if isinstance(actual, Callable): actual = actual.fallback From 7866a38677834c2f9e9210b3d955b21b689341b9 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 15:20:39 -0800 Subject: [PATCH 135/144] Fixes to Python 2 builtins stubs --- stubs/2.7/builtins.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/stubs/2.7/builtins.py b/stubs/2.7/builtins.py index b19f7b414d05..80f18498d765 100644 --- a/stubs/2.7/builtins.py +++ b/stubs/2.7/builtins.py @@ -152,8 +152,12 @@ def __init__(self) -> None: pass @overload def __init__(self, o: object) -> None: pass @overload + def __init__(self, o: str, encoding: str = None, errors: str = 'strict') -> None: pass + @overload def __init__(self, o: str, encoding: unicode = None, errors: unicode = 'strict') -> None: pass @overload + def __init__(self, o: bytearray, encoding: str = None, errors: str = 'strict') -> None: pass + @overload def __init__(self, o: bytearray, encoding: unicode = None, errors: unicode = 'strict') -> None: pass def capitalize(self) -> unicode: pass @@ -799,9 +803,11 @@ def next(i: Iterator[_T]) -> _T: pass def next(i: Iterator[_T], default: _T) -> _T: pass def oct(i: int) -> str: pass # TODO __index__ @overload -def open(file: unicode, mode: unicode = 'r', buffering: int = -1) -> BinaryIO: pass +def open(file: str, mode: str = 'r', buffering: int = -1) -> BinaryIO: pass +@overload +def open(file: unicode, mode: str = 'r', buffering: int = -1) -> BinaryIO: pass @overload -def open(file: int, mode: unicode = 'r', buffering: int = -1) -> BinaryIO: pass +def open(file: int, mode: str = 'r', buffering: int = -1) -> BinaryIO: pass @overload def ord(c: unicode) -> int: pass @overload From 9a565ededa0e7a0cfae9dca64595d2990afd7c93 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 15:22:16 -0800 Subject: [PATCH 136/144] Fix to Python 2 builtins --- stubs/2.7/builtins.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/stubs/2.7/builtins.py b/stubs/2.7/builtins.py index 80f18498d765..0add71d8e48a 100644 --- a/stubs/2.7/builtins.py +++ b/stubs/2.7/builtins.py @@ -4,7 +4,7 @@ Undefined, typevar, AbstractGeneric, Iterator, Iterable, overload, Sequence, Mapping, Tuple, List, Any, Dict, Function, Generic, Set, AbstractSet, Sized, Reversible, SupportsInt, SupportsFloat, SupportsAbs, - SupportsRound, IO, BinaryIO, builtinclass, ducktype + SupportsRound, IO, BinaryIO, builtinclass, ducktype, Union ) from abc import abstractmethod, ABCMeta @@ -240,10 +240,7 @@ def capitalize(self) -> str: pass def center(self, width: int, fillchar: str = None) -> str: pass @overload def center(self, width: int, fillchar: bytearray = None) -> str: pass - @overload - def count(self, x: unicode) -> int: pass - @overload - def count(self, x: bytearray) -> int: pass + def count(self, x: Union[unicode, bytearray]) -> int: pass def decode(self, encoding: unicode = 'utf-8', errors: unicode = 'strict') -> unicode: pass def encode(self, encoding: unicode = 'utf-8', errors: unicode = 'strict') -> str: pass @overload From 9fbd6e031a7ac520235e3b945b35b377455dee38 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 15:28:01 -0800 Subject: [PATCH 137/144] Fixes to Python 2 stubs --- stubs/2.7/builtins.py | 43 +++++++------------------------------------ 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/stubs/2.7/builtins.py b/stubs/2.7/builtins.py index 0add71d8e48a..dcc9b3f4d264 100644 --- a/stubs/2.7/builtins.py +++ b/stubs/2.7/builtins.py @@ -236,34 +236,14 @@ def __hash__(self) -> int: pass class str(Sequence[str]): def __init__(self, object: object) -> None: pass def capitalize(self) -> str: pass - @overload - def center(self, width: int, fillchar: str = None) -> str: pass - @overload - def center(self, width: int, fillchar: bytearray = None) -> str: pass + def center(self, width: int, fillchar: Union[str, bytearray] = None) -> str: pass def count(self, x: Union[unicode, bytearray]) -> int: pass def decode(self, encoding: unicode = 'utf-8', errors: unicode = 'strict') -> unicode: pass def encode(self, encoding: unicode = 'utf-8', errors: unicode = 'strict') -> str: pass - @overload - def endswith(self, suffix: unicode) -> bool: pass - @overload - def endswith(self, suffix: bytearray) -> bool: pass + def endswith(self, suffix: Union[unicode, bytearray]) -> bool: pass def expandtabs(self, tabsize: int = 8) -> str: pass - @overload - def find(self, sub: unicode, start: int = 0) -> int: pass - @overload - def find(self, sub: unicode, start: int, end: int) -> int: pass - @overload - def find(self, sub: bytearray, start: int = 0) -> int: pass - @overload - def find(self, sub: bytearray, start: int, end: int) -> int: pass - @overload - def index(self, sub: unicode, start: int = 0) -> int: pass - @overload - def index(self, sub: unicode, start: int, end: int) -> int: pass - @overload - def index(self, sub: bytearray, start: int = 0) -> int: pass - @overload - def index(self, sub: bytearray, start: int, end: int) -> int: pass + def find(self, sub: Union[unicode, bytearray], start: int = 0, end: int = 0) -> int: pass + def index(self, sub: Union[unicode, bytearray], start: int = 0, end: int = 0) -> int: pass def isalnum(self) -> bool: pass def isalpha(self) -> bool: pass def isdigit(self) -> bool: pass @@ -275,10 +255,7 @@ def isupper(self) -> bool: pass def join(self, iterable: Iterable[str]) -> str: pass # TODO unicode @overload def join(self, iterable: Iterable[bytearray]) -> str: pass - @overload - def ljust(self, width: int, fillchar: str = None) -> str: pass - @overload - def ljust(self, width: int, fillchar: bytearray = None) -> str: pass + def ljust(self, width: int, fillchar: Union[str, bytearray] = None) -> str: pass def lower(self) -> str: pass @overload def lstrip(self, chars: str = None) -> str: pass # TODO unicode @@ -308,10 +285,7 @@ def rindex(self, sub: unicode, start: int, end: int) -> int: pass def rindex(self, sub: bytearray, start: int = 0) -> int: pass @overload def rindex(self, sub: bytearray, start: int, end: int) -> int: pass - @overload - def rjust(self, width: int, fillchar: str = None) -> str: pass - @overload - def rjust(self, width: int, fillchar: bytearray = None) -> str: pass + def rjust(self, width: int, fillchar: Union[str, bytearray] = None) -> str: pass @overload def rpartition(self, sep: str) -> Tuple[str, str, str]: pass # TODO unicode @overload @@ -332,10 +306,7 @@ def split(self, sep: str = None, maxsplit: int = -1) -> List[str]: pass # TODO def split(self, sep: bytearray = None, # TODO unicode maxsplit: int = -1) -> List[str]: pass def splitlines(self, keepends: bool = False) -> List[str]: pass - @overload - def startswith(self, prefix: unicode) -> bool: pass - @overload - def startswith(self, prefix: bytearray) -> bool: pass + def startswith(self, prefix: Union[unicode, bytearray]) -> bool: pass @overload def strip(self, chars: str = None) -> str: pass # TODO unicode @overload From 4e29058ff00d28e5fc8efa967ad9aca4693b14db Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 15:31:31 -0800 Subject: [PATCH 138/144] Fix Python 2 stub of str.__add__ With the latest fixes, we can have a precise signature at last. --- mypy/test/data/python2eval.test | 11 +++++++++++ stubs/2.7/builtins.py | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/mypy/test/data/python2eval.test b/mypy/test/data/python2eval.test index dd0a69eeb9c9..920a6d36d9a5 100644 --- a/mypy/test/data/python2eval.test +++ b/mypy/test/data/python2eval.test @@ -247,3 +247,14 @@ print f('ab') [out] 12 ab + +[case testStrAdd_python2] +import typing +s = '' +u = u'' +n = 0 +n = s + '' # E +s = s + u'' # E +[out] +_program.py, line 5: Incompatible types in assignment (expression has type "str", variable has type "int") +_program.py, line 6: Incompatible types in assignment (expression has type "unicode", variable has type "str") diff --git a/stubs/2.7/builtins.py b/stubs/2.7/builtins.py index dcc9b3f4d264..05bddafd7201 100644 --- a/stubs/2.7/builtins.py +++ b/stubs/2.7/builtins.py @@ -335,9 +335,11 @@ def __getitem__(self, i: int) -> str: pass def __getitem__(self, s: slice) -> str: pass def __getslice__(self, start: int, stop: int) -> str: pass @overload - def __add__(self, s: str) -> str: pass # TODO unicode + def __add__(self, s: str) -> str: pass @overload def __add__(self, s: bytearray) -> str: pass + @overload + def __add__(self, s: unicode) -> unicode: pass def __mul__(self, n: int) -> str: pass def __rmul__(self, n: int) -> str: pass def __contains__(self, o: object) -> bool: pass From 748c6f44cce2bfb965fc342d7c3c4a9be4e261d9 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 15:49:02 -0800 Subject: [PATCH 139/144] Fix type checking overloads with Unions in formals --- mypy/checkexpr.py | 4 +++- mypy/meet.py | 8 +++++++- mypy/test/data/check-unions.test | 17 ++++++++++++++--- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 8e2fc7131b8e..0e103a56fe52 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1398,13 +1398,15 @@ def visit_erased_type(self, t: ErasedType) -> bool: def is_compatible_overload_arg(actual: Type, formal: Type) -> bool: - # TODO: Union type as the formal type. if (isinstance(actual, NoneTyp) or isinstance(actual, AnyType) or isinstance(formal, AnyType) or isinstance(formal, TypeVar)): return True if isinstance(actual, UnionType): return any(is_compatible_overload_arg(item, formal) for item in actual.items) + if isinstance(formal, UnionType): + return any(is_compatible_overload_arg(actual, item) + for item in formal.items) if isinstance(formal, Instance): if isinstance(actual, Callable): actual = actual.fallback diff --git a/mypy/meet.py b/mypy/meet.py index 98d0c814919d..ffeb7b18ea7b 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -71,7 +71,13 @@ def is_overlapping_types(t: Type, s: Type) -> bool: if tbuiltin in sbuiltin.mro or sbuiltin in tbuiltin.mro: return True return tbuiltin == sbuiltin - # We conservatively assume that non-instance types can overlap any other + if isinstance(t, UnionType): + return any(is_overlapping_types(item, s) + for item in t.items) + if isinstance(s, UnionType): + return any(is_overlapping_types(t, item) + for item in s.items) + # We conservatively assume that non-instance, non-union types can overlap any other # types. return True diff --git a/mypy/test/data/check-unions.test b/mypy/test/data/check-unions.test index 0a909a07b173..7b69d65d057b 100644 --- a/mypy/test/data/check-unions.test +++ b/mypy/test/data/check-unions.test @@ -79,10 +79,21 @@ i = y.foo() # E: Incompatible types in assignment (expression has type "Union[ [case testUnionIndexing] from typing import Union, List, Undefined - x = Undefined # type: Union[List[int], str] x[2] x[2] + 1 # E: Unsupported operand types for + (likely involving Union) - - [builtins fixtures/isinstancelist.py] + +[case testUnionAsOverloadArg] +from typing import Union, overload +@overload +def f(x: Union[int, str]) -> int: pass +@overload +def f(x: type) -> str: pass +x = 0 +x = f(1) +x = f('') +s = '' +s = f(int) +s = f(1) # E: Incompatible types in assignment (expression has type "int", variable has type "str") +x = f(int) # E: Incompatible types in assignment (expression has type "str", variable has type "int") From 38b47b0ef1ad5cef9e5730ae755557b112a9d4c9 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 16:01:35 -0800 Subject: [PATCH 140/144] Another fix to overload resolution --- mypy/checkexpr.py | 5 ++++- mypy/test/data/pythoneval.test | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0e103a56fe52..ccea678016c8 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1399,7 +1399,9 @@ def visit_erased_type(self, t: ErasedType) -> bool: def is_compatible_overload_arg(actual: Type, formal: Type) -> bool: if (isinstance(actual, NoneTyp) or isinstance(actual, AnyType) or - isinstance(formal, AnyType) or isinstance(formal, TypeVar)): + isinstance(formal, AnyType) or isinstance(formal, TypeVar) or + isinstance(formal, Callable)): + # These could match anything at runtime. return True if isinstance(actual, UnionType): return any(is_compatible_overload_arg(item, formal) @@ -1418,4 +1420,5 @@ def is_compatible_overload_arg(actual: Type, formal: Type) -> bool: return formal.type in actual.type.mro else: return False + # Fall back to a conservative equality check for the remaining kinds of type. return is_same_type(erasetype.erase_type(actual), erasetype.erase_type(formal)) diff --git a/mypy/test/data/pythoneval.test b/mypy/test/data/pythoneval.test index f07afeba841e..7683d4335e50 100644 --- a/mypy/test/data/pythoneval.test +++ b/mypy/test/data/pythoneval.test @@ -919,3 +919,11 @@ map(f, ['x']) map(f, [1]) [out] _program.py, line 4: Argument 1 to "map" has incompatible type Function[["str"] -> "str"]; expected Function[["int"] -> "str"] + +[case testMapStr] +import typing +x = range(3) +a = list(map(str, x)) +a + 1 +[out] +_program.py, line 4: Unsupported operand types for + (List[str] and "int") From 16adf9ff2f68ab0c9d40099ad5ff4e059298eacb Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 14:24:30 -0800 Subject: [PATCH 141/144] Fix type inference problem with overloaded functions Fixes #496. --- mypy/constraints.py | 16 +++++++++++++++- mypy/subtypes.py | 4 ++-- mypy/test/data/check-typevar-values.test | 22 ++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/mypy/constraints.py b/mypy/constraints.py index 7f5f8311eace..159707a47e7e 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -9,6 +9,7 @@ from mypy.expandtype import expand_caller_var_args from mypy.maptype import map_instance_to_supertype from mypy import nodes +import mypy.subtypes SUBTYPE_OF = 0 # type: int @@ -234,7 +235,7 @@ def infer_against_overloaded(self, overloaded: Overloaded, # the first overload item, and by only matching the return type. This # seems to work somewhat well, but we should really use a more # reliable technique. - item = overloaded.items()[0] + item = find_matching_overload_item(overloaded, template) return infer_constraints(template.ret_type, item.ret_type, self.direction) @@ -282,3 +283,16 @@ def neg_op(op: int) -> int: return SUBTYPE_OF else: raise ValueError('Invalid operator {}'.format(op)) + + +def find_matching_overload_item(overloaded: Overloaded, template: Callable) -> Callable: + """Disambiguate overload item against a template.""" + items = overloaded.items() + for item in items: + # Return type may be indeterminate in the template, so ignore it when performing a + # subtype check. + if mypy.subtypes.is_callable_subtype(item, template, ignore_return=True): + return item + # Fall back to the first item if we can't find a match. This is totally arbitrary -- + # maybe we should just bail out at this point. + return items[0] diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 24daf54b2832..3bf063c24ef0 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -160,7 +160,7 @@ def visit_overloaded(self, left: Overloaded) -> bool: return False -def is_callable_subtype(left: Callable, right: Callable) -> bool: +def is_callable_subtype(left: Callable, right: Callable, ignore_return: bool = False) -> bool: """Is left a subtype of right?""" # TODO: Support named arguments, **args, etc. # Non-type cannot be a subtype of type. @@ -176,7 +176,7 @@ def is_callable_subtype(left: Callable, right: Callable) -> bool: return False # Check return types. - if not is_subtype(left.ret_type, right.ret_type): + if not ignore_return and not is_subtype(left.ret_type, right.ret_type): return False # Check argument types. diff --git a/mypy/test/data/check-typevar-values.test b/mypy/test/data/check-typevar-values.test index 01623193c06d..9f7c4f29f13f 100644 --- a/mypy/test/data/check-typevar-values.test +++ b/mypy/test/data/check-typevar-values.test @@ -463,3 +463,25 @@ from typing import typevar, Generic T = typevar('T', values=(int, str)) class C(Generic[T]): def f(self, x: int = None) -> None: pass + +[case testTypevarValuesWithOverloadedFunctionSpecialCase] +from typing import typevar, overload, Function + +T = typevar('T', values=(int, str)) +def f(x: T) -> None: + y = m(g, x) + x = y + y = object() + +A = typevar('A') +R = typevar('R') +def m(f: Function[[A], R], it: A) -> A: pass + +@overload +def g(x: int) -> int: return x +@overload +def g(x: str) -> str: return x +[out] +main: In function "f": +main, line 7: Incompatible types in assignment (expression has type "object", variable has type "int") +main, line 7: Incompatible types in assignment (expression has type "object", variable has type "str") From 3fc7c25865d3d8b67840997b7d8864555b8020b1 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 16:33:40 -0800 Subject: [PATCH 142/144] Some PyPy fixes --- mypy/test/data/pythoneval.test | 15 +++++++-------- stubs/3.2/typing.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/mypy/test/data/pythoneval.test b/mypy/test/data/pythoneval.test index 7683d4335e50..c5d8b48be003 100644 --- a/mypy/test/data/pythoneval.test +++ b/mypy/test/data/pythoneval.test @@ -17,7 +17,8 @@ from typing import Sized, Sequence, Iterator, Iterable, Mapping, AbstractSet def check(o, t): rep = re.sub('0x[0-9a-f]+', '0x...', repr(o)) - print(rep, t, isinstance(o, t)) + s = str(t).replace('sequenceiterator', 'str_iterator') + print(rep, s, isinstance(o, t)) def f(): check('x', Sized) @@ -271,7 +272,7 @@ import math import typing print(math.__name__) print(type(math.__dict__)) -print(type(math.__doc__)) +print(type(typing.__doc__)) print(math.__class__) [out] math @@ -291,12 +292,12 @@ The most base type [case testFunctionAttributes] import typing print(ord.__class__) -print(ord.__doc__[:10]) +print(type(ord.__doc__ + '')) print(ord.__name__) print(ord.__module__) [out] -ord(c) -> + ord builtins @@ -394,9 +395,8 @@ True [case testArray] import typing import array -print(type(array.typecodes)) +array.array('b', [1, 2]) [out] - [case testDictFromkeys] import typing @@ -739,13 +739,12 @@ import typing import sys print(type(sys.stdin.encoding)) print(type(sys.stdin.errors)) -print(type(sys.stdin.line_buffering)) +sys.stdin.line_buffering sys.stdin.buffer sys.stdin.newlines [out] - [case testIOProperties] import typing diff --git a/stubs/3.2/typing.py b/stubs/3.2/typing.py index 84d4dc8d5257..dcb44bde56c5 100644 --- a/stubs/3.2/typing.py +++ b/stubs/3.2/typing.py @@ -238,7 +238,7 @@ def encoding(self) -> str: pass @property def errors(self) -> str: pass @property - def line_buffering(self) -> bool: pass + def line_buffering(self) -> int: pass # int on PyPy, bool on CPython @property def newlines(self) -> Any: pass # None, str or tuple @abstractmethod From e281108c06f4b8c37283ee7305f843a4a22eb9c5 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 16:50:59 -0800 Subject: [PATCH 143/144] Improvements to Python 2 builtins stubs --- mypy/test/data/python2eval.test | 11 ++++++ stubs/2.7/builtins.py | 67 +++++++++++++-------------------- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/mypy/test/data/python2eval.test b/mypy/test/data/python2eval.test index 920a6d36d9a5..26eab265d2bd 100644 --- a/mypy/test/data/python2eval.test +++ b/mypy/test/data/python2eval.test @@ -258,3 +258,14 @@ s = s + u'' # E [out] _program.py, line 5: Incompatible types in assignment (expression has type "str", variable has type "int") _program.py, line 6: Incompatible types in assignment (expression has type "unicode", variable has type "str") + +[case testStrJoin_python2] +import typing +s = '' +u = u'' +n = 0 +n = ''.join(['']) # Error +s = ''.join([u'']) # Error +[out] +_program.py, line 5: Incompatible types in assignment (expression has type "str", variable has type "int") +_program.py, line 6: Incompatible types in assignment (expression has type "unicode", variable has type "str") diff --git a/stubs/2.7/builtins.py b/stubs/2.7/builtins.py index 05bddafd7201..bc40279326d5 100644 --- a/stubs/2.7/builtins.py +++ b/stubs/2.7/builtins.py @@ -4,7 +4,7 @@ Undefined, typevar, AbstractGeneric, Iterator, Iterable, overload, Sequence, Mapping, Tuple, List, Any, Dict, Function, Generic, Set, AbstractSet, Sized, Reversible, SupportsInt, SupportsFloat, SupportsAbs, - SupportsRound, IO, BinaryIO, builtinclass, ducktype, Union + SupportsRound, IO, BinaryIO, builtinclass, ducktype, Union, AnyStr ) from abc import abstractmethod, ABCMeta @@ -251,72 +251,57 @@ def islower(self) -> bool: pass def isspace(self) -> bool: pass def istitle(self) -> bool: pass def isupper(self) -> bool: pass - @overload - def join(self, iterable: Iterable[str]) -> str: pass # TODO unicode - @overload - def join(self, iterable: Iterable[bytearray]) -> str: pass + def join(self, iterable: Iterable[AnyStr]) -> AnyStr: pass def ljust(self, width: int, fillchar: Union[str, bytearray] = None) -> str: pass def lower(self) -> str: pass @overload - def lstrip(self, chars: str = None) -> str: pass # TODO unicode - @overload - def lstrip(self, chars: bytearray = None) -> str: pass - @overload - def partition(self, sep: str) -> Tuple[str, str, str]: pass # TODO unicode - @overload - def partition(self, sep: bytearray) -> Tuple[str, str, str]: pass - @overload - def replace(self, old: str, new: str, count: int = -1) -> str: pass # TODO unicode - @overload - def replace(self, old: bytearray, new: bytearray, count: int = -1) -> str: pass - @overload - def rfind(self, sub: unicode, start: int = 0) -> int: pass - @overload - def rfind(self, sub: unicode, start: int, end: int) -> int: pass + def lstrip(self, chars: Union[str, bytearray] = None) -> str: pass @overload - def rfind(self, sub: bytearray, start: int = 0) -> int: pass + def lstrip(self, chars: unicode) -> unicode: pass @overload - def rfind(self, sub: bytearray, start: int, end: int) -> int: pass + def partition(self, sep: str) -> Tuple[str, str, str]: pass @overload - def rindex(self, sub: unicode, start: int = 0) -> int: pass + def partition(self, sep: bytearray) -> Tuple[str, bytearray, str]: pass @overload - def rindex(self, sub: unicode, start: int, end: int) -> int: pass + def partition(self, sep: unicode) -> Tuple[unicode, unicode, unicode]: pass @overload - def rindex(self, sub: bytearray, start: int = 0) -> int: pass + def replace(self, old: Union[str, bytearray], new: Union[str, bytearray], + count: int = -1) -> str: pass @overload - def rindex(self, sub: bytearray, start: int, end: int) -> int: pass + def replace(self, old: unicode, new: unicode, count: int = -1) -> unicode: pass + def rfind(self, sub: Union[unicode, bytearray], start: int = 0, end: int = 0) -> int: pass + def rindex(self, sub: Union[unicode, bytearray], start: int = 0, end: int = 0) -> int: pass def rjust(self, width: int, fillchar: Union[str, bytearray] = None) -> str: pass @overload - def rpartition(self, sep: str) -> Tuple[str, str, str]: pass # TODO unicode + def rpartition(self, sep: str) -> Tuple[str, str, str]: pass + @overload + def rpartition(self, sep: bytearray) -> Tuple[str, bytearray, str]: pass @overload - def rpartition(self, sep: bytearray) -> Tuple[str, str, str]: pass + def rpartition(self, sep: unicode) -> Tuple[unicode, unicode, unicode]: pass @overload - def rsplit(self, sep: str = None, # TODO unicode - maxsplit: int = -1) -> List[str]: pass + def rsplit(self, sep: Union[str, bytearray] = None, maxsplit: int = -1) -> List[str]: pass @overload - def rsplit(self, sep: bytearray = None, - maxsplit: int = -1) -> List[str]: pass + def rsplit(self, sep: unicode, maxsplit: int = -1) -> List[unicode]: pass @overload - def rstrip(self, chars: str = None) -> str: pass # TODO unicode + def rstrip(self, chars: Union[str, bytearray] = None) -> str: pass @overload - def rstrip(self, chars: bytearray = None) -> str: pass + def rstrip(self, chars: unicode) -> unicode: pass @overload - def split(self, sep: str = None, maxsplit: int = -1) -> List[str]: pass # TODO unicode + def split(self, sep: Union[str, bytearray] = None, maxsplit: int = -1) -> List[str]: pass @overload - def split(self, sep: bytearray = None, # TODO unicode - maxsplit: int = -1) -> List[str]: pass + def split(self, sep: unicode, maxsplit: int = -1) -> List[unicode]: pass def splitlines(self, keepends: bool = False) -> List[str]: pass def startswith(self, prefix: Union[unicode, bytearray]) -> bool: pass @overload - def strip(self, chars: str = None) -> str: pass # TODO unicode + def strip(self, chars: Union[str, bytearray] = None) -> str: pass @overload - def strip(self, chars: bytearray = None) -> str: pass + def strip(self, chars: unicode) -> unicode: pass def swapcase(self) -> str: pass def title(self) -> str: pass @overload - def translate(self, table: str) -> str: pass # TODO unicode + def translate(self, table: Union[str, bytearray]) -> str: pass @overload - def translate(self, table: bytearray) -> str: pass + def translate(self, table: unicode) -> unicode: pass def upper(self) -> str: pass def zfill(self, width: int) -> str: pass # TODO fromhex From 82fea44b7fbd20c5f2ac1729b29155a12469bf1f Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Sat, 8 Nov 2014 16:56:29 -0800 Subject: [PATCH 144/144] More PyPy test fixes --- mypy/test/data/pythoneval.test | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mypy/test/data/pythoneval.test b/mypy/test/data/pythoneval.test index c5d8b48be003..2b8627d1f3a7 100644 --- a/mypy/test/data/pythoneval.test +++ b/mypy/test/data/pythoneval.test @@ -17,8 +17,8 @@ from typing import Sized, Sequence, Iterator, Iterable, Mapping, AbstractSet def check(o, t): rep = re.sub('0x[0-9a-f]+', '0x...', repr(o)) - s = str(t).replace('sequenceiterator', 'str_iterator') - print(rep, s, isinstance(o, t)) + rep = rep.replace('sequenceiterator', 'str_iterator') + print(rep, t, isinstance(o, t)) def f(): check('x', Sized) @@ -291,12 +291,11 @@ The most base type [case testFunctionAttributes] import typing -print(ord.__class__) +ord.__class__ print(type(ord.__doc__ + '')) print(ord.__name__) print(ord.__module__) [out] - ord builtins