use an immutable list for an lru_cached functions

When mutable items are stored in an lru cache, changing the returned
items changes the cached items as well. Therefore we want to ensure that
we're not mutating them. Using the ImmutableListProtocol allows mypy to
find mutations and reject them. This doesn't solve the problem of
mutable values inside the values, so you could have to do things like:

```python
ImmutableListProtocol[ImmutableListProtocol[str]]
```

or equally hacky. It can also be used for input types and acts a bit
like C's const:

```python
def foo(arg: ImmutableListProtocol[str]) -> T.List[str]:
    arg[1] = 'foo'  # works while running, but mypy errors
```
pull/8643/merge
Dylan Baker 4 years ago
parent f8be4f8fc7
commit 113a159514
  1. 12
      mesonbuild/backend/backends.py
  2. 16
      mesonbuild/backend/ninjabackend.py
  3. 5
      mesonbuild/build.py
  4. 5
      mesonbuild/cmake/interpreter.py
  5. 1
      mesonbuild/compilers/compilers.py
  6. 5
      mesonbuild/compilers/mixins/clike.py
  7. 5
      mesonbuild/compilers/mixins/gnu.py
  8. 3
      mesonbuild/mesonlib/universal.py

@ -36,6 +36,7 @@ from ..mesonlib import (
) )
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from .._typing import ImmutableListProtocol
from ..arglist import CompilerArgs from ..arglist import CompilerArgs
from ..compilers import Compiler from ..compilers import Compiler
from ..interpreter import Interpreter, Test from ..interpreter import Interpreter, Test
@ -1103,21 +1104,20 @@ class Backend:
return result return result
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
def get_custom_target_provided_by_generated_source(self, generated_source): def get_custom_target_provided_by_generated_source(self, generated_source: build.CustomTarget) -> 'ImmutableListProtocol[str]':
libs = [] libs: T.List[str] = []
for f in generated_source.get_outputs(): for f in generated_source.get_outputs():
if self.environment.is_library(f): if self.environment.is_library(f):
libs.append(os.path.join(self.get_target_dir(generated_source), f)) libs.append(os.path.join(self.get_target_dir(generated_source), f))
return libs return libs
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
def get_custom_target_provided_libraries(self, target): def get_custom_target_provided_libraries(self, target: T.Union[build.BuildTarget, build.CustomTarget]) -> 'ImmutableListProtocol[str]':
libs = [] libs: T.List[str] = []
for t in target.get_generated_sources(): for t in target.get_generated_sources():
if not isinstance(t, build.CustomTarget): if not isinstance(t, build.CustomTarget):
continue continue
l = self.get_custom_target_provided_by_generated_source(t) libs.extend(self.get_custom_target_provided_by_generated_source(t))
libs = libs + l
return libs return libs
def is_unity(self, target): def is_unity(self, target):

@ -49,6 +49,7 @@ from ..interpreter import Interpreter
from ..mesonmain import need_setup_vsenv from ..mesonmain import need_setup_vsenv
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from .._typing import ImmutableListProtocol
from ..linkers import StaticLinker from ..linkers import StaticLinker
from ..compilers.cs import CsCompiler from ..compilers.cs import CsCompiler
@ -2329,7 +2330,8 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
return (rel_obj, rel_src) return (rel_obj, rel_src)
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
def generate_inc_dir(self, compiler, d, basedir, is_system): def generate_inc_dir(self, compiler: 'Compiler', d: str, basedir: str, is_system: bool) -> \
T.Tuple['ImmutableListProtocol[str]', 'ImmutableListProtocol[str]']:
# Avoid superfluous '/.' at the end of paths when d is '.' # Avoid superfluous '/.' at the end of paths when d is '.'
if d not in ('', '.'): if d not in ('', '.'):
expdir = os.path.normpath(os.path.join(basedir, d)) expdir = os.path.normpath(os.path.join(basedir, d))
@ -2349,12 +2351,13 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
bargs = [] bargs = []
return (sargs, bargs) return (sargs, bargs)
def _generate_single_compile(self, target, compiler, is_generated=False): def _generate_single_compile(self, target: build.BuildTarget, compiler: 'Compiler',
commands = self._generate_single_compile_base_args(target, compiler, is_generated) is_generated: bool = False) -> 'CompilerArgs':
commands = self._generate_single_compile_base_args(target, compiler)
commands += self._generate_single_compile_target_args(target, compiler, is_generated) commands += self._generate_single_compile_target_args(target, compiler, is_generated)
return commands return commands
def _generate_single_compile_base_args(self, target, compiler, is_generated): def _generate_single_compile_base_args(self, target: build.BuildTarget, compiler: 'Compiler') -> 'CompilerArgs':
base_proxy = self.get_base_options_for_target(target) base_proxy = self.get_base_options_for_target(target)
# Create an empty commands list, and start adding arguments from # Create an empty commands list, and start adding arguments from
# various sources in the order in which they must override each other # various sources in the order in which they must override each other
@ -2369,7 +2372,8 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
return commands return commands
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
def _generate_single_compile_target_args(self, target, compiler, is_generated): def _generate_single_compile_target_args(self, target: build.BuildTarget, compiler: 'Compiler',
is_generated: bool = False) -> 'ImmutableListProtocol[str]':
# The code generated by valac is usually crap and has tons of unused # The code generated by valac is usually crap and has tons of unused
# variables and such, so disable warnings for Vala C sources. # variables and such, so disable warnings for Vala C sources.
no_warn_args = (is_generated == 'vala') no_warn_args = (is_generated == 'vala')
@ -2439,7 +2443,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
raise AssertionError(f'BUG: sources should not contain headers {src!r}') raise AssertionError(f'BUG: sources should not contain headers {src!r}')
compiler = get_compiler_for_source(target.compilers.values(), src) compiler = get_compiler_for_source(target.compilers.values(), src)
commands = self._generate_single_compile_base_args(target, compiler, is_generated) commands = self._generate_single_compile_base_args(target, compiler)
# Include PCH header as first thing as it must be the first one or it will be # Include PCH header as first thing as it must be the first one or it will be
# ignored by gcc https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100462 # ignored by gcc https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100462

@ -40,6 +40,7 @@ from .linkers import StaticLinker
from .interpreterbase import FeatureNew from .interpreterbase import FeatureNew
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from ._typing import ImmutableListProtocol
from .interpreter.interpreter import Test, SourceOutputs from .interpreter.interpreter import Test, SourceOutputs
from .mesonlib import FileMode, FileOrString from .mesonlib import FileMode, FileOrString
from .backend.backends import Backend from .backend.backends import Backend
@ -909,8 +910,8 @@ class BuildTarget(Target):
return self.get_transitive_link_deps() return self.get_transitive_link_deps()
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
def get_transitive_link_deps(self): def get_transitive_link_deps(self) -> 'ImmutableListProtocol[Target]':
result = [] result: T.List[Target] = []
for i in self.link_targets: for i in self.link_targets:
result += i.get_all_link_deps() result += i.get_all_link_deps()
return result return result

@ -51,6 +51,7 @@ from ..mparser import (
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from .._typing import ImmutableListProtocol
from ..build import Build from ..build import Build
from ..backend.backends import Backend from ..backend.backends import Backend
from ..environment import Environment from ..environment import Environment
@ -582,14 +583,14 @@ class ConverterTarget:
self.compile_opts[lang] += [x for x in opts if x not in self.compile_opts[lang]] self.compile_opts[lang] += [x for x in opts if x not in self.compile_opts[lang]]
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
def _all_source_suffixes(self) -> T.List[str]: def _all_source_suffixes(self) -> 'ImmutableListProtocol[str]':
suffixes = [] # type: T.List[str] suffixes = [] # type: T.List[str]
for exts in lang_suffixes.values(): for exts in lang_suffixes.values():
suffixes += [x for x in exts] suffixes += [x for x in exts]
return suffixes return suffixes
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
def _all_lang_stds(self, lang: str) -> T.List[str]: def _all_lang_stds(self, lang: str) -> 'ImmutableListProtocol[str]':
try: try:
res = self.env.coredata.options[OptionKey('std', machine=MachineChoice.BUILD, lang=lang)].choices res = self.env.coredata.options[OptionKey('std', machine=MachineChoice.BUILD, lang=lang)].choices
except KeyError: except KeyError:

@ -1023,6 +1023,7 @@ class Compiler(metaclass=abc.ABCMeta):
raise EnvironmentException('This compiler does not have a preprocessor') raise EnvironmentException('This compiler does not have a preprocessor')
def get_default_include_dirs(self) -> T.List[str]: def get_default_include_dirs(self) -> T.List[str]:
# TODO: This is a candidate for returning an immutable list
return [] return []
def get_largefile_args(self) -> T.List[str]: def get_largefile_args(self) -> T.List[str]:

@ -42,6 +42,7 @@ from .visualstudio import VisualStudioLikeCompiler
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from ...dependencies import Dependency from ...dependencies import Dependency
from ..._typing import ImmutableListProtocol
from ...environment import Environment from ...environment import Environment
from ...compilers.compilers import Compiler from ...compilers.compilers import Compiler
from ...programs import ExternalProgram from ...programs import ExternalProgram
@ -207,7 +208,7 @@ class CLikeCompiler(Compiler):
@functools.lru_cache() @functools.lru_cache()
def _get_library_dirs(self, env: 'Environment', def _get_library_dirs(self, env: 'Environment',
elf_class: T.Optional[int] = None) -> T.List[str]: elf_class: T.Optional[int] = None) -> 'ImmutableListProtocol[str]':
# TODO: replace elf_class with enum # TODO: replace elf_class with enum
dirs = self.get_compiler_dirs(env, 'libraries') dirs = self.get_compiler_dirs(env, 'libraries')
if elf_class is None or elf_class == 0: if elf_class is None or elf_class == 0:
@ -253,7 +254,7 @@ class CLikeCompiler(Compiler):
return self._get_library_dirs(env, elf_class).copy() return self._get_library_dirs(env, elf_class).copy()
@functools.lru_cache() @functools.lru_cache()
def _get_program_dirs(self, env: 'Environment') -> T.List[str]: def _get_program_dirs(self, env: 'Environment') -> 'ImmutableListProtocol[str]':
''' '''
Programs used by the compiler. Also where toolchain DLLs such as Programs used by the compiler. Also where toolchain DLLs such as
libstdc++-6.dll are found with MinGW. libstdc++-6.dll are found with MinGW.

@ -28,6 +28,7 @@ from ... import mlog
from ...mesonlib import OptionKey from ...mesonlib import OptionKey
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from ..._typing import ImmutableListProtocol
from ...environment import Environment from ...environment import Environment
from ..compilers import Compiler from ..compilers import Compiler
else: else:
@ -92,7 +93,7 @@ gnu_color_args = {
@functools.lru_cache(maxsize=None) @functools.lru_cache(maxsize=None)
def gnulike_default_include_dirs(compiler: T.Tuple[str], lang: str) -> T.List[str]: def gnulike_default_include_dirs(compiler: T.Tuple[str, ...], lang: str) -> 'ImmutableListProtocol[str]':
lang_map = { lang_map = {
'c': 'c', 'c': 'c',
'cpp': 'c++', 'cpp': 'c++',
@ -189,7 +190,7 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta):
return gnulike_instruction_set_args.get(instruction_set, None) return gnulike_instruction_set_args.get(instruction_set, None)
def get_default_include_dirs(self) -> T.List[str]: def get_default_include_dirs(self) -> T.List[str]:
return gnulike_default_include_dirs(tuple(self.exelist), self.language) return gnulike_default_include_dirs(tuple(self.exelist), self.language).copy()
@abc.abstractmethod @abc.abstractmethod
def openmp_flags(self) -> T.List[str]: def openmp_flags(self) -> T.List[str]:

@ -30,6 +30,7 @@ import textwrap
from mesonbuild import mlog from mesonbuild import mlog
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from .._typing import ImmutableListProtocol
from ..build import ConfigurationData from ..build import ConfigurationData
from ..coredata import KeyedOptionDictType, UserOption from ..coredata import KeyedOptionDictType, UserOption
from ..compilers.compilers import CompilerType from ..compilers.compilers import CompilerType
@ -659,7 +660,7 @@ def exe_exists(arglist: T.List[str]) -> bool:
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
def darwin_get_object_archs(objpath: str) -> T.List[str]: def darwin_get_object_archs(objpath: str) -> 'ImmutableListProtocol[str]':
''' '''
For a specific object (executable, static library, dylib, etc), run `lipo` For a specific object (executable, static library, dylib, etc), run `lipo`
to fetch the list of archs supported by it. Supports both thin objects and to fetch the list of archs supported by it. Supports both thin objects and

Loading…
Cancel
Save