Repository: reagento/adaptix (language: python)

Issue:
feature/list-coercion
allow to use model conversion inside type hints like list ot optional


Discussion / hints:

Fix patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..5ace4600
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
diff --git a/.github/workflows/coverage_external_pr.yml b/.github/workflows/coverage_external_pr.yml
new file mode 100644
index 00000000..3931c0d9
--- /dev/null
+++ b/.github/workflows/coverage_external_pr.yml
@@ -0,0 +1,24 @@
+name: Post coverage comment
+
+on:
+  workflow_run:
+    workflows: ["Run linters and tests"]
+    types:
+      - completed
+
+jobs:
+  test:
+    name: Run tests & display coverage
+    runs-on: ubuntu-latest
+    if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
+    permissions:
+      pull-requests: write
+      actions: read
+    steps:
+      # DO NOT run actions/checkout here, for security reasons
+      # For details, refer to https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
+      - name: Post comment
+        uses: py-cov-action/python-coverage-comment-action@b16205b76b824c17afe95a014fb22e58b4f239cb
+        with:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GITHUB_PR_RUN_ID: ${{ github.event.workflow_run.id }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d2b9e632..9f1c9b4f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -33,7 +33,7 @@ repos:
         args: ['-e']  # remove default --in-place argument and emit error
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.3.0
+    rev: v0.3.2
     hooks:
       - id: ruff
         args: [ --fix-only ]
diff --git a/.pylintrc b/.pylintrc
deleted file mode 100644
index a85f318e..00000000
--- a/.pylintrc
+++ /dev/null
@@ -1,632 +0,0 @@
-[MAIN]
-
-# Analyse import fallback blocks. This can be used to support both Python 2 and
-# 3 compatible code, which means that the block might have code that exists
-# only in one or another interpreter, leading to false positives when analysed.
-analyse-fallback-blocks=no
-
-# Load and enable all available extensions. Use --list-extensions to see a list
-# all available extensions.
-#enable-all-extensions=
-
-# In error mode, messages with a category besides ERROR or FATAL are
-# suppressed, and no reports are done by default. Error mode is compatible with
-# disabling specific errors.
-#errors-only=
-
-# Always return a 0 (non-error) status code, even if lint errors are found.
-# This is primarily useful in continuous integration scripts.
-#exit-zero=
-
-# A comma-separated list of package or module names from where C extensions may
-# be loaded. Extensions are loading into the active Python interpreter and may
-# run arbitrary code.
-extension-pkg-allow-list=
-
-# A comma-separated list of package or module names from where C extensions may
-# be loaded. Extensions are loading into the active Python interpreter and may
-# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
-# for backward compatibility.)
-extension-pkg-whitelist=
-
-# Return non-zero exit code if any of these messages/categories are detected,
-# even if score is above --fail-under value. Syntax same as enable. Messages
-# specified are enabled, while categories only check already-enabled messages.
-fail-on=
-
-# Specify a score threshold to be exceeded before program exits with error.
-fail-under=10
-
-# Interpret the stdin as a python script, whose filename needs to be passed as
-# the module_or_package argument.
-#from-stdin=
-
-# Files or directories to be skipped. They should be base names, not paths.
-ignore=CVS
-
-# Add files or directories matching the regex patterns to the ignore-list. The
-# regex matches against paths and can be in Posix or Windows format.
-ignore-paths=
-
-# Files or directories matching the regex patterns are skipped. The regex
-# matches against base names, not paths. The default value ignores Emacs file
-# locks
-ignore-patterns=^\.#
-
-# List of module names for which member attributes should not be checked
-# (useful for modules/projects where namespaces are manipulated during runtime
-# and thus existing member attributes cannot be deduced by static analysis). It
-# supports qualified module names, as well as Unix pattern matching.
-ignored-modules=
-
-# Python code to execute, usually for sys.path manipulation such as
-# pygtk.require().
-#init-hook=
-
-# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
-# number of processors available to use, and will cap the count on Windows to
-# avoid hangs.
-jobs=0
-
-# Control the amount of potential inferred values when inferring a single
-# object. This can help the performance when dealing with large functions or
-# complex, nested conditions.
-limit-inference-results=100
-
-# List of plugins (as comma separated values of python module names) to load,
-# usually to register additional checkers.
-load-plugins=
-
-# Pickle collected data for later comparisons.
-persistent=yes
-
-# Minimum Python version to use for version dependent checks. Will default to
-# the version used to run pylint.
-py-version=3.8
-
-# Discover python modules and packages in the file system subtree.
-recursive=no
-
-# When enabled, pylint would attempt to guess common misconfiguration and emit
-# user-friendly hints instead of false-positive error messages.
-suggestion-mode=yes
-
-# Allow loading of arbitrary C extensions. Extensions are imported into the
-# active Python interpreter and may run arbitrary code.
-unsafe-load-any-extension=no
-
-# In verbose mode, extra non-checker-related info will be displayed.
-#verbose=
-
-
-[REPORTS]
-
-# Python expression which should return a score less than or equal to 10. You
-# have access to the variables 'fatal', 'error', 'warning', 'refactor',
-# 'convention', and 'info' which contain the number of messages in each
-# category, as well as 'statement' which is the total number of statements
-# analyzed. This score is used by the global evaluation report (RP0004).
-evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
-
-# Template used to display messages. This is a python new-style format string
-# used to format the message information. See doc for all details.
-msg-template=
-
-# Set the output format. Available formats are text, parseable, colorized, json
-# and msvs (visual studio). You can also give a reporter class, e.g.
-# mypackage.mymodule.MyReporterClass.
-#output-format=
-
-# Tells whether to display a full report or only the messages.
-reports=no
-
-# Activate the evaluation score.
-score=yes
-
-
-[MESSAGES CONTROL]
-
-# Only show warnings with the listed confidence levels. Leave empty to show
-# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
-# UNDEFINED.
-confidence=HIGH,
-           CONTROL_FLOW,
-           INFERENCE,
-           INFERENCE_FAILURE,
-           UNDEFINED
-
-# Disable the message, report, category or checker with the given id(s). You
-# can either give multiple identifiers separated by comma (,) or put this
-# option multiple times (only on the command line, not in the configuration
-# file where it should appear only once). You can also use "--disable=all" to
-# disable everything first and then re-enable specific checks. For example, if
-# you want to run only the similarities checker, you can use "--disable=all
-# --enable=similarities". If you want to run only the classes checker, but have
-# no Warning level messages displayed, use "--disable=all --enable=classes
-# --disable=W".
-disable=raw-checker-failed,
-        bad-inline-option,
-        locally-disabled,
-        file-ignored,
-        suppressed-message,
-        useless-suppression,
-        deprecated-pragma,
-        use-symbolic-message-instead,
-        missing-function-docstring,
-        missing-class-docstring,
-        missing-module-docstring,
-        too-few-public-methods,
-        unused-argument,
-        too-many-ancestors,
-        too-many-instance-attributes,
-        too-many-arguments,
-        fixme,
-        raise-missing-from,
-        unhashable-member,
-        unused-import,
-        duplicate-code,
-
-# Enable the message, report, category or checker with the given id(s). You can
-# either give multiple identifier separated by comma (,) or put this option
-# multiple time (only on the command line, not in the configuration file where
-# it should appear only once). See also the "--disable" option for examples.
-enable=c-extension-no-member
-
-
-[FORMAT]
-
-# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
-expected-line-ending-format=
-
-# Regexp for a line that is allowed to be longer than the limit.
-ignore-long-lines=^\s*(# )?<?https?://\S+>?$
-
-# Number of spaces of indent required inside a hanging or continued line.
-indent-after-paren=4
-
-# String used as indentation unit. This is usually "    " (4 spaces) or "\t" (1
-# tab).
-indent-string='    '
-
-# Maximum number of characters on a single line.
-# https://stackoverflow.com/a/75267980
-max-line-length=1500000
-
-# Maximum number of lines in a module.
-# https://stackoverflow.com/a/75267980
-max-module-lines=1500000
-
-# Allow the body of a class to be on the same line as the declaration if body
-# contains single statement.
-single-line-class-stmt=no
-
-# Allow the body of an if to be on the same line as the test if there is no
-# else.
-single-line-if-stmt=no
-
-
-[IMPORTS]
-
-# List of modules that can be imported at any level, not just the top level
-# one.
-allow-any-import-level=typing
-
-# Allow wildcard imports from modules that define __all__.
-allow-wildcard-with-all=no
-
-# Deprecated modules which should not be used, separated by a comma.
-deprecated-modules=
-
-# Output a graph (.gv or any supported image format) of external dependencies
-# to the given file (report RP0402 must not be disabled).
-ext-import-graph=
-
-# Output a graph (.gv or any supported image format) of all (i.e. internal and
-# external) dependencies to the given file (report RP0402 must not be
-# disabled).
-import-graph=
-
-# Output a graph (.gv or any supported image format) of internal dependencies
-# to the given file (report RP0402 must not be disabled).
-int-import-graph=
-
-# Force import order to recognize a module as part of the standard
-# compatibility libraries.
-known-standard-library=
-
-# Force import order to recognize a module as part of a third party library.
-known-third-party=enchant
-
-# Couples of modules and preferred modules, separated by a comma.
-preferred-modules=
-
-
-[SIMILARITIES]
-
-# Comments are removed from the similarity computation
-ignore-comments=yes
-
-# Docstrings are removed from the similarity computation
-ignore-docstrings=yes
-
-# Imports are removed from the similarity computation
-ignore-imports=yes
-
-# Signatures are removed from the similarity computation
-ignore-signatures=yes
-
-# Minimum lines number of a similarity.
-min-similarity-lines=4
-
-
-[LOGGING]
-
-# The type of string formatting that logging methods do. `old` means using %
-# formatting, `new` is for `{}` formatting.
-logging-format-style=old
-
-# Logging modules to check that the string format arguments are in logging
-# function parameter format.
-logging-modules=logging
-
-
-[EXCEPTIONS]
-
-# Exceptions that will emit a warning when caught.
-overgeneral-exceptions=builtins.BaseException,
-                       builtins.Exception
-
-
-[MISCELLANEOUS]
-
-# List of note tags to take in consideration, separated by a comma.
-notes=FIXME,
-      XXX,
-      TODO
-
-# Regular expression of note tags to take in consideration.
-notes-rgx=
-
-
-[SPELLING]
-
-# Limits count of emitted suggestions for spelling mistakes.
-max-spelling-suggestions=4
-
-# Spelling dictionary name. Available dictionaries: none. To make it work,
-# install the 'python-enchant' package.
-spelling-dict=
-
-# List of comma separated words that should be considered directives if they
-# appear at the beginning of a comment and should not be checked.
-spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
-
-# List of comma separated words that should not be checked.
-spelling-ignore-words=
-
-# A path to a file that contains the private dictionary; one word per line.
-spelling-private-dict-file=
-
-# Tells whether to store unknown words to the private dictionary (see the
-# --spelling-private-dict-file option) instead of raising a message.
-spelling-store-unknown-words=no
-
-
-[STRING]
-
-# This flag controls whether inconsistent-quotes generates a warning when the
-# character used as a quote delimiter is used inconsistently within a module.
-check-quote-consistency=no
-
-# This flag controls whether the implicit-str-concat should generate a warning
-# on implicit string concatenation in sequences defined over several lines.
-check-str-concat-over-line-jumps=no
-
-
-[CLASSES]
-
-# Warn about protected attribute access inside special methods
-check-protected-access-in-special-methods=no
-
-# List of method names used to declare (i.e. assign) instance attributes.
-defining-attr-methods=__init__,
-                      __new__,
-                      setUp,
-                      __post_init__
-
-# List of member names, which should be excluded from the protected access
-# warning.
-exclude-protected=_asdict,
-                  _fields,
-                  _replace,
-                  _source,
-                  _make
-
-# List of valid names for the first argument in a class method.
-valid-classmethod-first-arg=cls
-
-# List of valid names for the first argument in a metaclass class method.
-valid-metaclass-classmethod-first-arg=mcs
-
-
-[VARIABLES]
-
-# List of additional names supposed to be defined in builtins. Remember that
-# you should avoid defining new builtins when possible.
-additional-builtins=
-
-# Tells whether unused global variables should be treated as a violation.
-allow-global-unused-variables=yes
-
-# List of names allowed to shadow builtins
-allowed-redefined-builtins=
-  map,
-# allow redefine this builtin for name_mapping
-
-
-# List of strings which can identify a callback function by name. A callback
-# name must start or end with one of those strings.
-callbacks=cb_,
-          _cb
-
-# A regular expression matching the name of dummy variables (i.e. expected to
-# not be used).
-dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
-
-# Argument names that match this expression will be ignored. Default to name
-# with leading underscore.
-ignored-argument-names=_.*|^ignored_|^unused_
-
-# Tells whether we should check for unused import in __init__ files.
-init-import=no
-
-# List of qualified module names which can have objects that can redefine
-# builtins.
-redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
-
-
-[DESIGN]
-
-# List of regular expressions of class ancestor names to ignore when counting
-# public methods (see R0903)
-exclude-too-few-public-methods=
-
-# List of qualified class names to ignore when counting class parents (see
-# R0901)
-ignored-parents=
-
-# Maximum number of arguments for function / method.
-max-args=5
-
-# Maximum number of attributes for a class (see R0902).
-max-attributes=7
-
-# Maximum number of boolean expressions in an if statement (see R0916).
-max-bool-expr=5
-
-# Maximum number of branch for function / method body.
-max-branches=12
-
-# Maximum number of locals for function / method body.
-max-locals=15
-
-# Maximum number of parents for a class (see R0901).
-max-parents=7
-
-# Maximum number of public methods for a class (see R0904).
-max-public-methods=20
-
-# Maximum number of return / yield for function / method body.
-max-returns=6
-
-# Maximum number of statements in function / method body.
-max-statements=50
-
-# Minimum number of public methods for a class (see R0903).
-min-public-methods=2
-
-
-[TYPECHECK]
-
-# List of decorators that produce context managers, such as
-# contextlib.contextmanager. Add to this list to register other decorators that
-# produce valid context managers.
-contextmanager-decorators=contextlib.contextmanager
-
-# List of members which are set dynamically and missed by pylint inference
-# system, and so shouldn't trigger E1101 when accessed. Python regular
-# expressions are accepted.
-generated-members=
-
-# Tells whether to warn about missing members when the owner of the attribute
-# is inferred to be None.
-ignore-none=yes
-
-# This flag controls whether pylint should warn about no-member and similar
-# checks whenever an opaque object is returned when inferring. The inference
-# can return multiple potential results while evaluating a Python object, but
-# some branches might not be evaluated, which results in partial inference. In
-# that case, it might be useful to still emit no-member and other checks for
-# the rest of the inferred objects.
-ignore-on-opaque-inference=yes
-
-# List of symbolic message names to ignore for Mixin members.
-ignored-checks-for-mixins=no-member,
-                          not-async-context-manager,
-                          not-context-manager,
-                          attribute-defined-outside-init
-
-# List of class names for which member attributes should not be checked (useful
-# for classes with dynamically set attributes). This supports the use of
-# qualified names.
-ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
-
-# Show a hint with possible names when a member name was not found. The aspect
-# of finding the hint is based on edit distance.
-missing-member-hint=yes
-
-# The minimum edit distance a name should have in order to be considered a
-# similar match for a missing member name.
-missing-member-hint-distance=1
-
-# The total number of similar names that should be taken in consideration when
-# showing a hint for a missing member.
-missing-member-max-choices=1
-
-# Regex pattern to define which classes are considered mixins.
-mixin-class-rgx=.*[Mm]ixin
-
-# List of decorators that change the signature of a decorated function.
-signature-mutators=
-
-
-[BASIC]
-
-# Naming style matching correct argument names.
-argument-naming-style=snake_case
-
-# Regular expression matching correct argument names. Overrides argument-
-# naming-style. If left empty, argument names will be checked with the set
-# naming style.
-argument-rgx=[a-z_][a-z0-9_]{1,30}$
-# default regex does not allow two letter names
-
-# Naming style matching correct attribute names.
-attr-naming-style=snake_case
-
-# Regular expression matching correct attribute names. Overrides attr-naming-
-# style. If left empty, attribute names will be checked with the set naming
-# style.
-attr-rgx=[a-z_][a-z0-9_]{1,30}$
-# default regex does not allow two letter names
-
-# Bad variable names which should always be refused, separated by a comma.
-bad-names=foo,
-          bar,
-          baz,
-          toto,
-          tutu,
-          tata
-
-# Bad variable names regexes, separated by a comma. If names match any regex,
-# they will always be refused
-bad-names-rgxs=
-
-# Naming style matching correct class attribute names.
-class-attribute-naming-style=any
-
-# Regular expression matching correct class attribute names. Overrides class-
-# attribute-naming-style. If left empty, class attribute names will be checked
-# with the set naming style.
-#class-attribute-rgx=
-
-# Naming style matching correct class constant names.
-class-const-naming-style=UPPER_CASE
-
-# Regular expression matching correct class constant names. Overrides class-
-# const-naming-style. If left empty, class constant names will be checked with
-# the set naming style.
-#class-const-rgx=
-
-# Naming style matching correct class names.
-class-naming-style=PascalCase
-
-# Regular expression matching correct class names. Overrides class-naming-
-# style. If left empty, class names will be checked with the set naming style.
-#class-rgx=
-
-# Naming style matching correct constant names.
-const-naming-style=UPPER_CASE
-
-# Regular expression matching correct constant names. Overrides const-naming-
-# style. If left empty, constant names will be checked with the set naming
-# style.
-#const-rgx=
-
-# Minimum line length for functions/classes that require docstrings, shorter
-# ones are exempt.
-docstring-min-length=-1
-
-# Naming style matching correct function names.
-function-naming-style=snake_case
-
-# Regular expression matching correct function names. Overrides function-
-# naming-style. If left empty, function names will be checked with the set
-# naming style.
-#function-rgx=
-
-# Good variable names which should always be accepted, separated by a comma.
-good-names=i,
-           j,
-           k,
-           v,
-           e,
-           ex,
-           _
-
-# Good variable names regexes, separated by a comma. If names match any regex,
-# they will always be accepted
-good-names-rgxs=
-
-# Include a hint for the correct naming format with invalid-name.
-include-naming-hint=no
-
-# Naming style matching correct inline iteration names.
-inlinevar-naming-style=any
-
-# Regular expression matching correct inline iteration names. Overrides
-# inlinevar-naming-style. If left empty, inline iteration names will be checked
-# with the set naming style.
-#inlinevar-rgx=
-
-# Naming style matching correct method names.
-method-naming-style=snake_case
-
-# Regular expression matching correct method names. Overrides method-naming-
-# style. If left empty, method names will be checked with the set naming style.
-#method-rgx=
-
-# Naming style matching correct module names.
-module-naming-style=snake_case
-
-# Regular expression matching correct module names. Overrides module-naming-
-# style. If left empty, module names will be checked with the set naming style.
-#module-rgx=
-
-# Colon-delimited sets of names that determine each other's naming style when
-# the name regexes allow several styles.
-name-group=
-
-# Regular expression which should only match function or class names that do
-# not require a docstring.
-no-docstring-rgx=^_
-
-# List of decorators that produce properties, such as abc.abstractproperty. Add
-# to this list to register other decorators that produce valid properties.
-# These decorators are taken in consideration only for invalid-name.
-property-classes=abc.abstractproperty
-
-# Regular expression matching correct type variable names. If left empty, type
-# variable names will be checked with the set naming style.
-typevar-rgx=_{0,2}(?:[A-Z_][a-zA-Z0-9]*)(?:_co(?:ntra)?)?$
-# default regex does not match CapWords
-
-# Naming style matching correct variable names.
-variable-naming-style=snake_case
-
-# Regular expression matching correct variable names. Overrides variable-
-# naming-style. If left empty, variable names will be checked with the set
-# naming style.
-variable-rgx=[a-z_][a-z0-9_]{1,30}$
-# default regex does not allow two letter names
-
-[REFACTORING]
-
-# Maximum number of nested blocks for function / method body
-max-nested-blocks=5
-
-# Complete name of functions that never returns. When checking for
-# inconsistent-return-statements if a never returning function is called then
-# it will be considered as an explicit return statement and no message will be
-# printed.
-never-returning-functions=sys.exit,argparse.parse_error
diff --git a/README.md b/README.md
index 99805d24..7b93486d 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
   <picture>
     <source media="(prefers-color-scheme: dark)" srcset="https://github.com/reagento/adaptix/blob/v3.0.0b2/docs/logo/adaptix-with-title-dark.png?raw=true">
     <source media="(prefers-color-scheme: light)" srcset="https://github.com/reagento/adaptix/blob/v3.0.0b2/docs/logo/adaptix-with-title-light.png?raw=true">
-    <img alt="adaptix logo" src="docs/logo/adaptix-with-title-light.png">
+    <img alt="adaptix logo" src="https://raw.githubusercontent.com/reagento/adaptix/v3.0.0b2/docs/logo/adaptix-with-title-light.png?raw=true">
   </picture>
 
   <hr>
@@ -25,7 +25,7 @@ An extremely flexible and configurable data model conversion library.
 
 Install
 ```bash
-pip install adaptix==3.0.0b3
+pip install adaptix==3.0.0b4
 ```
 
 Use for model loading and dumping.
diff --git a/benchmarks/benchmarks/gh_issues/bench_mashumaro.py b/benchmarks/benchmarks/gh_issues/bench_mashumaro.py
index 49a4ddea..bf85d360 100644
--- a/benchmarks/benchmarks/gh_issues/bench_mashumaro.py
+++ b/benchmarks/benchmarks/gh_issues/bench_mashumaro.py
@@ -1,6 +1,6 @@
 from dataclasses import dataclass
 from datetime import datetime
-from typing import Any, Dict, List, Optional
+from typing import List, Optional
 
 from mashumaro import DataClassDictMixin
 from mashumaro.config import BaseConfig
diff --git a/docs/changelog/changelog_body.rst b/docs/changelog/changelog_body.rst
index 72fe883e..a9bbec32 100644
--- a/docs/changelog/changelog_body.rst
+++ b/docs/changelog/changelog_body.rst
@@ -1,6 +1,46 @@
 ----------------------------------------------------
 
 
+.. _v3.0.0b4:
+
+`3.0.0b4 <https://github.com/reagento/adaptix/tree/v3.0.0b4>`_ -- 2024-03-30
+============================================================================
+
+.. _v3.0.0b4-Features:
+
+Features
+--------
+
+- Add coercer for builtin iterables and dict.
+- Models can be automatically converted inside compound types like ``Optional``, ``list``, ``dict`` etc.
+- Add :func:`.conversion.from_param` predicate factory to match only parameters
+- An error of loader, dumper, and converter generation contains a much more readable location.
+
+  For example:
+
+  - ``Linking: `Book.author_ids: list[int] -> BookDTO.author_ids: list[str]```
+  - ``Location: `Stub.f3: memoryview```
+
+.. _v3.0.0b4-Breaking Changes:
+
+Breaking Changes
+----------------
+
+- Now, parameters are automatically linked only to top-level model fields.
+  For manual linking, you can use the new :func:`adaptix.conversion.from_param` predicate factory.
+
+.. _v3.0.0b4-Bug Fixes:
+
+Bug Fixes
+---------
+
+- Fix fail to import adaptix package on python 3.8-3.10 when ``-OO`` is used.
+- Fix unexpected error on creating coercer between fields with ``Optional`` type.
+- Fix unexpected error with type vars getting from ``UnionType``.
+
+----------------------------------------------------
+
+
 .. _v3.0.0b3:
 
 `3.0.0b3 <https://github.com/reagento/adaptix/tree/v3.0.0b3>`_ -- 2024-03-08
@@ -62,7 +102,7 @@ Breaking Changes
 - Due to refactoring of predicate system required for new features:
 
   1. ``create_request_checker`` was renamed to ``create_loc_stack_checker``
-  2. ``LocStackPattern`` (class of ``P``) was renamed ``RequestPattern``
+  2. ``RequestPattern`` (class of ``P``) was renamed to ``LocStackPattern``
   3. method ``RequestPattern.build_request_checker()`` was renamed to ``LocStackPattern.build_loc_stack_checker()``
 
 .. _v3.0.0b2-Deprecations:
diff --git a/docs/common/installation.rst b/docs/common/installation.rst
index 4885b51d..2045eba7 100644
--- a/docs/common/installation.rst
+++ b/docs/common/installation.rst
@@ -2,7 +2,7 @@ Just use pip to install the library
 
 .. code-block:: text
 
-    pip install adaptix==3.0.0b3
+    pip install adaptix==3.0.0b4
 
 
 Integrations with 3-rd party libraries are turned on automatically,
@@ -24,7 +24,7 @@ the second (strict) additionally checks that the version same or older than the
    * - ``sqlalchemy``
      - ``sqlalchemy >= 2.0.0``
    * - ``sqlalchemy-strict``
-     - ``sqlalchemy >= 2.0.0, <= 2.0.27``
+     - ``sqlalchemy >= 2.0.0, <= 2.0.29``
 
 
 Extras are specified inside square brackets, separating by comma.
@@ -33,5 +33,5 @@ So, this is valid installation variants:
 
 .. code-block:: text
 
-   pip install adaptix[attrs-strict]==3.0.0b3
-   pip install adaptix[attrs, sqlalchemy-strict]==3.0.0b3
+   pip install adaptix[attrs-strict]==3.0.0b4
+   pip install adaptix[attrs, sqlalchemy-strict]==3.0.0b4
diff --git a/docs/conversion/tutorial.rst b/docs/conversion/tutorial.rst
index 57b4709e..9ab618ed 100644
--- a/docs/conversion/tutorial.rst
+++ b/docs/conversion/tutorial.rst
@@ -26,7 +26,7 @@ Adaptix can transform between any of the supported models, see :ref:`supported-m
 for exact list of models and known limitations.
 
 How it works? Adaptix scans each field of the destination model and matches it with the field of the source model.
-By default, only fields with the same name are matched. You can :ref:`override <field-linking>` this behavior.
+By default, only fields with the same name are matched. You can :ref:`override <fields-linking>` this behavior.
 
 Also, it works for nested models.
 
@@ -44,14 +44,14 @@ Upcasting
 
 All source model additional fields not found in the destination model are simply ignored.
 
-.. literalinclude:: /examples/conversion/tutorial/downcasting.py
+.. literalinclude:: /examples/conversion/tutorial/upcasting.py
 
 Downcasting
 =============
 
 Sometimes you need to add extra data to the source model. For this, you can use a special decorator.
 
-.. literalinclude:: /examples/conversion/tutorial/upcasting.py
+.. literalinclude:: /examples/conversion/tutorial/downcasting.py
 
 :func:`.conversion.impl_converter` takes an empty function and generates its body by signature.
 
@@ -60,7 +60,7 @@ because mypy forbids functions without body.
 Also, you can set this option at `mypy config <https://mypy.readthedocs.io/en/stable/config_file.html#example-mypy-ini>`_
 or supress each error individually via ``# type: ignore[empty-body]``.
 
-.. _field-linking:
+.. _fields-linking:
 
 Fields linking
 ================
@@ -78,6 +78,13 @@ will be linked with the field ``writer`` of class ``BookDTO``.
 You can use simple strings instead of ``P`` construct,
 but it will match any field with the same name despite of owner class.
 
+By default, additional parameters can replace fields only on the top-level model.
+If you want to pass this data to a nested model, you should use :func:`.conversion.from_param` predicate factory.
+
+.. literalinclude:: /examples/conversion/tutorial/nested_from_param.py
+
+If the field name differs from the parameter name, you also can use :func:`.conversion.from_param` to link them.
+
 Linking algorithm
 ===================
 
@@ -87,7 +94,8 @@ For each field of the destination model, adaptix searches a corresponding field.
 Additional parameters are checked (from right to left) before the fields.
 So, your custom linking looks among the additional parameters too.
 
-By default, fields are matched by exact name equivalence.
+By default, fields are matched by exact name equivalence,
+parameters are matched only for top-level destination model fields.
 
 After fields are matched adaptix tries to create a coercer
 that transforms data from the source field to the destination type.
@@ -95,15 +103,22 @@ that transforms data from the source field to the destination type.
 Type coercion
 ================
 
-By default, there are no implicit coercions.
+By default, there are no implicit coercions between scalar types.
 
 However, there are cases where type casting involves passing the data as is and adaptix detects its:
 
-* source type and destination type are the same
-* destination type is ``Any``
-* source type is a subclass of destination type (excluding generics)
-* source union is subset of destination union (simple ``==`` check is using)
-* source type and destination type is ``Optional`` and inner types are coercible
+- source type and destination type are the same
+- destination type is ``Any``
+- source type is a subclass of destination type (excluding generics)
+- source union is a subset of destination union (simple ``==`` check is using)
+
+Also, some compound types can be coerced if corresponding inner types are coercible:
+
+- source and destination types are models (conversion like top-level models)
+- source and destination types are ``Optional``
+- source and destination types are one of the builtin iterable
+- source and destination types are ``dict``
+
 
 You can define your own coercion rule.
 
diff --git a/docs/examples/conversion/tutorial/downcasting.py b/docs/examples/conversion/tutorial/downcasting.py
index 6463a77d..fe0becc5 100644
--- a/docs/examples/conversion/tutorial/downcasting.py
+++ b/docs/examples/conversion/tutorial/downcasting.py
@@ -1,7 +1,7 @@
+# mypy: disable-error-code="empty-body"
 from dataclasses import dataclass
-from datetime import date
 
-from adaptix.conversion import get_converter
+from adaptix.conversion import impl_converter
 
 
 @dataclass
@@ -9,9 +9,6 @@ class Book:
     title: str
     price: int
     author: str
-    release_date: date
-    page_count: int
-    isbn: str
 
 
 @dataclass
@@ -19,25 +16,28 @@ class BookDTO:
     title: str
     price: int
     author: str
+    page_count: int
+
 
+@impl_converter
+def convert_book_to_dto(book: Book, page_count: int) -> BookDTO:
+    ...
 
-convert_book_to_dto = get_converter(Book, BookDTO)
 
 assert (
     convert_book_to_dto(
-        Book(
+        book=Book(
             title="Fahrenheit 451",
             price=100,
             author="Ray Bradbury",
-            release_date=date(1953, 10, 19),
-            page_count=158,
-            isbn="978-0-7432-4722-1",
         ),
+        page_count=158,
     )
     ==
     BookDTO(
         title="Fahrenheit 451",
         price=100,
         author="Ray Bradbury",
+        page_count=158,
     )
 )
diff --git a/docs/examples/conversion/tutorial/nested_from_param.py b/docs/examples/conversion/tutorial/nested_from_param.py
new file mode 100644
index 00000000..ee082c7b
--- /dev/null
+++ b/docs/examples/conversion/tutorial/nested_from_param.py
@@ -0,0 +1,43 @@
+# mypy: disable-error-code="empty-body"
+from dataclasses import dataclass
+
+from adaptix import P
+from adaptix.conversion import from_param, impl_converter, link
+
+
+@dataclass
+class Person:
+    name: str
+
+
+@dataclass
+class Book:
+    title: str
+    author: Person
+
+
+@dataclass
+class PersonDTO:
+    name: str
+    rating: float
+
+
+@dataclass
+class BookDTO:
+    title: str
+    author: PersonDTO
+
+
+@impl_converter(recipe=[link(from_param("author_rating"), P[PersonDTO].rating)])
+def convert_book_to_dto(book: Book, author_rating: float) -> BookDTO:
+    ...
+
+
+assert (
+    convert_book_to_dto(
+        Book(title="Fahrenheit 451", author=Person("Ray Bradbury")),
+        4.8,
+    )
+    ==
+    BookDTO(title="Fahrenheit 451", author=PersonDTO("Ray Bradbury", 4.8))
+)
diff --git a/docs/examples/conversion/tutorial/putting_together.py b/docs/examples/conversion/tutorial/putting_together.py
index 8a9bda4a..3eca56dc 100644
--- a/docs/examples/conversion/tutorial/putting_together.py
+++ b/docs/examples/conversion/tutorial/putting_together.py
@@ -4,6 +4,7 @@
 from uuid import UUID
 
 from adaptix import P
+from adaptix._internal.conversion.facade.provider import from_param
 from adaptix.conversion import coercer, impl_converter, link
 
 
@@ -40,7 +41,7 @@ class BookDTO:
 
 @impl_converter(
     recipe=[
-        link("pages_len", "page_count"),
+        link(from_param("pages_len"), P[BookDTO].page_count),
         link(P[Book].author, P[BookDTO].writer),
         coercer(UUID, str, func=str),
         coercer(P[Author].birthday, P[AuthorDTO].birthday, date.isoformat),
diff --git a/docs/examples/conversion/tutorial/upcasting.py b/docs/examples/conversion/tutorial/upcasting.py
index fe0becc5..6463a77d 100644
--- a/docs/examples/conversion/tutorial/upcasting.py
+++ b/docs/examples/conversion/tutorial/upcasting.py
@@ -1,7 +1,7 @@
-# mypy: disable-error-code="empty-body"
 from dataclasses import dataclass
+from datetime import date
 
-from adaptix.conversion import impl_converter
+from adaptix.conversion import get_converter
 
 
 @dataclass
@@ -9,6 +9,9 @@ class Book:
     title: str
     price: int
     author: str
+    release_date: date
+    page_count: int
+    isbn: str
 
 
 @dataclass
@@ -16,28 +19,25 @@ class BookDTO:
     title: str
     price: int
     author: str
-    page_count: int
-
 
-@impl_converter
-def convert_book_to_dto(book: Book, page_count: int) -> BookDTO:
-    ...
 
+convert_book_to_dto = get_converter(Book, BookDTO)
 
 assert (
     convert_book_to_dto(
-        book=Book(
+        Book(
             title="Fahrenheit 451",
             price=100,
             author="Ray Bradbury",
+            release_date=date(1953, 10, 19),
+            page_count=158,
+            isbn="978-0-7432-4722-1",
         ),
-        page_count=158,
     )
     ==
     BookDTO(
         title="Fahrenheit 451",
         price=100,
         author="Ray Bradbury",
-        page_count=158,
     )
 )
diff --git a/docs/examples/loading-and-dumping/tutorial/validators.py b/docs/examples/loading-and-dumping/tutorial/validators.py
index c99a8f12..02d9667a 100644
--- a/docs/examples/loading-and-dumping/tutorial/validators.py
+++ b/docs/examples/loading-and-dumping/tutorial/validators.py
@@ -29,7 +29,7 @@ class Book:
     assert e.exceptions[0].msg == "value must be greater or equal 0"
 
 
-class BelowZero(LoadError):
+class BelowZeroError(LoadError):
     def __init__(self, actual_value: int):
         self.actual_value = actual_value
 
@@ -39,7 +39,7 @@ def __str__(self):
 
 retort = Retort(
     recipe=[
-        validator(P[Book].price, lambda x: x >= 0, lambda x: BelowZero(x)),
+        validator(P[Book].price, lambda x: x >= 0, lambda x: BelowZeroError(x)),
     ],
 )
 
@@ -47,5 +47,5 @@ def __str__(self):
     retort.load(data, Book)
 except AggregateLoadError as e:
     assert len(e.exceptions) == 1
-    assert isinstance(e.exceptions[0], BelowZero)
+    assert isinstance(e.exceptions[0], BelowZeroError)
     assert e.exceptions[0].actual_value == -10
diff --git a/docs/overview.rst b/docs/overview.rst
index b5e12e24..3c65ec42 100644
--- a/docs/overview.rst
+++ b/docs/overview.rst
@@ -16,7 +16,7 @@ Installation
 
 .. code-block:: text
 
-    pip install adaptix==3.0.0b3
+    pip install adaptix==3.0.0b4
 
 
 Example
diff --git a/docs/reference/integrations.rst b/docs/reference/integrations.rst
index d7708fc9..621271ff 100644
--- a/docs/reference/integrations.rst
+++ b/docs/reference/integrations.rst
@@ -46,7 +46,7 @@ Known limitations:
   - Only mapping to ``Table`` is supported,
     implementations for ``FromClause`` instances such as ``Subquery`` and ``Join`` are not provided.
 
-  - Mapping to ``dataclass`` and ``attrs`` are not supported.
+  - ``dataclass`` and ``attrs`` mapped by sqlalchemy are not supported for introspection.
 
   - It does not support registering order of mapped fields by design,
     so you should use manual mapping to list instead automatic ``as_list=True``.
diff --git a/examples/api_division/money.py b/examples/api_division/money.py
index c1e7e9d6..13852797 100644
--- a/examples/api_division/money.py
+++ b/examples/api_division/money.py
@@ -3,7 +3,7 @@
 from typing import Union
 
 
-class TooPreciseAmount(Exception):
+class TooPreciseAmountError(Exception):
     pass
 
 
@@ -34,7 +34,7 @@ def rubles(self) -> Decimal:
     def from_decimal_rubles(cls, rubles_: Decimal):
         kopek = rubles_ * 100
         if kopek % 1 != 0:
-            raise TooPreciseAmount
+            raise TooPreciseAmountError
         return cls(int(kopek))
 
 
diff --git a/examples/api_division/retort.py b/examples/api_division/retort.py
index cec10998..1cf830f3 100644
--- a/examples/api_division/retort.py
+++ b/examples/api_division/retort.py
@@ -8,7 +8,7 @@
 from adaptix.load_error import ExtraFieldsLoadError, ValueLoadError
 
 from .models import Receipt, ReceiptType, RecItem
-from .money import Money, TooPreciseAmount
+from .money import Money, TooPreciseAmountError
 
 
 def load_phone_number(num_obj: PhoneNumber):
@@ -45,7 +45,7 @@ def outer_phonenumber_loader(data: str):
 def money_loader(data):
     try:
         return Money.from_decimal_rubles(data)
-    except TooPreciseAmount:
+    except TooPreciseAmountError:
         raise ValueLoadError("Rubles cannot have more than 2 decimal places", data)
 
 
diff --git a/justfile b/justfile
index c09d82e3..455b018c 100644
--- a/justfile
+++ b/justfile
@@ -60,6 +60,9 @@ doc_target := "docs-build"
     sphinx-build -M clean {{ doc_source }} {{ doc_target }}
 
 
+@changelog version='Preview':
+    towncrier build --keep --version {{ version }}
+
 # Continious integration
 
 [private]
diff --git a/mypy.ini b/mypy.ini
deleted file mode 100644
index 40e4303d..00000000
--- a/mypy.ini
+++ /dev/null
@@ -1,13 +0,0 @@
-[mypy]
-ignore_missing_imports = True
-
-show_error_codes = True
-warn_unused_configs = True
-no_implicit_optional = True
-
-warn_unused_ignores = True
-warn_redundant_casts = True
-warn_unreachable = True
-disallow_subclassing_any = True
-# disallow_any_generics = True  # maybe turn on in the future
-
diff --git a/pyproject.toml b/pyproject.toml
index 52c22cb5..f002d02f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -37,7 +37,7 @@ classifiers = [
 attrs = ['attrs >= 21.3.0']
 attrs-strict = ['attrs >= 21.3.0, <= 23.2.0']
 sqlalchemy = ['sqlalchemy >= 2.0.0']
-sqlalchemy-strict = ['sqlalchemy >= 2.0.0, <= 2.0.27']
+sqlalchemy-strict = ['sqlalchemy >= 2.0.0, <= 2.0.29']
 
 [project.urls]
 'Homepage' = 'https://github.com/reagento/adaptix'
@@ -57,13 +57,9 @@ adaptix = ['py.typed']
 name = 'A. Tikhonov'
 email = '17@itishka.org'
 
-
-[tool.isort]
-multi_line_output = 3
-line_length = 120
-include_trailing_comma = true
-combine_as_imports = true
-remove_redundant_aliases = true
+#   ┌             ┐
+#   │   TESTING   │
+#   └             ┘
 
 [tool.pytest.ini_options]
 python_classes = 'WeDoNotUseClassTestCase'
@@ -71,20 +67,46 @@ python_files = ['test_*.py', '*_test.py', 'bench_[!nexus]*.py', 'tests_helpers.p
 testpaths = ['tests', 'examples']
 filterwarnings = ['ignore::adaptix.TypedDictAt38Warning']
 
-[tool.bandit]
-skips = ['B102', 'B106', 'B404', 'B603', 'B607']
+[tool.coverage.run]
+branch = true
+relative_files = true
+include = ["src/**"]
+
+#   ┌             ┐
+#   │   LINTING   │
+#   └             ┘
+
+[tool.mypy]
+ignore_missing_imports = true
+
+show_error_codes = true
+warn_unused_configs = true
+no_implicit_optional = true
+
+warn_unused_ignores = true
+warn_redundant_casts = true
+warn_unreachable = true
+disallow_subclassing_any = true
+# disallow_any_generics = true # maybe turn on in the future
+local_partial_types = true
+
+
+[tool.isort]
+multi_line_output = 3
+line_length = 120
+include_trailing_comma = true
+combine_as_imports = true
+remove_redundant_aliases = true
 
-[tool.bandit.assert_used]
-skips = ['*/test_*.py', '*/*_test.py', 'docs/examples/*', '*/bench_*.py']
 
 [tool.vulture]
 min_confidence = 60
 ignore_decorators = ['@_aspect_storage.add', '@overload', '@abstractmethod']
 
+
 [tool.ruff]
 line-length = 120
 
-
 [tool.ruff.lint]
 select = ['ALL']
 fixable = [
@@ -128,6 +150,7 @@ ignore = [
   'TCH001',
   'TD002',
   'PTH201',
+  'RSE102',
 
   # Rules conflictin with other tools
   'I001',
@@ -157,6 +180,11 @@ keep-runtime-typing = true
 parametrize-names-type = "list"
 
 
+#   ┌             ┐
+#   │    OTHER    │
+#   └             ┘
+
+
 [tool.towncrier]
 package = 'adaptix'
 filename = 'docs/changelog/changelog_body.rst'
@@ -171,9 +199,3 @@ type = [
   { name = "Bug Fixes",        directory = "bugfix",      showcontent = true },
   { name = "Other",            directory = "other",       showcontent = true },
 ]
-
-
-[tool.coverage.run]
-branch = true
-relative_files = true
-include = ["src/**"]
diff --git a/requirements/dev.txt b/requirements/dev.txt
index af18437c..75a4f979 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -16,16 +16,12 @@ astpath==0.9.1
     # via
     #   -r requirements/raw/lint.txt
     #   astpath
-astroid==3.0.2
-    # via pylint
 attrs==23.2.0
     # via
     #   -r requirements/raw/test_extra_new.txt
     #   cattrs
 babel==2.14.0
     # via sphinx
-bandit==1.7.6
-    # via -r requirements/raw/lint.txt
 beautifulsoup4==4.12.2
     # via furo
 build==1.0.3
@@ -60,8 +56,6 @@ cycler==0.12.1
     # via matplotlib
 dataclass-factory==2.16
     # via -r requirements/raw/bench.txt
-dill==0.3.7
-    # via pylint
 dirty-equals==0.7.1.post0
     # via -r requirements/raw/test_extra_none.txt
 distlib==0.3.8
@@ -90,9 +84,7 @@ furo==2023.9.10
 gitdb==4.0.11
     # via gitpython
 gitpython==3.1.40
-    # via
-    #   -r requirements/raw/doc.txt
-    #   bandit
+    # via -r requirements/raw/doc.txt
 greenlet==3.0.2
     # via
     #   -r requirements/raw/test_extra_none.txt
@@ -109,8 +101,6 @@ iniconfig==2.0.0
     # via pytest
 invoke==2.2.0
     # via -r requirements/raw/runner.txt
-isort==5.13.2
-    # via pylint
 jinja2==3.1.2
     # via
     #   myst-parser
@@ -126,7 +116,6 @@ markdown-it-py==3.0.0
     # via
     #   mdit-py-plugins
     #   myst-parser
-    #   rich
 markupsafe==2.1.3
     # via jinja2
 marshmallow==3.20.1
@@ -136,16 +125,14 @@ mashumaro==3.10
 matplotlib==3.8.2
     # via -r requirements/raw/_bench-orchestrate.txt
 mccabe==0.7.0
-    # via
-    #   flake8
-    #   pylint
+    # via flake8
 mdit-py-plugins==0.4.0
     # via myst-parser
 mdurl==0.1.2
     # via markdown-it-py
 msgspec==0.18.4 ; implementation_name != "pypy"
     # via -r requirements/raw/bench.txt
-mypy==1.7.1
+mypy==1.9.0
     # via -r requirements/raw/lint.txt
 mypy-extensions==1.0.0
     # via mypy
@@ -168,9 +155,7 @@ packaging==23.2
     #   sphinx
     #   tox
 pbr==6.0.0
-    # via
-    #   sphinxcontrib-apidoc
-    #   stevedore
+    # via sphinxcontrib-apidoc
 phonenumberslite==8.13.26
     # via -r requirements/raw/test_extra_none.txt
 pillow==10.2.0
@@ -179,7 +164,6 @@ pip-tools==7.3.0
     # via -r requirements/raw/dev.txt
 platformdirs==4.1.0
     # via
-    #   pylint
     #   tox
     #   virtualenv
 plotly==5.18.0
@@ -190,7 +174,7 @@ pluggy==1.3.0
     # via
     #   pytest
     #   tox
-pre-commit==3.6.0
+pre-commit==3.6.2
     # via -r requirements/raw/lint.txt
 psutil==5.9.5
     # via
@@ -207,10 +191,7 @@ pyflakes==3.1.0
 pygments==2.17.2
     # via
     #   furo
-    #   rich
     #   sphinx
-pylint==3.0.3
-    # via -r requirements/raw/lint.txt
 pyparsing==3.1.1
     # via matplotlib
 pyperf==2.6.1
@@ -229,16 +210,13 @@ pytz==2023.3.post1
     # via dirty-equals
 pyyaml==6.0.1
     # via
-    #   bandit
     #   myst-parser
     #   pre-commit
 radon==6.0.1
     # via -r requirements/raw/lint.txt
 requests==2.31.0
     # via sphinx
-rich==13.7.0
-    # via bandit
-ruff==0.3.0
+ruff==0.3.3
     # via -r requirements/raw/lint.txt
 schematics==2.1.1
     # via -r requirements/raw/bench.txt
@@ -297,18 +275,14 @@ sphinxcontrib-serializinghtml==1.1.9
     # via sphinx
 sphinxext-opengraph==0.9.1
     # via -r requirements/raw/doc.txt
-sqlalchemy==2.0.27
+sqlalchemy==2.0.29
     # via
     #   -r requirements/raw/test_extra_new.txt
     #   -r requirements/raw/test_extra_none.txt
-stevedore==5.1.0
-    # via bandit
 tenacity==8.2.3
     # via plotly
 toml==0.10.2
     # via vulture
-tomlkit==0.12.3
-    # via pylint
 towncrier==23.11.0
     # via -r requirements/raw/dev.txt
 tox==4.11.4
diff --git a/requirements/lint.txt b/requirements/lint.txt
index 9c1daceb..d7f23e21 100644
--- a/requirements/lint.txt
+++ b/requirements/lint.txt
@@ -16,16 +16,12 @@ astpath==0.9.1
     # via
     #   -r requirements/raw/lint.txt
     #   astpath
-astroid==3.0.2
-    # via pylint
 attrs==23.2.0
     # via
     #   -r requirements/raw/test_extra_new.txt
     #   cattrs
 babel==2.14.0
     # via sphinx
-bandit==1.7.6
-    # via -r requirements/raw/lint.txt
 beautifulsoup4==4.12.2
     # via furo
 cattrs==23.1.2
@@ -46,8 +42,6 @@ cycler==0.12.1
     # via matplotlib
 dataclass-factory==2.16
     # via -r requirements/raw/bench.txt
-dill==0.3.7
-    # via pylint
 dirty-equals==0.7.1.post0
     # via -r requirements/raw/test_extra_none.txt
 distlib==0.3.8
@@ -74,9 +68,7 @@ furo==2023.9.10
 gitdb==4.0.11
     # via gitpython
 gitpython==3.1.40
-    # via
-    #   -r requirements/raw/doc.txt
-    #   bandit
+    # via -r requirements/raw/doc.txt
 greenlet==3.0.2
     # via
     #   -r requirements/raw/test_extra_none.txt
@@ -89,8 +81,6 @@ imagesize==1.4.1
     # via sphinx
 iniconfig==2.0.0
     # via pytest
-isort==5.13.2
-    # via pylint
 jinja2==3.1.2
     # via
     #   myst-parser
@@ -105,7 +95,6 @@ markdown-it-py==3.0.0
     # via
     #   mdit-py-plugins
     #   myst-parser
-    #   rich
 markupsafe==2.1.3
     # via jinja2
 marshmallow==3.20.1
@@ -115,16 +104,14 @@ mashumaro==3.10
 matplotlib==3.8.2
     # via -r requirements/raw/_bench-orchestrate.txt
 mccabe==0.7.0
-    # via
-    #   flake8
-    #   pylint
+    # via flake8
 mdit-py-plugins==0.4.0
     # via myst-parser
 mdurl==0.1.2
     # via markdown-it-py
 msgspec==0.18.4 ; implementation_name != "pypy"
     # via -r requirements/raw/bench.txt
-mypy==1.7.1
+mypy==1.9.0
     # via -r requirements/raw/lint.txt
 mypy-extensions==1.0.0
     # via mypy
@@ -144,24 +131,20 @@ packaging==23.2
     #   pytest
     #   sphinx
 pbr==6.0.0
-    # via
-    #   sphinxcontrib-apidoc
-    #   stevedore
+    # via sphinxcontrib-apidoc
 phonenumberslite==8.13.26
     # via -r requirements/raw/test_extra_none.txt
 pillow==10.2.0
     # via matplotlib
 platformdirs==4.1.0
-    # via
-    #   pylint
-    #   virtualenv
+    # via virtualenv
 plotly==5.18.0
     # via
     #   -r requirements/raw/_bench-orchestrate.txt
     #   -r requirements/raw/doc.txt
 pluggy==1.3.0
     # via pytest
-pre-commit==3.6.0
+pre-commit==3.6.2
     # via -r requirements/raw/lint.txt
 psutil==5.9.5
     # via
@@ -178,10 +161,7 @@ pyflakes==3.1.0
 pygments==2.17.2
     # via
     #   furo
-    #   rich
     #   sphinx
-pylint==3.0.3
-    # via -r requirements/raw/lint.txt
 pyparsing==3.1.1
     # via matplotlib
 pyperf==2.6.1
@@ -196,16 +176,13 @@ pytz==2023.3.post1
     # via dirty-equals
 pyyaml==6.0.1
     # via
-    #   bandit
     #   myst-parser
     #   pre-commit
 radon==6.0.1
     # via -r requirements/raw/lint.txt
 requests==2.31.0
     # via sphinx
-rich==13.7.0
-    # via bandit
-ruff==0.3.0
+ruff==0.3.3
     # via -r requirements/raw/lint.txt
 schematics==2.1.1
     # via -r requirements/raw/bench.txt
@@ -264,18 +241,14 @@ sphinxcontrib-serializinghtml==1.1.9
     # via sphinx
 sphinxext-opengraph==0.9.1
     # via -r requirements/raw/doc.txt
-sqlalchemy==2.0.27
+sqlalchemy==2.0.29
     # via
     #   -r requirements/raw/test_extra_new.txt
     #   -r requirements/raw/test_extra_none.txt
-stevedore==5.1.0
-    # via bandit
 tenacity==8.2.3
     # via plotly
 toml==0.10.2
     # via vulture
-tomlkit==0.12.3
-    # via pylint
 typing-extensions==4.9.0
     # via
     #   -r requirements/raw/test_extra_none.txt
diff --git a/requirements/raw/lint.txt b/requirements/raw/lint.txt
index de5acc55..6248bf4a 100644
--- a/requirements/raw/lint.txt
+++ b/requirements/raw/lint.txt
@@ -2,16 +2,13 @@
 -r _bench-orchestrate.txt
 -r doc.txt
 
-pre-commit==3.6.0
-bandit==1.7.6
+pre-commit==3.6.2
 
-mypy==1.7.1
+mypy==1.9.0
 
 vulture==2.10
 
-pylint==3.0.3
-
-ruff==0.3.0
+ruff==0.3.3
 
 radon==6.0.1
 dlint==0.14.1
diff --git a/src/adaptix/_internal/code_tools/ast_templater.py b/src/adaptix/_internal/code_tools/ast_templater.py
index c5e4c59d..31b11e26 100644
--- a/src/adaptix/_internal/code_tools/ast_templater.py
+++ b/src/adaptix/_internal/code_tools/ast_templater.py
@@ -9,7 +9,7 @@ class Substitutor(NodeTransformer):
     def __init__(self, substitution: Mapping[str, AST]):
         self._substitution = substitution
 
-    def visit_Name(self, node: ast.Name):  # pylint: disable=invalid-name  # noqa: N802
+    def visit_Name(self, node: ast.Name):  # noqa: N802
         if node.id in self._substitution:
             return self._substitution[node.id]
         return node
diff --git a/src/adaptix/_internal/code_tools/cascade_namespace.py b/src/adaptix/_internal/code_tools/cascade_namespace.py
index 93bc4988..2eb57d61 100644
--- a/src/adaptix/_internal/code_tools/cascade_namespace.py
+++ b/src/adaptix/_internal/code_tools/cascade_namespace.py
@@ -23,16 +23,18 @@ def try_register_var(self, name: str) -> bool:
 
 
 class BuiltinCascadeNamespace(CascadeNamespace):
-    __slots__ = ("_constants", "_occupied", "_variables", "_allow_builtins")
+    __slots__ = ("_constants", "_outer_constants", "_occupied", "_variables", "_allow_builtins")
 
     def __init__(
         self,
         constants: Optional[Mapping[str, object]] = None,
+        outer_constants: Optional[Mapping[str, object]] = None,
         occupied: Optional[AbstractSet[str]] = None,
         *,
         allow_builtins: bool = False,
     ):
         self._constants = {} if constants is None else dict(constants)
+        self._outer_constants = {} if outer_constants is None else dict(outer_constants)
         self._occupied = set() if occupied is None else occupied
         self._variables: Set[str] = set()
         self._allow_builtins = allow_builtins
@@ -41,6 +43,7 @@ def try_add_constant(self, name: str, value: object) -> bool:
         if (
             name in self._occupied
             or name in self._variables
+            or name in self._outer_constants
             or (name in NAME_TO_BUILTIN and not self._allow_builtins)
         ):
             return False
@@ -49,6 +52,17 @@ def try_add_constant(self, name: str, value: object) -> bool:
         self._constants[name] = value
         return True
 
+    def try_add_outer_constant(self, name: str, value: object) -> bool:
+        if (
+            name in self._constants
+            or (name in NAME_TO_BUILTIN and not self._allow_builtins)
+        ):
+            return False
+        if name in self._outer_constants:
+            return value is self._outer_constants[name]
+        self._outer_constants[name] = value
+        return True
+
     def try_register_var(self, name: str) -> bool:
         if (
             name in self._occupied
@@ -64,10 +78,14 @@ def add_constant(self, name: str, value: object) -> None:
         if not self.try_add_constant(name, value):
             raise KeyError(f"Key {name} is duplicated")
 
+    def add_outer_constant(self, name: str, value: object) -> None:
+        if not self.try_add_outer_constant(name, value):
+            raise KeyError(f"Key {name} is duplicated")
+
     def register_var(self, name: str) -> None:
         if not self.try_register_var(name):
             raise KeyError(f"Key {name} is duplicated")
 
     @property
-    def constants(self) -> Mapping[str, object]:
-        return self._constants
+    def all_constants(self) -> Mapping[str, object]:
+        return {**self._constants, **self._outer_constants}
diff --git a/src/adaptix/_internal/code_tools/code_builder.py b/src/adaptix/_internal/code_tools/code_builder.py
index 320ab751..06a2fb4a 100644
--- a/src/adaptix/_internal/code_tools/code_builder.py
+++ b/src/adaptix/_internal/code_tools/code_builder.py
@@ -82,11 +82,10 @@ def __exit__(self, exc_type, exc_val, exc_tb):
         self._current_indent -= self._indent_delta
 
     def extend(self: CB, other: CB) -> CB:
-        self._add_indented_lines(other._lines)  # pylint: disable=protected-access
+        self._add_indented_lines(other._lines)
         return self
 
     def extend_including(self: CB, other: CB) -> CB:
-        # pylint: disable=protected-access
         if not other._lines:
             return self
         self._include_line(other._lines[0])
@@ -94,7 +93,7 @@ def extend_including(self: CB, other: CB) -> CB:
         return self
 
     def extend_above(self: CB, other: CB) -> CB:
-        self._lines.extendleft(reversed(other._lines))  # pylint: disable=protected-access
+        self._lines.extendleft(reversed(other._lines))
         return self
 
     def string(self) -> str:
diff --git a/src/adaptix/_internal/code_tools/compiler.py b/src/adaptix/_internal/code_tools/compiler.py
index 4e8ef8f5..8d568cce 100644
--- a/src/adaptix/_internal/code_tools/compiler.py
+++ b/src/adaptix/_internal/code_tools/compiler.py
@@ -1,4 +1,3 @@
-# pylint: disable=exec-used
 import linecache
 from abc import ABC, abstractmethod
 from collections import defaultdict
diff --git a/src/adaptix/_internal/code_tools/utils.py b/src/adaptix/_internal/code_tools/utils.py
index 5b361c90..e70a6b2e 100644
--- a/src/adaptix/_internal/code_tools/utils.py
+++ b/src/adaptix/_internal/code_tools/utils.py
@@ -17,7 +17,6 @@ class _CannotBeRenderedError(Exception):
 
 
 def get_literal_expr(obj: object) -> Optional[str]:
-    # pylint: disable=unidiomatic-typecheck,too-many-return-statements
     if type(obj) in (int, str, bytes, bytearray):
         return repr(obj)
     if type(obj) is float:
@@ -55,7 +54,6 @@ def _try_sort(iterable):
 
 
 def _get_complex_literal_expr(obj: object) -> Optional[str]:  # noqa: PLR0911
-    # pylint: disable=unidiomatic-typecheck,too-many-return-statements
     if type(obj) is list:
         return _parenthesize("[]", obj)
 
diff --git a/src/adaptix/_internal/common.py b/src/adaptix/_internal/common.py
index 1d56456f..bae88ea4 100644
--- a/src/adaptix/_internal/common.py
+++ b/src/adaptix/_internal/common.py
@@ -7,7 +7,8 @@
 Loader = Callable[[Any], V_co]
 Dumper = Callable[[K_contra], Any]
 Converter = Callable[..., Any]
-Coercer = Callable[[Any], Any]
+Coercer = Callable[[Any, Any], Any]
+OneArgCoercer = Callable[[Any], Any]
 
 TypeHint = Any
 
@@ -17,6 +18,6 @@
 
 # https://github.com/python/typing/issues/684#issuecomment-548203158
 if TYPE_CHECKING:
-    EllipsisType = ellipsis  # pylint: disable=undefined-variable  # noqa: F821
+    EllipsisType = ellipsis  # noqa: F821
 else:
     EllipsisType = type(Ellipsis)
diff --git a/src/adaptix/_internal/conversion/broaching/code_generator.py b/src/adaptix/_internal/conversion/broaching/code_generator.py
index c646cc75..89558e82 100644
--- a/src/adaptix/_internal/conversion/broaching/code_generator.py
+++ b/src/adaptix/_internal/conversion/broaching/code_generator.py
@@ -3,9 +3,8 @@
 from abc import ABC, abstractmethod
 from ast import AST
 from collections import defaultdict
-from functools import update_wrapper
 from inspect import Signature
-from typing import Callable, DefaultDict, Mapping, Optional, Tuple, Union
+from typing import DefaultDict, Mapping, Tuple, Union
 
 from ...code_tools.ast_templater import ast_substitute
 from ...code_tools.cascade_namespace import BuiltinCascadeNamespace, CascadeNamespace
@@ -14,7 +13,7 @@
 from ...code_tools.utils import get_literal_expr, get_literal_from_factory
 from ...compat import compat_ast_unparse
 from ...model_tools.definitions import DescriptorAccessor, ItemAccessor
-from ...special_cases_optimization import as_is_stub
+from ...special_cases_optimization import as_is_stub, as_is_stub_with_ctx
 from .definitions import (
     AccessorElement,
     ConstantElement,
@@ -60,12 +59,7 @@ def register_mangled(self, base: str, obj: object) -> str:
 
 class BroachingCodeGenerator(ABC):
     @abstractmethod
-    def produce_code(
-        self,
-        signature: Signature,
-        closure_name: str,
-        stub_function: Optional[Callable],
-    ) -> Tuple[str, Mapping[str, object]]:
+    def produce_code(self, signature: Signature, closure_name: str) -> Tuple[str, Mapping[str, object]]:
         ...
 
 
@@ -80,19 +74,12 @@ def _create_state(self, namespace: CascadeNamespace) -> GenState:
             name_sanitizer=self._name_sanitizer,
         )
 
-    def produce_code(
-        self,
-        signature: Signature,
-        closure_name: str,
-        stub_function: Optional[Callable],
-    ) -> Tuple[str, Mapping[str, object]]:
+    def produce_code(self, signature: Signature, closure_name: str) -> Tuple[str, Mapping[str, object]]:
         builder = CodeBuilder()
         namespace = BuiltinCascadeNamespace(occupied=signature.parameters.keys())
         state = self._create_state(namespace=namespace)
 
-        namespace.add_constant("_closure_signature", signature)
-        namespace.add_constant("_stub_function", stub_function)
-        namespace.add_constant("_update_wrapper", update_wrapper)
+        namespace.add_outer_constant("_closure_signature", signature)
         no_types_signature = signature.replace(
             parameters=[param.replace(annotation=Signature.empty) for param in signature.parameters.values()],
             return_annotation=Signature.empty,
@@ -101,12 +88,9 @@ def produce_code(
             body = self._gen_plan_element_dispatch(state, self._plan)
             builder += "return " + compat_ast_unparse(body)
 
-        if stub_function is not None:
-            builder += f"_update_wrapper({closure_name}, _stub_function)"
-
         builder += f"{closure_name}.__signature__ = _closure_signature"
         builder += f"{closure_name}.__name__ = {closure_name!r}"
-        return builder.string(), namespace.constants
+        return builder.string(), namespace.all_constants
 
     def _gen_plan_element_dispatch(self, state: GenState, element: BroachingPlan) -> AST:
         if isinstance(element, ParameterElement):
@@ -138,6 +122,14 @@ def _gen_function_element(self, state: GenState, element: FunctionElement[Broach
         ):
             return self._gen_plan_element_dispatch(state, element.args[0].element)
 
+        if (
+            element.func == as_is_stub_with_ctx
+            and len(element.args) == 2  # noqa: PLR2004
+            and isinstance(element.args[0], PositionalArg)
+            and isinstance(element.args[1], PositionalArg)
+        ):
+            return self._gen_plan_element_dispatch(state, element.args[0].element)
+
         if not element.args:
             literal = get_literal_from_factory(element.func)
             if literal is not None:
@@ -148,6 +140,9 @@ def _gen_function_element(self, state: GenState, element: FunctionElement[Broach
         else:
             name = state.register_next_id("func", element.func)
 
+        return self._gen_function_call(state, element, name)
+
+    def _gen_function_call(self, state: GenState, element: FunctionElement[BroachingPlan], name: str) -> AST:
         args = []
         keywords = []
         for arg in element.args:
diff --git a/src/adaptix/_internal/conversion/coercer_provider.py b/src/adaptix/_internal/conversion/coercer_provider.py
index 96a5c856..50f3d92c 100644
--- a/src/adaptix/_internal/conversion/coercer_provider.py
+++ b/src/adaptix/_internal/conversion/coercer_provider.py
@@ -1,149 +1,150 @@
+import collections.abc
 from abc import ABC, abstractmethod
+from collections import deque
 from dataclasses import replace
-from typing import Any, Union
+from typing import Any, Callable, Tuple, Union, final
 
-from ..common import Coercer
+from ..common import Coercer, OneArgCoercer, TypeHint
 from ..provider.essential import CannotProvide, Mediator
 from ..provider.loc_stack_filtering import LocStackChecker
+from ..provider.location import GenericParamLoc
 from ..provider.request_cls import try_normalize_type
-from ..provider.static_provider import StaticProvider, static_provision_action
-from ..special_cases_optimization import as_is_stub
-from ..type_tools import BaseNormType, is_generic, is_parametrized, is_subclass_soft, strip_tags
+from ..special_cases_optimization import as_is_stub, as_is_stub_with_ctx
+from ..type_tools import BaseNormType, is_generic, is_parametrized, is_subclass_soft, normalize_type, strip_tags
+from .provider_template import CoercerProvider
 from .request_cls import CoercerRequest
 
 
-class CoercerProvider(StaticProvider, ABC):
-    @static_provision_action
-    @abstractmethod
-    def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coercer:
-        ...
-
-
-class SameTypeCoercerProvider(CoercerProvider):
+class NormTypeCoercerProvider(CoercerProvider, ABC):
+    @final
     def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coercer:
-        src_tp = request.src[-1].type
-        dst_tp = request.dst[-1].type
-
-        if src_tp == dst_tp:
-            return as_is_stub
+        norm_src = try_normalize_type(request.src.last.type)
+        norm_dst = try_normalize_type(request.dst.last.type)
+        return self._provide_coercer_norm_types(mediator, request, norm_src, norm_dst)
 
-        norm_src = try_normalize_type(src_tp)
-        norm_dst = try_normalize_type(dst_tp)
-        if strip_tags(norm_src) == strip_tags(norm_dst):
-            return as_is_stub
-        raise CannotProvide
+    @abstractmethod
+    def _provide_coercer_norm_types(
+        self,
+        mediator: Mediator,
+        request: CoercerRequest,
+        norm_src: BaseNormType,
+        norm_dst: BaseNormType,
+    ) -> Coercer:
+        ...
 
 
-class DstAnyCoercerProvider(CoercerProvider):
-    def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coercer:
-        dst_tp = request.dst[-1].type
-        norm_dst = strip_tags(try_normalize_type(dst_tp))
-        if norm_dst.origin == Any:
-            return as_is_stub
+class SameTypeCoercerProvider(NormTypeCoercerProvider):
+    def _provide_coercer_norm_types(
+        self,
+        mediator: Mediator,
+        request: CoercerRequest,
+        norm_src: BaseNormType,
+        norm_dst: BaseNormType,
+    ) -> Coercer:
+        if norm_src == norm_dst:
+            return as_is_stub_with_ctx
         raise CannotProvide
 
 
-class StrippedTypeCoercerProvider(CoercerProvider, ABC):
-    def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coercer:
-        src_tp = request.src[-1].type
-        dst_tp = request.dst[-1].type
-        norm_src = try_normalize_type(src_tp)
-        norm_dst = try_normalize_type(dst_tp)
-        stripped_src = strip_tags(norm_src)
-        stripped_dst = strip_tags(norm_dst)
-        return self._provide_coercer_stripped_types(mediator, request, stripped_src, stripped_dst)
-
-    @abstractmethod
-    def _provide_coercer_stripped_types(
+class DstAnyCoercerProvider(NormTypeCoercerProvider):
+    def _provide_coercer_norm_types(
         self,
         mediator: Mediator,
         request: CoercerRequest,
-        stripped_src: BaseNormType,
-        stripped_dst: BaseNormType,
+        norm_src: BaseNormType,
+        norm_dst: BaseNormType,
     ) -> Coercer:
-        ...
+        if norm_dst.origin == Any:
+            return as_is_stub_with_ctx
+        raise CannotProvide
 
 
-class SubclassCoercerProvider(StrippedTypeCoercerProvider):
-    def _provide_coercer_stripped_types(
+class SubclassCoercerProvider(NormTypeCoercerProvider):
+    def _provide_coercer_norm_types(
         self,
         mediator: Mediator,
         request: CoercerRequest,
-        stripped_src: BaseNormType,
-        stripped_dst: BaseNormType,
+        norm_src: BaseNormType,
+        norm_dst: BaseNormType,
     ) -> Coercer:
         if (
-            is_generic(stripped_src.source)
-            or is_parametrized(stripped_src.source)
-            or is_generic(stripped_dst.source)
-            or is_parametrized(stripped_dst.source)
+            is_generic(norm_src.source)
+            or is_parametrized(norm_src.source)
+            or is_generic(norm_dst.source)
+            or is_parametrized(norm_dst.source)
         ):
             raise CannotProvide
-        if is_subclass_soft(stripped_src.origin, stripped_dst.origin):
-            return as_is_stub
+        if is_subclass_soft(norm_src.origin, norm_dst.origin):
+            return as_is_stub_with_ctx
         raise CannotProvide
 
 
 class MatchingCoercerProvider(CoercerProvider):
-    def __init__(self, src_lsc: LocStackChecker, dst_lsc: LocStackChecker, coercer: Coercer):
+    def __init__(self, src_lsc: LocStackChecker, dst_lsc: LocStackChecker, coercer: OneArgCoercer):
         self._src_lsc = src_lsc
         self._dst_lsc = dst_lsc
-        self._coercer = coercer
+        self._one_arg_coercer = coercer
 
     def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coercer:
         if (
-            self._src_lsc.check_loc_stack(mediator, request.src.to_loc_stack())
-            and self._dst_lsc.check_loc_stack(mediator, request.dst.to_loc_stack())
+            self._src_lsc.check_loc_stack(mediator, request.src)
+            and self._dst_lsc.check_loc_stack(mediator, request.dst)
         ):
-            return self._coercer
+            if self._one_arg_coercer == as_is_stub:
+                return as_is_stub_with_ctx
+            one_arg_coercer = self._one_arg_coercer
+            return lambda x, ctx: one_arg_coercer(x)
         raise CannotProvide
 
 
-class UnionSubcaseCoercerProvider(StrippedTypeCoercerProvider):
-    def _provide_coercer_stripped_types(
+class UnionSubcaseCoercerProvider(NormTypeCoercerProvider):
+    def _provide_coercer_norm_types(
         self,
         mediator: Mediator,
         request: CoercerRequest,
-        stripped_src: BaseNormType,
-        stripped_dst: BaseNormType,
+        norm_src: BaseNormType,
+        norm_dst: BaseNormType,
     ) -> Coercer:
-        if stripped_dst.origin != Union:
+        if norm_dst.origin != Union:
             raise CannotProvide
 
-        if stripped_src.origin == Union:
-            src_args_set = set(map(strip_tags, stripped_src.args))
-            dst_args_set = set(map(strip_tags, stripped_dst.args))
+        if norm_src.origin == Union:
+            src_args_set = set(map(strip_tags, norm_src.args))
+            dst_args_set = set(map(strip_tags, norm_dst.args))
             if src_args_set.issubset(dst_args_set):
-                return as_is_stub
-        elif stripped_src.origin in [strip_tags(arg).origin for arg in stripped_dst.args]:
-            return as_is_stub
+                return as_is_stub_with_ctx
+        elif norm_src.origin in [strip_tags(arg).origin for arg in norm_dst.args]:
+            return as_is_stub_with_ctx
         raise CannotProvide
 
 
-class OptionalCoercerProvider(StrippedTypeCoercerProvider):
-    def _provide_coercer_stripped_types(
+class OptionalCoercerProvider(NormTypeCoercerProvider):
+    def _provide_coercer_norm_types(
         self,
         mediator: Mediator,
         request: CoercerRequest,
-        stripped_src: BaseNormType,
-        stripped_dst: BaseNormType,
+        norm_src: BaseNormType,
+        norm_dst: BaseNormType,
     ) -> Coercer:
-        if not (self._is_optional(stripped_dst) and self._is_optional(stripped_src)):
+        if not (self._is_optional(norm_dst) and self._is_optional(norm_src)):
             raise CannotProvide
 
-        not_none_src = self._get_not_none(stripped_src)
-        not_none_dst = self._get_not_none(stripped_dst)
+        not_none_src = self._get_not_none(norm_src)
+        not_none_dst = self._get_not_none(norm_dst)
         not_none_request = replace(
             request,
-            src=request.src.replace_last(replace(request.dst.last, type=not_none_src)),
-            dst=request.dst.replace_last(replace(request.dst.last, type=not_none_dst)),
+            src=request.src.replace_last_type(not_none_src),
+            dst=request.dst.replace_last_type(not_none_dst),
+        )
+        not_none_coercer = mediator.mandatory_provide(
+            not_none_request,
+            lambda x: "Cannot create coercer for optionals. Coercer for wrapped value cannot be created",
         )
-        not_none_coercer = mediator.delegating_provide(not_none_request)
 
-        def optional_coercer(data):
+        def optional_coercer(data, ctx):
             if data is None:
                 return None
-            return not_none_coercer(data)
+            return not_none_coercer(data, ctx)
 
         return optional_coercer
 
@@ -152,3 +153,135 @@ def _is_optional(self, norm: BaseNormType) -> bool:
 
     def _get_not_none(self, norm: BaseNormType) -> BaseNormType:
         return next(case.origin for case in norm.args if case.origin is not None)
+
+
+class TypeHintTagsUnwrappingProvider(CoercerProvider):
+    def _unwrap_type(self, tp: TypeHint) -> TypeHint:
+        try:
+            norm = normalize_type(tp)
+        except ValueError:
+            return tp
+        return strip_tags(norm).source
+
+    def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coercer:
+        src_tp = request.src.last.type
+        dst_tp = request.dst.last.type
+        unwrapped_src_tp = self._unwrap_type(src_tp)
+        unwrapped_dst_tp = self._unwrap_type(dst_tp)
+        if unwrapped_src_tp == src_tp and unwrapped_dst_tp == dst_tp:
+            raise CannotProvide
+        return mediator.delegating_provide(
+            replace(
+                request,
+                src=request.src.replace_last_type(unwrapped_src_tp),
+                dst=request.dst.replace_last_type(unwrapped_dst_tp),
+            ),
+        )
+
+
+class IterableCoercerProvider(NormTypeCoercerProvider):
+    CONCRETE_ORIGINS = {set, list, tuple, deque}
+    ABC_TO_IMPL = {
+        collections.abc.Iterable: tuple,
+        collections.abc.Reversible: tuple,
+        collections.abc.Collection: tuple,
+        collections.abc.Sequence: tuple,
+        collections.abc.MutableSequence: list,
+        # exclude ByteString, because it does not process as Iterable
+        collections.abc.Set: frozenset,
+        collections.abc.MutableSet: set,
+    }
+
+    def _provide_coercer_norm_types(
+        self,
+        mediator: Mediator,
+        request: CoercerRequest,
+        norm_src: BaseNormType,
+        norm_dst: BaseNormType,
+    ) -> Coercer:
+        src_arg_tp = self._parse_source(norm_src)
+        dst_factory, dst_arg_tp = self._parse_destination(norm_dst)
+        element_coercer = mediator.mandatory_provide(
+            CoercerRequest(
+                src=request.src.append_with(
+                    GenericParamLoc(type=src_arg_tp, generic_pos=0),
+                ),
+                ctx=request.ctx,
+                dst=request.dst.append_with(
+                    GenericParamLoc(type=dst_arg_tp, generic_pos=0),
+                ),
+            ),
+            lambda x: "Cannot create coercer for iterables. Coercer for element cannot be created",
+        )
+
+        def iterable_coercer(data, ctx):
+            return dst_factory(element_coercer(element, ctx) for element in data)
+
+        return iterable_coercer
+
+    def _parse_source(self, norm: BaseNormType) -> TypeHint:
+        if norm.origin == tuple and norm.args[-1] != Ellipsis:
+            raise CannotProvide("Constant-length tuple is not suppoerted yet", is_demonstrative=True)
+        if norm.origin in self.CONCRETE_ORIGINS or norm.origin in self.ABC_TO_IMPL:
+            return norm.args[0].source
+        raise CannotProvide
+
+    def _parse_destination(self, norm: BaseNormType) -> Tuple[Callable, TypeHint]:
+        if norm.origin == tuple and norm.args[-1] != Ellipsis:
+            raise CannotProvide("Constant-length tuple is not suppoerted yet", is_demonstrative=True)
+        if norm.origin in self.CONCRETE_ORIGINS:
+            return norm.origin, norm.args[0].source
+        if norm.origin in self.ABC_TO_IMPL:
+            return self.ABC_TO_IMPL[norm.origin], norm.args[0].source
+        raise CannotProvide
+
+
+class DictCoercerProvider(NormTypeCoercerProvider):
+    def _provide_coercer_norm_types(
+        self,
+        mediator: Mediator,
+        request: CoercerRequest,
+        norm_src: BaseNormType,
+        norm_dst: BaseNormType,
+    ) -> Coercer:
+        src_key_tp, src_value_tp = self._parse_source(norm_src)
+        dst_key_tp, dst_value_tp = self._parse_destination(norm_dst)
+        key_coercer = mediator.mandatory_provide(
+            CoercerRequest(
+                src=request.src.append_with(
+                    GenericParamLoc(type=src_key_tp, generic_pos=0),
+                ),
+                ctx=request.ctx,
+                dst=request.dst.append_with(
+                    GenericParamLoc(type=dst_key_tp, generic_pos=0),
+                ),
+            ),
+            lambda x: "Cannot create coercer for dicts. Coercer for key cannot be created",
+        )
+        value_coercer = mediator.mandatory_provide(
+            CoercerRequest(
+                src=request.src.append_with(
+                    GenericParamLoc(type=src_value_tp, generic_pos=1),
+                ),
+                ctx=request.ctx,
+                dst=request.dst.append_with(
+                    GenericParamLoc(type=dst_value_tp, generic_pos=1),
+                ),
+            ),
+            lambda x: "Cannot create coercer for dicts. Coercer for value cannot be created",
+        )
+
+        def dict_coercer(data, ctx):
+            return {key_coercer(key, ctx): value_coercer(value, ctx) for key, value in data.items()}
+
+        return dict_coercer
+
+    def _parse_source(self, norm: BaseNormType) -> Tuple[TypeHint, TypeHint]:
+        if norm.origin in (dict, collections.abc.Mapping, collections.abc.MutableMapping):
+            return norm.args[0].source, norm.args[1].source
+        raise CannotProvide
+
+    def _parse_destination(self, norm: BaseNormType) -> Tuple[TypeHint, TypeHint]:
+        if norm.origin in (dict, collections.abc.Mapping, collections.abc.MutableMapping):
+            return norm.args[0].source, norm.args[1].source
+        raise CannotProvide
diff --git a/src/adaptix/_internal/conversion/converter_provider.py b/src/adaptix/_internal/conversion/converter_provider.py
index 59e56dc7..179cf346 100644
--- a/src/adaptix/_internal/conversion/converter_provider.py
+++ b/src/adaptix/_internal/conversion/converter_provider.py
@@ -1,51 +1,20 @@
-from abc import ABC, abstractmethod
-from functools import reduce
+import itertools
+from functools import update_wrapper
 from inspect import Parameter, Signature
-from typing import Any, Iterable, List, Mapping, Optional, Sequence, Tuple, cast, final
+from typing import Any, Callable, Mapping, Optional, Sequence, Tuple
 
+from ..code_tools.cascade_namespace import BuiltinCascadeNamespace, CascadeNamespace
+from ..code_tools.code_builder import CodeBuilder
 from ..code_tools.compiler import BasicClosureCompiler, ClosureCompiler
 from ..code_tools.name_sanitizer import BuiltinNameSanitizer, NameSanitizer
 from ..common import Coercer, Converter, TypeHint
-from ..conversion.broaching.code_generator import BroachingCodeGenerator, BroachingPlan, BuiltinBroachingCodeGenerator
-from ..conversion.broaching.definitions import (
-    AccessorElement,
-    ConstantElement,
-    FuncCallArg,
-    FunctionElement,
-    KeywordArg,
-    ParameterElement,
-    PositionalArg,
-)
-from ..conversion.request_cls import (
-    CoercerRequest,
-    ConstantLinking,
-    ConverterRequest,
-    FieldLinking,
-    LinkingDest,
-    LinkingRequest,
-    LinkingResult,
-    LinkingSource,
-    UnlinkedOptionalPolicyRequest,
-)
-from ..model_tools.definitions import BaseField, DefaultValue, InputField, InputShape, NoDefault, OutputShape, ParamKind
+from ..conversion.request_cls import CoercerRequest, ConversionContext, ConverterRequest
+from ..model_tools.definitions import DefaultValue, NoDefault
 from ..morphing.model.basic_gen import compile_closure_with_globals_capturing, fetch_code_gen_hook
-from ..provider.essential import CannotProvide, Mediator, mandatory_apply_by_iterable
-from ..provider.fields import base_field_to_loc_map, input_field_to_loc_map
-from ..provider.request_cls import LocMap, LocStack, TypeHintLoc
-from ..provider.shape_provider import InputShapeRequest, OutputShapeRequest, provide_generic_resolved_shape
-from ..provider.static_provider import StaticProvider, static_provision_action
-from ..utils import add_note
-
-
-class ConverterProvider(StaticProvider, ABC):
-    @final
-    @static_provision_action
-    def _outer_provide_converter(self, mediator: Mediator, request: ConverterRequest):
-        return self._provide_converter(mediator, request)
-
-    @abstractmethod
-    def _provide_converter(self, mediator: Mediator, request: ConverterRequest) -> Converter:
-        ...
+from ..provider.essential import CannotProvide, Mediator
+from ..provider.location import FieldLoc
+from ..provider.request_cls import LocStack, TypeHintLoc
+from .provider_template import ConverterProvider
 
 
 class BuiltinConverterProvider(ConverterProvider):
@@ -68,44 +37,89 @@ def _provide_converter(self, mediator: Mediator, request: ConverterRequest) -> C
                 is_demonstrative=True,
             )
 
-        source_model_field, *extra_params = map(self._param_to_base_field, signature.parameters.values())
-        dst_model_field = self._get_dst_field(signature.return_annotation)
-        dst_shape = self._fetch_dst_shape(mediator, dst_model_field)
-        src_shape = self._fetch_src_shape(mediator, source_model_field)
-        broaching_plan = self._make_broaching_plan(
-            mediator=mediator,
-            dst_shape=dst_shape,
-            src_shape=src_shape,
-            owner_linking_src=LinkingSource(source_model_field),
-            owner_linking_dst=LinkingDest(dst_model_field),
-            extra_params=tuple(map(LinkingSource, extra_params)),
-        )
-        return self._make_converter(mediator, request, broaching_plan, signature)
+        return self._make_converter(mediator, request)
 
-    def _make_converter(
-        self,
-        mediator: Mediator,
-        request: ConverterRequest,
-        broaching_plan: BroachingPlan,
-        signature: Signature,
-    ):
-        code_gen = self._create_broaching_code_gen(broaching_plan)
+    def _make_converter(self, mediator: Mediator, request: ConverterRequest):
+        src_loc, *extra_params_locs = map(self._param_to_loc, request.signature.parameters.values())
+        dst_loc = self._get_dst_field(request.signature.return_annotation)
+
+        coercer = mediator.mandatory_provide(
+            CoercerRequest(
+                src=LocStack(src_loc),
+                ctx=ConversionContext(tuple(extra_params_locs)),
+                dst=LocStack(dst_loc),
+            ),
+            lambda x: "Cannot create top-level coercer",
+        )
         closure_name = self._get_closure_name(request)
-        dumper_code, dumper_namespace = code_gen.produce_code(
+        dumper_code, dumper_namespace = self._produce_code(
             signature=request.signature,
             closure_name=closure_name,
             stub_function=request.stub_function,
+            coercer=coercer,
         )
-        code_gen_loc_stack = LocStack(LocMap(TypeHintLoc(self._get_type_from_annotation(signature.return_annotation))))
         return compile_closure_with_globals_capturing(
             compiler=self._get_compiler(),
-            code_gen_hook=fetch_code_gen_hook(mediator, code_gen_loc_stack),
+            code_gen_hook=fetch_code_gen_hook(mediator, LocStack(dst_loc)),
             namespace=dumper_namespace,
             closure_code=dumper_code,
             closure_name=closure_name,
             file_name=self._get_file_name(request),
         )
 
+    def register_mangled(self, namespace: CascadeNamespace, base: str, obj: object) -> str:
+        base = self._name_sanitizer.sanitize(base)
+        if namespace.try_add_constant(base, obj):
+            return base
+
+        for i in itertools.count(1):
+            name = f"{base}_{i}"
+            if namespace.try_add_constant(name, obj):
+                return name
+        raise RuntimeError
+
+    def _produce_code(
+        self,
+        signature: Signature,
+        stub_function: Optional[Callable],
+        closure_name: str,
+        coercer: Coercer,
+    ) -> Tuple[str, Mapping[str, object]]:
+        builder = CodeBuilder()
+        namespace = BuiltinCascadeNamespace(occupied=signature.parameters.keys())
+        namespace.add_outer_constant("_closure_signature", signature)
+        namespace.add_outer_constant("_stub_function", stub_function)
+        namespace.add_outer_constant("_update_wrapper", update_wrapper)
+        coercer_var = self.register_mangled(namespace, "coercer", coercer)
+
+        no_types_signature = signature.replace(
+            parameters=[param.replace(annotation=Signature.empty) for param in signature.parameters.values()],
+            return_annotation=Signature.empty,
+        )
+        parameters = tuple(signature.parameters.values())
+        ctx_passing = self._get_ctx_passing(parameters[1:])
+        builder(
+            f"""
+            def {closure_name}{no_types_signature}:
+                return {coercer_var}({parameters[0].name}, {ctx_passing})
+            """,
+        )
+        if stub_function is not None:
+            builder += f"_update_wrapper({closure_name}, _stub_function)"
+        builder += f"{closure_name}.__signature__ = _closure_signature"
+        builder += f"{closure_name}.__name__ = {closure_name!r}"
+        return builder.string(), namespace.all_constants
+
+    def _get_ctx_passing(self, ctx_parameters: Sequence[Parameter]) -> str:
+        if len(ctx_parameters) == 0:
+            return "None"
+        if len(ctx_parameters) == 1:
+            return ctx_parameters[0].name
+        return "(" + ", ".join(param.name for param in ctx_parameters) + ")"
+
+    def _get_compiler(self) -> ClosureCompiler:
+        return BasicClosureCompiler()
+
     def _get_closure_name(self, request: ConverterRequest) -> str:
         if request.function_name is not None:
             return request.function_name
@@ -124,280 +138,20 @@ def _get_file_name(self, request: ConverterRequest) -> str:
             return stub_function_name
         src = next(iter(request.signature.parameters.values()))
         dst = self._get_type_from_annotation(request.signature.return_annotation)
-        return f"convert_{src}_to_{dst}"
+        return self._name_sanitizer.sanitize(f"convert_{src}_to_{dst}")
 
     def _get_type_from_annotation(self, annotation: Any) -> TypeHint:
         return Any if annotation == Signature.empty else annotation
 
-    def _param_to_base_field(self, parameter: Parameter) -> BaseField:
-        return BaseField(
-            id=parameter.name,
+    def _param_to_loc(self, parameter: Parameter) -> FieldLoc:
+        return FieldLoc(
+            field_id=parameter.name,
             type=self._get_type_from_annotation(parameter.annotation),
             default=NoDefault() if parameter.default == Signature.empty else DefaultValue(parameter.default),
             metadata={},
-            original=None,
-        )
-
-    def _get_dst_field(self, return_annotation: Any) -> InputField:
-        return InputField(
-            id="__return__",
-            type=self._get_type_from_annotation(return_annotation),
-            metadata={},
-            default=NoDefault(),
-            original=None,
-            is_required=True,
-        )
-
-    def _fetch_dst_shape(self, mediator: Mediator, model_dst_field: InputField) -> InputShape:
-        return provide_generic_resolved_shape(
-            mediator,
-            InputShapeRequest(loc_stack=LocStack(input_field_to_loc_map(model_dst_field))),
-        )
-
-    def _fetch_src_shape(self, mediator: Mediator, source_model_field: BaseField) -> OutputShape:
-        src_loc_map = base_field_to_loc_map(source_model_field)
-        return provide_generic_resolved_shape(
-            mediator,
-            OutputShapeRequest(loc_stack=LocStack(src_loc_map)),
-        )
-
-    def _get_compiler(self) -> ClosureCompiler:
-        return BasicClosureCompiler()
-
-    def _create_broaching_code_gen(self, plan: BroachingPlan) -> BroachingCodeGenerator:
-        return BuiltinBroachingCodeGenerator(plan=plan, name_sanitizer=self._name_sanitizer)
-
-    def _fetch_linkings(
-        self,
-        mediator: Mediator,
-        dst_shape: InputShape,
-        src_shape: OutputShape,
-        owner_linking_src: LinkingSource,
-        owner_linking_dst: LinkingDest,
-        extra_params: Sequence[LinkingSource],
-    ) -> Iterable[Tuple[InputField, Optional[LinkingResult]]]:
-        model_linking_sources = tuple(
-            owner_linking_src.append_with(src_field)
-            for src_field in src_shape.fields
-        )
-        sources = (model_linking_sources, *extra_params)
-
-        def fetch_field_linking(dst_field: InputField) -> Tuple[InputField, Optional[LinkingResult]]:
-            destination = owner_linking_dst.append_with(dst_field)
-            try:
-                linking = mediator.provide(
-                    LinkingRequest(
-                        sources=sources,  # type: ignore[arg-type]
-                        destination=destination,
-                    ),
-                )
-            except CannotProvide as e:
-                if dst_field.is_required:
-                    add_note(e, "Note: This is a required filed, so it must take value")
-                    raise
-
-                policy = mediator.mandatory_provide(
-                    UnlinkedOptionalPolicyRequest(loc_stack=destination.to_loc_stack()),
-                )
-                if policy.is_allowed:
-                    return dst_field, None
-                add_note(
-                    e,
-                    "Note: Current policy forbids unlinked optional fields,"
-                    " so you need to link it to another field"
-                    " or explicitly confirm the desire to skipping using `allow_unlinked_optional`",
-                )
-                raise
-            return dst_field, linking
-
-        return mandatory_apply_by_iterable(
-            fetch_field_linking,
-            zip(dst_shape.fields),
-            lambda: "Linkings for some fields are not found",
-        )
-
-    def _get_nested_models_sub_plan(
-        self,
-        mediator: Mediator,
-        linking_src: LinkingSource,
-        linking_dst: LinkingDest,
-        extra_params: Sequence[LinkingSource],
-    ) -> Optional[BroachingPlan]:
-        try:
-            dst_shape = provide_generic_resolved_shape(
-                mediator,
-                InputShapeRequest(loc_stack=linking_dst.to_loc_stack()),
-            )
-            src_shape = provide_generic_resolved_shape(
-                mediator,
-                OutputShapeRequest(loc_stack=linking_src.to_loc_stack()),
-            )
-        except CannotProvide:
-            return None
-
-        return self._make_broaching_plan(
-            mediator=mediator,
-            dst_shape=dst_shape,
-            src_shape=src_shape,
-            extra_params=extra_params,
-            owner_linking_src=linking_src,
-            owner_linking_dst=linking_dst,
-        )
-
-    def _linking_source_to_plan(self, linking_src: LinkingSource) -> BroachingPlan:
-        return reduce(
-            lambda plan, item: (
-                AccessorElement(
-                    target=plan,
-                    accessor=item.accessor,
-                )
-            ),
-            linking_src.tail,
-            cast(BroachingPlan, ParameterElement(linking_src.head.id)),
-        )
-
-    def _get_coercer_sub_plan(self, coercer: Coercer, linking_src: LinkingSource) -> BroachingPlan:
-        return FunctionElement(
-            func=coercer,
-            args=(
-                PositionalArg(
-                    self._linking_source_to_plan(linking_src),
-                ),
-            ),
         )
 
-    def _generate_field_linking_to_sub_plan(
-        self,
-        mediator: Mediator,
-        extra_params: Sequence[LinkingSource],
-        owner_linking_dst: LinkingDest,
-        input_field: InputField,
-        linking: FieldLinking,
-    ) -> BroachingPlan:
-        if linking.coercer is not None:
-            return self._get_coercer_sub_plan(linking.coercer, linking.source)
-
-        linking_dst = owner_linking_dst.append_with(input_field)
-        try:
-            coercer = mediator.provide(
-                CoercerRequest(
-                    src=linking.source,
-                    dst=linking_dst,
-                ),
-            )
-        except CannotProvide as e:
-            result = self._get_nested_models_sub_plan(
-                mediator=mediator,
-                linking_src=linking.source,
-                linking_dst=linking_dst,
-                extra_params=extra_params,
-            )
-            if result is not None:
-                return result
-            raise e
-        return self._get_coercer_sub_plan(coercer, linking.source)
-
-    def _generate_constant_linking_to_sub_plan(
-        self,
-        mediator: Mediator,
-        linking: ConstantLinking,
-    ) -> BroachingPlan:
-        if isinstance(linking.constant, DefaultValue):
-            return ConstantElement(value=linking.constant.value)
-        return FunctionElement(func=linking.constant.factory, args=())
-
-    def _generate_field_to_sub_plan(
-        self,
-        mediator: Mediator,
-        extra_params: Sequence[LinkingSource],
-        field_linkings: Iterable[Tuple[InputField, LinkingResult]],
-        owner_linking_dst: LinkingDest,
-    ) -> Mapping[InputField, BroachingPlan]:
-        def generate_sub_plan(input_field: InputField, linking_result: LinkingResult):
-            if isinstance(linking_result.linking, ConstantLinking):
-                return self._generate_constant_linking_to_sub_plan(
-                    mediator=mediator,
-                    linking=linking_result.linking,
-                )
-            return self._generate_field_linking_to_sub_plan(
-                mediator=mediator,
-                extra_params=extra_params,
-                owner_linking_dst=owner_linking_dst,
-                input_field=input_field,
-                linking=linking_result.linking,
-            )
-
-        coercers = mandatory_apply_by_iterable(
-            generate_sub_plan,
-            field_linkings,
-            lambda: "Coercers for some linkings are not found",
-        )
-        return {
-            dst_field: coercer
-            for (dst_field, linking), coercer in zip(field_linkings, coercers)
-        }
-
-    def _make_broaching_plan(
-        self,
-        mediator: Mediator,
-        dst_shape: InputShape,
-        src_shape: OutputShape,
-        extra_params: Sequence[LinkingSource],
-        owner_linking_src: LinkingSource,
-        owner_linking_dst: LinkingDest,
-    ) -> BroachingPlan:
-        field_linkings = self._fetch_linkings(
-            mediator=mediator,
-            dst_shape=dst_shape,
-            src_shape=src_shape,
-            extra_params=extra_params,
-            owner_linking_src=owner_linking_src,
-            owner_linking_dst=owner_linking_dst,
-        )
-        field_to_sub_plan = self._generate_field_to_sub_plan(
-            mediator=mediator,
-            field_linkings=[
-                (dst_field, linking)
-                for dst_field, linking in field_linkings
-                if linking is not None
-            ],
-            extra_params=extra_params,
-            owner_linking_dst=owner_linking_dst,
-        )
-        return self._make_constructor_call(
-            dst_shape=dst_shape,
-            field_to_linking=dict(field_linkings),
-            field_to_sub_plan=field_to_sub_plan,
-        )
-
-    def _make_constructor_call(
-        self,
-        dst_shape: InputShape,
-        field_to_linking: Mapping[InputField, Optional[LinkingResult]],
-        field_to_sub_plan: Mapping[InputField, BroachingPlan],
-    ) -> BroachingPlan:
-        args: List[FuncCallArg[BroachingPlan]] = []
-        has_skipped_params = False
-        for param in dst_shape.params:
-            field = dst_shape.fields_dict[param.field_id]
-
-            if field_to_linking[field] is None:
-                has_skipped_params = True
-                continue
-
-            sub_plan = field_to_sub_plan[field]
-            if param.kind == ParamKind.KW_ONLY or has_skipped_params:
-                args.append(KeywordArg(param.name, sub_plan))
-            elif param.kind == ParamKind.POS_ONLY and has_skipped_params:
-                raise CannotProvide(
-                    "Can not generate consistent constructor call,"
-                    " positional-only parameter is skipped",
-                    is_demonstrative=True,
-                )
-            else:
-                args.append(PositionalArg(sub_plan))
-
-        return FunctionElement(
-            func=dst_shape.constructor,
-            args=tuple(args),
+    def _get_dst_field(self, return_annotation: Any) -> TypeHintLoc:
+        return TypeHintLoc(
+            self._get_type_from_annotation(return_annotation),
         )
diff --git a/src/adaptix/_internal/conversion/facade/checker.py b/src/adaptix/_internal/conversion/facade/checker.py
index ad885d7d..08132151 100644
--- a/src/adaptix/_internal/conversion/facade/checker.py
+++ b/src/adaptix/_internal/conversion/facade/checker.py
@@ -10,7 +10,10 @@ def ensure_function_is_stub(func):
         return
 
     source = dedent(raw_source)
-    ast_module = ast.parse(source)
+    try:
+        ast_module = ast.parse(source)
+    except SyntaxError:
+        return
     func_body = ast_module.body[0].body
     if len(func_body) == 1:
         body_element = func_body[0]
diff --git a/src/adaptix/_internal/conversion/facade/provider.py b/src/adaptix/_internal/conversion/facade/provider.py
index 5fb9a858..fd6ca849 100644
--- a/src/adaptix/_internal/conversion/facade/provider.py
+++ b/src/adaptix/_internal/conversion/facade/provider.py
@@ -1,17 +1,16 @@
-# pylint: disable=redefined-outer-name
 from typing import Any, Callable, Optional, overload
 
-from ...common import Coercer
+from ...common import OneArgCoercer
 from ...model_tools.definitions import DefaultFactory, DefaultValue
 from ...provider.essential import Provider
 from ...provider.facade.provider import bound_by_any
-from ...provider.loc_stack_filtering import Pred, create_loc_stack_checker
+from ...provider.loc_stack_filtering import LocStackChecker, LocStackSizeChecker, Pred, create_loc_stack_checker
 from ..coercer_provider import MatchingCoercerProvider
 from ..linking_provider import ConstantLinkingProvider, MatchingLinkingProvider
 from ..policy_provider import UnlinkedOptionalPolicyProvider
 
 
-def link(src: Pred, dst: Pred, *, coercer: Optional[Coercer] = None) -> Provider:
+def link(src: Pred, dst: Pred, *, coercer: Optional[OneArgCoercer] = None) -> Provider:
     """Basic provider to define custom linking between fields.
 
     :param src: Predicate specifying source point of linking. See :ref:`predicate-system` for details.
@@ -51,7 +50,7 @@ def link_constant(dst: Pred, *, value: Any = None, factory: Any = None) -> Provi
     )
 
 
-def coercer(src: Pred, dst: Pred, func: Coercer) -> Provider:
+def coercer(src: Pred, dst: Pred, func: OneArgCoercer) -> Provider:
     """Basic provider to define custom coercer.
 
     :param src: Predicate specifying source point of linking. See :ref:`predicate-system` for details.
@@ -86,3 +85,10 @@ def forbid_unlinked_optional(*preds: Pred) -> Provider:
     :return: Desired provider.
     """
     return bound_by_any(preds, UnlinkedOptionalPolicyProvider(is_allowed=False))
+
+
+def from_param(param_name: str) -> LocStackChecker:
+    """The special predicate form matching only top-level parameters by name"""
+    if not param_name.isidentifier():
+        raise ValueError("param_name must be a valid python identifier to exactly match parameter")
+    return LocStackSizeChecker(1) & create_loc_stack_checker(param_name)
diff --git a/src/adaptix/_internal/conversion/facade/retort.py b/src/adaptix/_internal/conversion/facade/retort.py
index c16914d0..872b6a89 100644
--- a/src/adaptix/_internal/conversion/facade/retort.py
+++ b/src/adaptix/_internal/conversion/facade/retort.py
@@ -1,4 +1,3 @@
-# pylint: disable=protected-access, not-callable
 import inspect
 from functools import partial
 from inspect import Parameter, Signature
@@ -13,14 +12,18 @@
 from ...retort.operating_retort import OperatingRetort
 from ...type_tools import is_generic_class
 from ..coercer_provider import (
+    DictCoercerProvider,
     DstAnyCoercerProvider,
+    IterableCoercerProvider,
     OptionalCoercerProvider,
     SameTypeCoercerProvider,
     SubclassCoercerProvider,
+    TypeHintTagsUnwrappingProvider,
     UnionSubcaseCoercerProvider,
 )
 from ..converter_provider import BuiltinConverterProvider
-from ..linking_provider import SameNameLinkingProvider
+from ..linking_provider import DefaultLinkingProvider
+from ..model_coercer_provider import ModelCoercerProvider
 from ..request_cls import ConverterRequest
 from .checker import ensure_function_is_stub
 from .provider import forbid_unlinked_optional
@@ -32,13 +35,18 @@ class FilledConversionRetort(OperatingRetort):
 
         BuiltinConverterProvider(),
 
-        SameNameLinkingProvider(is_default=True),
+        DefaultLinkingProvider(),
+
+        ModelCoercerProvider(),
+        IterableCoercerProvider(),
+        DictCoercerProvider(),
 
         SameTypeCoercerProvider(),
         DstAnyCoercerProvider(),
         SubclassCoercerProvider(),
         UnionSubcaseCoercerProvider(),
         OptionalCoercerProvider(),
+        TypeHintTagsUnwrappingProvider(),
 
         forbid_unlinked_optional(P.ANY),
     ]
@@ -56,7 +64,6 @@ def _calculate_derived(self) -> None:
         self._simple_converter_cache: Dict[Tuple[TypeHint, TypeHint, Optional[str]], Converter] = {}
 
     def extend(self: AR, *, recipe: Iterable[Provider]) -> AR:
-        # pylint: disable=protected-access
         with self._clone() as clone:
             clone._inc_instance_recipe = (
                 tuple(recipe) + clone._inc_instance_recipe
diff --git a/src/adaptix/_internal/conversion/linking_provider.py b/src/adaptix/_internal/conversion/linking_provider.py
index 2f8e1fe7..5b4dde4d 100644
--- a/src/adaptix/_internal/conversion/linking_provider.py
+++ b/src/adaptix/_internal/conversion/linking_provider.py
@@ -1,57 +1,51 @@
-from abc import ABC, abstractmethod
+import itertools
 from typing import Iterable, Optional, Union
 
-from ..common import Coercer
+from ..common import Coercer, OneArgCoercer
 from ..model_tools.definitions import DefaultFactory, DefaultValue
 from ..provider.essential import CannotProvide, Mediator
 from ..provider.loc_stack_filtering import LocStackChecker
-from ..provider.static_provider import StaticProvider, static_provision_action
-from .request_cls import ConstantLinking, FieldLinking, LinkingRequest, LinkingResult, LinkingSource, SourceCandidates
+from ..provider.location import FieldLoc
+from .provider_template import LinkingProvider
+from .request_cls import ConstantLinking, FieldLinking, LinkingRequest, LinkingResult, LinkingSource
 
 
-class LinkingProvider(StaticProvider, ABC):
-    @static_provision_action
-    @abstractmethod
+class DefaultLinkingProvider(LinkingProvider):
     def _provide_linking(self, mediator: Mediator, request: LinkingRequest) -> LinkingResult:
-        ...
+        target_field_id = request.destination.last.cast(FieldLoc).field_id
 
-
-def iterate_source_candidates(candidates: SourceCandidates) -> Iterable[LinkingSource]:
-    for source in reversed(candidates):
-        if isinstance(source, tuple):
-            yield from source
-        else:
-            yield source
-
-
-class SameNameLinkingProvider(LinkingProvider):
-    def __init__(self, *, is_default: bool):
-        self._is_default = is_default
-
-    def _provide_linking(self, mediator: Mediator, request: LinkingRequest) -> LinkingResult:
-        target_field_id = request.destination.last.id
-        for source in iterate_source_candidates(request.sources):
-            if source.last.id == target_field_id:
+        for source in self._iterate_sources(request):
+            if source.last.cast(FieldLoc).field_id == target_field_id:
                 return LinkingResult(
                     linking=FieldLinking(source=source, coercer=None),
-                    is_default=self._is_default,
                 )
         raise CannotProvide
 
+    def _iterate_sources(self, request: LinkingRequest) -> Iterable[LinkingSource]:
+        if len(request.destination) == 2:  # noqa: PLR2004
+            yield from reversed(request.context.loc_stacks)
+        yield from request.sources
+
 
 class MatchingLinkingProvider(LinkingProvider):
-    def __init__(self, src_lsc: LocStackChecker, dst_lsc: LocStackChecker, coercer: Optional[Coercer]):
+    def __init__(self, src_lsc: LocStackChecker, dst_lsc: LocStackChecker, coercer: Optional[OneArgCoercer]):
         self._src_lsc = src_lsc
         self._dst_lsc = dst_lsc
-        self._coercer = coercer
+        self._one_arg_coercer = coercer
+
+    def _get_coercer(self) -> Optional[Coercer]:
+        one_arg_coercer = self._one_arg_coercer
+        if one_arg_coercer is None:
+            return None
+        return lambda x, ctx: one_arg_coercer(x)
 
     def _provide_linking(self, mediator: Mediator, request: LinkingRequest) -> LinkingResult:
-        if not self._dst_lsc.check_loc_stack(mediator, request.destination.to_loc_stack()):
+        if not self._dst_lsc.check_loc_stack(mediator, request.destination):
             raise CannotProvide
 
-        for source in iterate_source_candidates(request.sources):
-            if self._src_lsc.check_loc_stack(mediator, source.to_loc_stack()):
-                return LinkingResult(linking=FieldLinking(source=source, coercer=self._coercer))
+        for source in itertools.chain(request.sources, reversed(request.context.loc_stacks)):
+            if self._src_lsc.check_loc_stack(mediator, source):
+                return LinkingResult(linking=FieldLinking(source=source, coercer=self._get_coercer()))
         raise CannotProvide
 
 
@@ -61,6 +55,6 @@ def __init__(self, dst_lsc: LocStackChecker, default: Union[DefaultValue, Defaul
         self._default = default
 
     def _provide_linking(self, mediator: Mediator, request: LinkingRequest) -> LinkingResult:
-        if self._dst_lsc.check_loc_stack(mediator, request.destination.to_loc_stack()):
+        if self._dst_lsc.check_loc_stack(mediator, request.destination):
             return LinkingResult(linking=ConstantLinking(self._default))
         raise CannotProvide
diff --git a/src/adaptix/_internal/conversion/model_coercer_provider.py b/src/adaptix/_internal/conversion/model_coercer_provider.py
new file mode 100644
index 00000000..f929324c
--- /dev/null
+++ b/src/adaptix/_internal/conversion/model_coercer_provider.py
@@ -0,0 +1,294 @@
+from inspect import Parameter, Signature
+from typing import Iterable, List, Mapping, Optional, Tuple
+
+from ..code_tools.compiler import BasicClosureCompiler, ClosureCompiler
+from ..code_tools.name_sanitizer import BuiltinNameSanitizer, NameSanitizer
+from ..common import Coercer
+from ..conversion.broaching.code_generator import BroachingCodeGenerator, BroachingPlan, BuiltinBroachingCodeGenerator
+from ..conversion.broaching.definitions import (
+    AccessorElement,
+    ConstantElement,
+    FuncCallArg,
+    FunctionElement,
+    KeywordArg,
+    ParameterElement,
+    PositionalArg,
+)
+from ..conversion.request_cls import (
+    CoercerRequest,
+    ConstantLinking,
+    ConversionDestItem,
+    ConversionSourceItem,
+    FieldLinking,
+    LinkingRequest,
+    LinkingResult,
+    UnlinkedOptionalPolicyRequest,
+)
+from ..model_tools.definitions import DefaultValue, InputField, InputShape, OutputShape, ParamKind, create_key_accessor
+from ..morphing.model.basic_gen import compile_closure_with_globals_capturing, fetch_code_gen_hook
+from ..provider.essential import CannotProvide, Mediator, mandatory_apply_by_iterable
+from ..provider.fields import input_field_to_loc, output_field_to_loc
+from ..provider.location import OutputFieldLoc
+from ..provider.request_cls import LocStack, TypeHintLoc
+from ..provider.shape_provider import InputShapeRequest, OutputShapeRequest, provide_generic_resolved_shape
+from ..utils import add_note
+from .provider_template import CoercerProvider
+
+
+class ModelCoercerProvider(CoercerProvider):
+    def __init__(self, *, name_sanitizer: NameSanitizer = BuiltinNameSanitizer()):
+        self._name_sanitizer = name_sanitizer
+
+    def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coercer:
+        dst_shape = self._fetch_dst_shape(mediator, request.dst)
+        src_shape = self._fetch_src_shape(mediator, request.src)
+        broaching_plan = self._make_broaching_plan(
+            mediator=mediator,
+            request=request,
+            dst_shape=dst_shape,
+            src_shape=src_shape,
+        )
+        return self._make_coercer(mediator, request, broaching_plan)
+
+    def _make_coercer(
+        self,
+        mediator: Mediator,
+        request: CoercerRequest,
+        broaching_plan: BroachingPlan,
+    ):
+        code_gen = self._create_broaching_code_gen(broaching_plan)
+        closure_name = self._get_closure_name(request)
+        dumper_code, dumper_namespace = code_gen.produce_code(
+            signature=Signature(
+                parameters=[
+                    Parameter("data", Parameter.POSITIONAL_ONLY),
+                    Parameter("ctx", Parameter.POSITIONAL_ONLY),
+                ],
+            ),
+            closure_name=closure_name,
+        )
+        return compile_closure_with_globals_capturing(
+            compiler=self._get_compiler(),
+            code_gen_hook=fetch_code_gen_hook(mediator, request.dst),
+            namespace=dumper_namespace,
+            closure_code=dumper_code,
+            closure_name=closure_name,
+            file_name=self._get_file_name(request),
+        )
+
+    def _get_closure_name(self, request: CoercerRequest) -> str:
+        src = request.src.last.cast(TypeHintLoc).type
+        dst = request.dst.last.cast(TypeHintLoc).type
+        return self._name_sanitizer.sanitize(f"coerce_{src}_to_{dst}")
+
+    def _get_file_name(self, request: CoercerRequest) -> str:
+        src = request.src.last.cast(TypeHintLoc).type
+        dst = request.dst.last.cast(TypeHintLoc).type
+        return self._name_sanitizer.sanitize(f"coerce_{src}_to_{dst}")
+
+    def _fetch_dst_shape(self, mediator: Mediator, loc_stack: LocStack[ConversionDestItem]) -> InputShape:
+        return provide_generic_resolved_shape(
+            mediator,
+            InputShapeRequest(loc_stack=loc_stack),
+        )
+
+    def _fetch_src_shape(self, mediator: Mediator, loc_stack: LocStack[ConversionSourceItem]) -> OutputShape:
+        return provide_generic_resolved_shape(
+            mediator,
+            OutputShapeRequest(loc_stack=loc_stack),
+        )
+
+    def _get_compiler(self) -> ClosureCompiler:
+        return BasicClosureCompiler()
+
+    def _create_broaching_code_gen(self, plan: BroachingPlan) -> BroachingCodeGenerator:
+        return BuiltinBroachingCodeGenerator(plan=plan, name_sanitizer=self._name_sanitizer)
+
+    def _fetch_linkings(
+        self,
+        mediator: Mediator,
+        request: CoercerRequest,
+        dst_shape: InputShape,
+        src_shape: OutputShape,
+    ) -> Iterable[Tuple[InputField, Optional[LinkingResult]]]:
+        sources = tuple(
+            request.src.append_with(output_field_to_loc(src_field))
+            for src_field in src_shape.fields
+        )
+
+        def fetch_field_linking(dst_field: InputField) -> Tuple[InputField, Optional[LinkingResult]]:
+            destination = request.dst.append_with(input_field_to_loc(dst_field))
+            try:
+                linking = mediator.provide(
+                    LinkingRequest(
+                        sources=sources,
+                        context=request.ctx,
+                        destination=destination,
+                    ),
+                )
+            except CannotProvide as e:
+                if dst_field.is_required:
+                    add_note(e, "Note: This is a required field, so it must take value")
+                    raise
+
+                policy = mediator.mandatory_provide(
+                    UnlinkedOptionalPolicyRequest(loc_stack=destination),
+                )
+                if policy.is_allowed:
+                    return dst_field, None
+                add_note(
+                    e,
+                    "Note: Current policy forbids unlinked optional fields,"
+                    " so you need to link it to another field"
+                    " or explicitly confirm the desire to skipping using `allow_unlinked_optional`",
+                )
+                raise
+            return dst_field, linking
+
+        return mandatory_apply_by_iterable(
+            fetch_field_linking,
+            zip(dst_shape.fields),
+            lambda: "Cannot create coercer for models. Linkings for some fields are not found",
+        )
+
+    def _field_linking_to_sub_plan(
+        self,
+        mediator: Mediator,
+        request: CoercerRequest,
+        input_field: InputField,
+        linking: FieldLinking,
+    ) -> BroachingPlan:
+        if linking.coercer is not None:
+            coercer = linking.coercer
+        else:
+            coercer = mediator.provide(
+                CoercerRequest(
+                    src=linking.source,
+                    ctx=request.ctx,
+                    dst=request.dst.append_with(input_field_to_loc(input_field)),
+                ),
+            )
+
+        return FunctionElement[BroachingPlan](
+            func=coercer,
+            args=(
+                PositionalArg(self._get_field_coercer_data_arg(mediator, request, linking)),
+                PositionalArg(ParameterElement("ctx")),
+            ),
+        )
+
+    def _get_field_coercer_data_arg(
+        self,
+        mediator: Mediator,
+        request: CoercerRequest,
+        linking: FieldLinking,
+    ) -> BroachingPlan:
+        if linking.source in request.ctx.loc_stacks:
+            if len(request.ctx.params) == 1:
+                return ParameterElement("ctx")
+            return AccessorElement(
+                ParameterElement("ctx"),
+                create_key_accessor(
+                    key=request.ctx.loc_stacks.index(linking.source),
+                    access_error=None,
+                ),
+            )
+        return AccessorElement(ParameterElement("data"), linking.source.last.cast(OutputFieldLoc).accessor)
+
+    def _generate_constant_linking_to_sub_plan(
+        self,
+        mediator: Mediator,
+        linking: ConstantLinking,
+    ) -> BroachingPlan:
+        if isinstance(linking.constant, DefaultValue):
+            return ConstantElement(value=linking.constant.value)
+        return FunctionElement(func=linking.constant.factory, args=())
+
+    def _generate_field_to_sub_plan(
+        self,
+        mediator: Mediator,
+        request: CoercerRequest,
+        field_linkings: Iterable[Tuple[InputField, LinkingResult]],
+    ) -> Mapping[InputField, BroachingPlan]:
+        def generate_sub_plan(input_field: InputField, linking_result: LinkingResult):
+            if isinstance(linking_result.linking, ConstantLinking):
+                return self._generate_constant_linking_to_sub_plan(
+                    mediator=mediator,
+                    linking=linking_result.linking,
+                )
+            return self._field_linking_to_sub_plan(
+                mediator=mediator,
+                request=request,
+                input_field=input_field,
+                linking=linking_result.linking,
+            )
+
+        field_sub_plans = mandatory_apply_by_iterable(
+            generate_sub_plan,
+            field_linkings,
+            lambda: "Cannot create coercer for models. Coercers for some linkings are not found",
+        )
+        return {
+            dst_field: sub_plan
+            for (dst_field, linking), sub_plan in zip(field_linkings, field_sub_plans)
+        }
+
+    def _make_broaching_plan(
+        self,
+        mediator: Mediator,
+        request: CoercerRequest,
+        dst_shape: InputShape,
+        src_shape: OutputShape,
+    ) -> BroachingPlan:
+        field_linkings = self._fetch_linkings(
+            mediator=mediator,
+            request=request,
+            dst_shape=dst_shape,
+            src_shape=src_shape,
+        )
+        field_to_sub_plan = self._generate_field_to_sub_plan(
+            mediator=mediator,
+            request=request,
+            field_linkings=[
+                (dst_field, linking)
+                for dst_field, linking in field_linkings
+                if linking is not None
+            ],
+        )
+        return self._make_constructor_call(
+            dst_shape=dst_shape,
+            field_to_linking=dict(field_linkings),
+            field_to_sub_plan=field_to_sub_plan,
+        )
+
+    def _make_constructor_call(
+        self,
+        dst_shape: InputShape,
+        field_to_linking: Mapping[InputField, Optional[LinkingResult]],
+        field_to_sub_plan: Mapping[InputField, BroachingPlan],
+    ) -> BroachingPlan:
+        args: List[FuncCallArg[BroachingPlan]] = []
+        has_skipped_params = False
+        for param in dst_shape.params:
+            field = dst_shape.fields_dict[param.field_id]
+
+            if field_to_linking[field] is None:
+                has_skipped_params = True
+                continue
+
+            sub_plan = field_to_sub_plan[field]
+            if param.kind == ParamKind.KW_ONLY or has_skipped_params:
+                args.append(KeywordArg(param.name, sub_plan))
+            elif param.kind == ParamKind.POS_ONLY and has_skipped_params:
+                raise CannotProvide(
+                    "Can not generate consistent constructor call,"
+                    " positional-only parameter is skipped",
+                    is_demonstrative=True,
+                )
+            else:
+                args.append(PositionalArg(sub_plan))
+
+        return FunctionElement(
+            func=dst_shape.constructor,
+            args=tuple(args),
+        )
diff --git a/src/adaptix/_internal/conversion/provider_template.py b/src/adaptix/_internal/conversion/provider_template.py
new file mode 100644
index 00000000..2f015336
--- /dev/null
+++ b/src/adaptix/_internal/conversion/provider_template.py
@@ -0,0 +1,32 @@
+from abc import ABC, abstractmethod
+from typing import final
+
+from ..common import Coercer, Converter
+from ..provider.essential import Mediator
+from ..provider.static_provider import StaticProvider, static_provision_action
+from .request_cls import CoercerRequest, ConverterRequest, LinkingRequest, LinkingResult
+
+
+class ConverterProvider(StaticProvider, ABC):
+    @final
+    @static_provision_action
+    def _outer_provide_converter(self, mediator: Mediator, request: ConverterRequest):
+        return self._provide_converter(mediator, request)
+
+    @abstractmethod
+    def _provide_converter(self, mediator: Mediator, request: ConverterRequest) -> Converter:
+        ...
+
+
+class CoercerProvider(StaticProvider, ABC):
+    @static_provision_action
+    @abstractmethod
+    def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coercer:
+        ...
+
+
+class LinkingProvider(StaticProvider, ABC):
+    @static_provision_action
+    @abstractmethod
+    def _provide_linking(self, mediator: Mediator, request: LinkingRequest) -> LinkingResult:
+        ...
diff --git a/src/adaptix/_internal/conversion/request_cls.py b/src/adaptix/_internal/conversion/request_cls.py
index d0252312..a605fc27 100644
--- a/src/adaptix/_internal/conversion/request_cls.py
+++ b/src/adaptix/_internal/conversion/request_cls.py
@@ -1,39 +1,35 @@
-from dataclasses import dataclass
+from dataclasses import dataclass, field
 from inspect import Signature
-from itertools import chain, islice
-from typing import Callable, Iterator, Optional, Union
+from typing import Callable, Optional, Union
 
 from ..common import Coercer, VarTuple
-from ..datastructures import ImmutableStack
-from ..model_tools.definitions import BaseField, DefaultFactory, DefaultValue, InputField, OutputField
+from ..model_tools.definitions import DefaultFactory, DefaultValue
 from ..provider.essential import Request
-from ..provider.fields import base_field_to_loc_map, input_field_to_loc_map, output_field_to_loc_map
+from ..provider.location import FieldLoc, GenericParamLoc, InputFieldLoc, OutputFieldLoc, TypeHintLoc
 from ..provider.request_cls import LocatedRequest, LocStack
 
-LinkingSourceItem = Union[OutputField, BaseField]
 
+@dataclass(frozen=True)
+class ConverterRequest(Request):
+    signature: Signature
+    function_name: Optional[str]
+    stub_function: Optional[Callable]
 
-class LinkingSource(ImmutableStack[LinkingSourceItem]):
-    def to_loc_stack(self) -> LocStack:
-        return LocStack.from_iter(
-            chain(
-                (base_field_to_loc_map(self.head), ),
-                map(output_field_to_loc_map, self.tail),
-            ),
-        )
 
-    @property
-    def head(self) -> BaseField:
-        return self[0]
+ConversionSourceItem = Union[FieldLoc, OutputFieldLoc, GenericParamLoc]
+ConversionDestItem = Union[TypeHintLoc, InputFieldLoc, GenericParamLoc]
 
-    @property
-    def tail(self) -> Iterator[OutputField]:
-        return islice(self, 1, None)  # type: ignore[arg-type]
 
+@dataclass(frozen=True)
+class ConversionContext:
+    params: VarTuple[FieldLoc]
+    loc_stacks: VarTuple[LocStack[FieldLoc]] = field(init=False, hash=False, repr=False, compare=False)
+
+    def __post_init__(self):
+        super().__setattr__("loc_stacks", tuple(LocStack(param) for param in self.params))
 
-class LinkingDest(ImmutableStack[InputField]):
-    def to_loc_stack(self) -> LocStack:
-        return LocStack.from_iter(map(input_field_to_loc_map, self))
+
+LinkingSource = LocStack[ConversionSourceItem]
 
 
 @dataclass(frozen=True)
@@ -50,22 +46,20 @@ class ConstantLinking:
 @dataclass(frozen=True)
 class LinkingResult:
     linking: Union[FieldLinking, ConstantLinking]
-    is_default: bool = False
-
-
-SourceCandidates = VarTuple[Union[LinkingSource, VarTuple[LinkingSource]]]
 
 
 @dataclass(frozen=True)
 class LinkingRequest(Request[LinkingResult]):
-    sources: SourceCandidates
-    destination: LinkingDest
+    sources: VarTuple[LinkingSource]
+    context: ConversionContext
+    destination: LocStack[ConversionDestItem]
 
 
 @dataclass(frozen=True)
 class CoercerRequest(Request[Coercer]):
-    src: LinkingSource
-    dst: LinkingDest
+    src: LocStack[ConversionSourceItem]
+    ctx: ConversionContext
+    dst: LocStack[ConversionDestItem]
 
 
 @dataclass(frozen=True)
@@ -76,10 +70,3 @@ class UnlinkedOptionalPolicy:
 @dataclass(frozen=True)
 class UnlinkedOptionalPolicyRequest(LocatedRequest[UnlinkedOptionalPolicy]):
     pass
-
-
-@dataclass(frozen=True)
-class ConverterRequest(Request):
-    signature: Signature
-    function_name: Optional[str]
-    stub_function: Optional[Callable]
diff --git a/src/adaptix/_internal/datastructures.py b/src/adaptix/_internal/datastructures.py
index 1d4afca4..442664ee 100644
--- a/src/adaptix/_internal/datastructures.py
+++ b/src/adaptix/_internal/datastructures.py
@@ -12,13 +12,13 @@
     Mapping,
     Optional,
     Protocol,
-    Sequence,
+    Reversible,
+    Sized,
     Tuple,
     Type,
     TypeVar,
     Union,
     ValuesView,
-    overload,
     runtime_checkable,
 )
 
@@ -182,7 +182,7 @@ def get_or_raise(
         try:
             return self._mapping[key]  # type: ignore[index,return-value]
         except KeyError:
-            raise exception_factory() from None  # noqa: RSE102
+            raise exception_factory() from None
 
     def keys(self) -> KeysView[Type[H]]:
         return self._mapping.keys()
@@ -217,46 +217,36 @@ def discard(self: CM, *classes: Type[H]) -> CM:
         )
 
 
-T = TypeVar("T")
+T_co = TypeVar("T_co", covariant=True)
 StackT = TypeVar("StackT", bound="ImmutableStack")
 
 
-class ImmutableStack(Sequence[T], Generic[T]):
+class ImmutableStack(Reversible[T_co], Hashable, Sized, Generic[T_co]):
     __slots__ = ("_tuple", )
 
-    def __init__(self, *args: T):
+    def __init__(self, *args: T_co):
         self._tuple = args
 
     @classmethod
-    def from_tuple(cls: Type[StackT], tpl: VarTuple[T]) -> StackT:
+    def _from_tuple(cls: Type[StackT], tpl: VarTuple[T_co]) -> StackT:
         self = cls.__new__(cls)
         self._tuple = tpl
         return self
 
     @classmethod
-    def from_iter(cls: Type[StackT], iterable: Iterable[T]) -> StackT:
-        return cls.from_tuple(tuple(iterable))
+    def from_iter(cls: Type[StackT], iterable: Iterable[T_co]) -> StackT:
+        return cls._from_tuple(tuple(iterable))
 
     @property
-    def last(self) -> T:
-        return self[-1]
+    def last(self) -> T_co:
+        return self._tuple[-1]
+
+    def __getitem__(self, item: int) -> T_co:
+        return self._tuple[item]
 
     def __repr__(self):
         return f"{type(self).__name__}{self._tuple!r}"
 
-    @overload
-    def __getitem__(self, index: int) -> T:
-        ...
-
-    @overload
-    def __getitem__(self: StackT, index: slice) -> StackT:
-        ...
-
-    def __getitem__(self, index):
-        if isinstance(index, int):
-            return self._tuple[index]
-        return self.from_tuple(self._tuple[index])
-
     def __len__(self):
         return len(self._tuple)
 
@@ -268,11 +258,20 @@ def __eq__(self, other):
             return self._tuple == other._tuple
         return NotImplemented
 
-    def __iter__(self) -> Iterator[T]:
+    def __iter__(self) -> Iterator[T_co]:
         return iter(self._tuple)
 
-    def append_with(self: StackT, item: T) -> StackT:
-        return self.from_tuple((*self._tuple, item))
+    def __reversed__(self) -> Iterator[T_co]:
+        return reversed(self._tuple)
+
+    def append_with(self: StackT, item: T_co) -> StackT:  # type: ignore[misc]
+        return self._from_tuple((*self._tuple, item))
+
+    def replace_last(self: StackT, item: T_co) -> StackT:  # type: ignore[misc]
+        return self._from_tuple((*islice(self, len(self) - 1), item))
+
+    def reversed_slice(self: StackT, end_offset: int) -> StackT:
+        return self._from_tuple(self._tuple[:len(self) - end_offset])
 
-    def replace_last(self: StackT, item: T) -> StackT:
-        return self.from_tuple((*islice(self, len(self) - 1), item))
+    def count(self, item: T_co) -> int:  # type: ignore[misc]
+        return sum(loc == item for loc in reversed(self))
diff --git a/src/adaptix/_internal/model_tools/definitions.py b/src/adaptix/_internal/model_tools/definitions.py
index 8a984f62..7799c286 100644
--- a/src/adaptix/_internal/model_tools/definitions.py
+++ b/src/adaptix/_internal/model_tools/definitions.py
@@ -1,4 +1,3 @@
-# pylint: disable=invalid-overridden-method,arguments-differ
 from abc import ABC, abstractmethod
 from dataclasses import dataclass, field
 from enum import Enum
@@ -327,20 +326,20 @@ class Shape(Generic[Inp, Out]):
 ShapeIntrospector = Callable[[Any], Shape]
 
 
-class IntrospectionImpossible(Exception):
+class IntrospectionError(Exception):
     pass
 
 
-class NoTargetPackage(IntrospectionImpossible):
+class NoTargetPackageError(IntrospectionError):
     def __init__(self, requirement: DistributionRequirement):
         self.requirement = requirement
 
 
-class PackageIsTooOld(IntrospectionImpossible):
+class TooOldPackageError(IntrospectionError):
     def __init__(self, requirement: DistributionVersionRequirement):
         self.requirement = requirement
 
 
-class ClarifiedIntrospectionImpossible(IntrospectionImpossible):
+class ClarifiedIntrospectionError(IntrospectionError):
     def __init__(self, description: str):
         self.description = description
diff --git a/src/adaptix/_internal/model_tools/introspection/attrs.py b/src/adaptix/_internal/model_tools/introspection/attrs.py
index ae044ec5..9ee857cd 100644
--- a/src/adaptix/_internal/model_tools/introspection/attrs.py
+++ b/src/adaptix/_internal/model_tools/introspection/attrs.py
@@ -10,14 +10,14 @@
     FullShape,
     InputField,
     InputShape,
-    IntrospectionImpossible,
+    IntrospectionError,
     NoDefault,
-    NoTargetPackage,
+    NoTargetPackageError,
     OutputField,
     OutputShape,
-    PackageIsTooOld,
     Param,
     Shape,
+    TooOldPackageError,
     create_attr_accessor,
 )
 from .class_init import get_class_init_shape
@@ -144,20 +144,20 @@ def _get_output_shape(attrs_fields, type_hints) -> OutputShape:
 def get_attrs_shape(tp) -> FullShape:
     if not HAS_SUPPORTED_ATTRS_PKG:
         if not HAS_ATTRS_PKG:
-            raise NoTargetPackage(HAS_ATTRS_PKG)
-        raise PackageIsTooOld(HAS_SUPPORTED_ATTRS_PKG)
+            raise NoTargetPackageError(HAS_ATTRS_PKG)
+        raise TooOldPackageError(HAS_SUPPORTED_ATTRS_PKG)
 
     try:
         is_attrs = attrs.has(tp)
     except TypeError:
-        raise IntrospectionImpossible
+        raise IntrospectionError
     if not is_attrs:
-        raise IntrospectionImpossible
+        raise IntrospectionError
 
     try:
         attrs_fields = attrs.fields(tp)
     except (TypeError, attrs.exceptions.NotAnAttrsClassError):
-        raise IntrospectionImpossible
+        raise IntrospectionError
 
     type_hints = get_all_type_hints(tp)
     return Shape(
diff --git a/src/adaptix/_internal/model_tools/introspection/callable.py b/src/adaptix/_internal/model_tools/introspection/callable.py
index b69787ea..68b7b625 100644
--- a/src/adaptix/_internal/model_tools/introspection/callable.py
+++ b/src/adaptix/_internal/model_tools/introspection/callable.py
@@ -11,7 +11,7 @@
     DefaultValue,
     InputField,
     InputShape,
-    IntrospectionImpossible,
+    IntrospectionError,
     NoDefault,
     Param,
     ParamKind,
@@ -47,7 +47,7 @@ def _upack_typed_dict_kwargs(
 
     try:
         shape = get_typed_dict_shape(norm.args[0].source)
-    except IntrospectionImpossible:
+    except IntrospectionError:
         return (), (), param_kwargs
 
     return shape.input.fields, shape.input.params, shape.input.kwargs
@@ -57,13 +57,13 @@ def get_callable_shape(func, params_slice=slice(0, None)) -> Shape[InputShape, N
     try:
         signature = inspect.signature(func)
     except TypeError:
-        raise IntrospectionImpossible
+        raise IntrospectionError
 
     params = list(signature.parameters.values())[params_slice]
     kinds = [p.kind for p in params]
 
     if Parameter.VAR_POSITIONAL in kinds:
-        raise IntrospectionImpossible(
+        raise IntrospectionError(
             f"Can not create InputShape"
             f" from the function that has {Parameter.VAR_POSITIONAL}"
             f" parameter",
diff --git a/src/adaptix/_internal/model_tools/introspection/class_init.py b/src/adaptix/_internal/model_tools/introspection/class_init.py
index f46338bf..e417f1d8 100644
--- a/src/adaptix/_internal/model_tools/introspection/class_init.py
+++ b/src/adaptix/_internal/model_tools/introspection/class_init.py
@@ -1,12 +1,12 @@
 from dataclasses import replace
 
-from ..definitions import InputShape, IntrospectionImpossible, Shape
+from ..definitions import InputShape, IntrospectionError, Shape
 from .callable import get_callable_shape
 
 
 def get_class_init_shape(tp) -> Shape[InputShape, None]:
     if not isinstance(tp, type):
-        raise IntrospectionImpossible
+        raise IntrospectionError
 
     shape = get_callable_shape(
         tp.__init__,  # type: ignore[misc]
diff --git a/src/adaptix/_internal/model_tools/introspection/dataclass.py b/src/adaptix/_internal/model_tools/introspection/dataclass.py
index 1548b14e..d5360d41 100644
--- a/src/adaptix/_internal/model_tools/introspection/dataclass.py
+++ b/src/adaptix/_internal/model_tools/introspection/dataclass.py
@@ -11,7 +11,7 @@
     FullShape,
     InputField,
     InputShape,
-    IntrospectionImpossible,
+    IntrospectionError,
     NoDefault,
     OutputField,
     OutputShape,
@@ -70,7 +70,7 @@ def get_dataclass_shape(tp) -> FullShape:
     """
 
     if not is_dataclass(tp):
-        raise IntrospectionImpossible
+        raise IntrospectionError
 
     name_to_dc_field = all_dc_fields(tp)
     dc_fields_public = dc_fields(tp)
diff --git a/src/adaptix/_internal/model_tools/introspection/named_tuple.py b/src/adaptix/_internal/model_tools/introspection/named_tuple.py
index 72d46bd1..455c0887 100644
--- a/src/adaptix/_internal/model_tools/introspection/named_tuple.py
+++ b/src/adaptix/_internal/model_tools/introspection/named_tuple.py
@@ -7,7 +7,7 @@
     FullShape,
     InputField,
     InputShape,
-    IntrospectionImpossible,
+    IntrospectionError,
     NoDefault,
     OutputField,
     OutputShape,
@@ -19,9 +19,8 @@
 
 
 def get_named_tuple_shape(tp) -> FullShape:
-    # pylint: disable=protected-access
     if not is_named_tuple_class(tp):
-        raise IntrospectionImpossible
+        raise IntrospectionError
 
     type_hints = get_all_type_hints(tp)
     if tuple in tp.__bases__:
diff --git a/src/adaptix/_internal/model_tools/introspection/sqlalchemy.py b/src/adaptix/_internal/model_tools/introspection/sqlalchemy.py
index aa399ca9..bb8b1610 100644
--- a/src/adaptix/_internal/model_tools/introspection/sqlalchemy.py
+++ b/src/adaptix/_internal/model_tools/introspection/sqlalchemy.py
@@ -22,21 +22,21 @@
 from ...feature_requirement import HAS_SQLALCHEMY_PKG, HAS_SUPPORTED_SQLALCHEMY_PKG
 from ...type_tools import get_all_type_hints
 from ..definitions import (
-    ClarifiedIntrospectionImpossible,
+    ClarifiedIntrospectionError,
     DefaultFactory,
     DefaultValue,
     FullShape,
     InputField,
     InputShape,
-    IntrospectionImpossible,
+    IntrospectionError,
     NoDefault,
-    NoTargetPackage,
+    NoTargetPackageError,
     OutputField,
     OutputShape,
-    PackageIsTooOld,
     Param,
     ParamKind,
     Shape,
+    TooOldPackageError,
     create_attr_accessor,
 )
 
@@ -116,7 +116,7 @@ def _get_autoincrement_column(table: "Table"):
         return table.autoincrement_column
     except AttributeError:
         # for old sqlalchemy
-        return table._autoincrement_column  # pylint: disable=protected-access
+        return table._autoincrement_column
 
 
 def _get_input_shape(
@@ -227,19 +227,19 @@ def _get_output_shape(
 def get_sqlalchemy_shape(tp) -> FullShape:
     if not HAS_SUPPORTED_SQLALCHEMY_PKG:
         if not HAS_SQLALCHEMY_PKG:
-            raise NoTargetPackage(HAS_SQLALCHEMY_PKG)
-        raise PackageIsTooOld(HAS_SUPPORTED_SQLALCHEMY_PKG)
+            raise NoTargetPackageError(HAS_SQLALCHEMY_PKG)
+        raise TooOldPackageError(HAS_SUPPORTED_SQLALCHEMY_PKG)
 
     try:
         mapper = sqlalchemy.inspect(tp)
     except NoInspectionAvailable:
-        raise IntrospectionImpossible
+        raise IntrospectionError
 
     if not isinstance(mapper, Mapper):
-        raise IntrospectionImpossible
+        raise IntrospectionError
 
     if not isinstance(mapper.local_table, Table):
-        raise ClarifiedIntrospectionImpossible(
+        raise ClarifiedIntrospectionError(
             "Only sqlalchemy mapping to Table is supported",
         )
 
diff --git a/src/adaptix/_internal/model_tools/introspection/typed_dict.py b/src/adaptix/_internal/model_tools/introspection/typed_dict.py
index cbc0150d..bcc67ec7 100644
--- a/src/adaptix/_internal/model_tools/introspection/typed_dict.py
+++ b/src/adaptix/_internal/model_tools/introspection/typed_dict.py
@@ -9,7 +9,7 @@
     FullShape,
     InputField,
     InputShape,
-    IntrospectionImpossible,
+    IntrospectionError,
     NoDefault,
     OutputField,
     OutputShape,
@@ -79,7 +79,7 @@ def get_typed_dict_shape(tp) -> FullShape:
     # __annotations__ of TypedDict contain also parents' type hints unlike any other classes,
     # so overriden_types always is empty
     if not is_typed_dict_class(tp):
-        raise IntrospectionImpossible
+        raise IntrospectionError
 
     type_hints = _get_td_hints(tp)
 
diff --git a/src/adaptix/_internal/morphing/concrete_provider.py b/src/adaptix/_internal/morphing/concrete_provider.py
index 00efa9d1..af692b28 100644
--- a/src/adaptix/_internal/morphing/concrete_provider.py
+++ b/src/adaptix/_internal/morphing/concrete_provider.py
@@ -13,7 +13,7 @@
 from ..provider.essential import CannotProvide, Mediator
 from ..provider.loc_stack_filtering import P, create_loc_stack_checker
 from ..provider.provider_template import for_predicate
-from ..provider.request_cls import LocatedRequest, StrictCoercionRequest, TypeHintLoc, find_owner_with_field
+from ..provider.request_cls import LocatedRequest, StrictCoercionRequest, find_owner_with_field
 from ..provider.static_provider import static_provision_action
 from ..special_cases_optimization import as_is_stub
 from .load_error import FormatMismatchLoadError, TypeLoadError, ValueLoadError
@@ -143,10 +143,9 @@ class BytearrayBase64Provider(LoaderProvider, Base64DumperMixin):
     _BYTES_PROVIDER = BytesBase64Provider()
 
     def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        request.last_map.get_or_raise(TypeHintLoc, lambda: CannotProvide)
         bytes_loader = self._BYTES_PROVIDER.apply_provider(
             mediator,
-            replace(request, loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(bytes))),
+            replace(request, loc_stack=request.loc_stack.replace_last_type(bytes)),
         )
 
         def bytearray_base64_loader(data):
@@ -196,7 +195,7 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
 
 
 def int_strict_coercion_loader(data):
-    if type(data) is int:  # pylint: disable=unidiomatic-typecheck # noqa: E721
+    if type(data) is int:  # noqa: E721
         return data
     raise TypeLoadError(int, data)
 
@@ -246,7 +245,7 @@ def float_lax_coercion_loader(data):
 
 
 def str_strict_coercion_loader(data):
-    if type(data) is str:  # pylint: disable=unidiomatic-typecheck # noqa: E721
+    if type(data) is str:  # noqa: E721
         return data
     raise TypeLoadError(str, data)
 
@@ -259,7 +258,7 @@ def str_strict_coercion_loader(data):
 
 
 def bool_strict_coercion_loader(data):
-    if type(data) is bool:  # pylint: disable=unidiomatic-typecheck # noqa: E721
+    if type(data) is bool:  # noqa: E721
         return data
     raise TypeLoadError(bool, data)
 
@@ -272,12 +271,12 @@ def bool_strict_coercion_loader(data):
 
 
 def decimal_strict_coercion_loader(data):
-    if type(data) is str:  # pylint: disable=unidiomatic-typecheck # noqa: E721
+    if type(data) is str:  # noqa: E721
         try:
             return Decimal(data)
         except InvalidOperation:
             raise ValueLoadError("Bad string format", data)
-    if type(data) is Decimal:  # pylint: disable=unidiomatic-typecheck
+    if type(data) is Decimal:
         return data
     raise TypeLoadError(Union[str, Decimal], data)
 
@@ -360,7 +359,7 @@ def _provide_substitute(self, mediator: Mediator, request: LocatedRequest) -> Lo
         self._apply_loc_stack_checker(mediator, request)
 
         try:
-            owner_loc_map, _field_loc_map = find_owner_with_field(request.loc_stack)
+            owner_loc, _field_loc = find_owner_with_field(request.loc_stack)
         except ValueError:
             raise CannotProvide(
                 "Owner type is not found",
@@ -371,7 +370,7 @@ def _provide_substitute(self, mediator: Mediator, request: LocatedRequest) -> Lo
         return mediator.delegating_provide(
             replace(
                 request,
-                loc_stack=request.loc_stack.add_to_last_map(owner_loc_map[TypeHintLoc]),
+                loc_stack=request.loc_stack.replace_last_type(owner_loc.type),
             ),
         )
 
diff --git a/src/adaptix/_internal/morphing/constant_length_tuple_provider.py b/src/adaptix/_internal/morphing/constant_length_tuple_provider.py
index c0e54cf9..3f9bc188 100644
--- a/src/adaptix/_internal/morphing/constant_length_tuple_provider.py
+++ b/src/adaptix/_internal/morphing/constant_length_tuple_provider.py
@@ -8,16 +8,9 @@
 from ..definitions import DebugTrail
 from ..feature_requirement import HAS_UNPACK
 from ..provider.essential import CannotProvide, Mediator
+from ..provider.location import GenericParamLoc
 from ..provider.provider_template import for_predicate
-from ..provider.request_cls import (
-    DebugTrailRequest,
-    GenericParamLoc,
-    LocMap,
-    StrictCoercionRequest,
-    TypeHintLoc,
-    get_type_from_request,
-    try_normalize_type,
-)
+from ..provider.request_cls import DebugTrailRequest, StrictCoercionRequest, get_type_from_request, try_normalize_type
 from ..struct_trail import append_trail, render_trail_as_note
 from .load_error import (
     AggregateLoadError,
@@ -50,9 +43,9 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
             [
                 LoaderRequest(
                     loc_stack=request.loc_stack.append_with(
-                        LocMap(
-                            TypeHintLoc(type=tp.source),
-                            GenericParamLoc(generic_pos=i),
+                        GenericParamLoc(
+                            type=tp.source,
+                            generic_pos=i,
                         ),
                     ),
                 )
@@ -93,7 +86,7 @@ def _get_dt_sc_loader(self, tuple_mapper):
         def dt_sc_loader(data):
             if isinstance(data, CollectionsMapping):
                 raise ExcludedTypeLoadError(tuple, Mapping, data)
-            if type(data) is str:  # pylint: disable=unidiomatic-typecheck # noqa: E721
+            if type(data) is str:  # noqa: E721
                 raise ExcludedTypeLoadError(tuple, str, data)
 
             try:
@@ -127,7 +120,7 @@ def dt_all_loader(data):
                     yield loader(field)
                 except LoadError as e:
                     errors.append(append_trail(e, idx))
-                except Exception as e:  # pylint: disable=broad-exception-caught
+                except Exception as e:
                     errors.append(append_trail(e, idx))
                     has_unexpected_error = True
 
@@ -165,7 +158,7 @@ def dt_first_loader(data):
             for loader, field in zip(loaders, data):
                 try:
                     yield loader(field)
-                except Exception as e:  # pylint: disable=broad-exception-caught
+                except Exception as e:
                     append_trail(e, idx)
                     raise
                 idx += 1
@@ -200,7 +193,7 @@ def _get_dt_disable_sc_loader(self, loaders: Collection[Loader]):
         def dt_disable_sc_loader(data):
             if isinstance(data, CollectionsMapping):
                 raise ExcludedTypeLoadError(tuple, Mapping, data)
-            if type(data) is str:  # pylint: disable=unidiomatic-typecheck # noqa: E721
+            if type(data) is str:  # noqa: E721
                 raise ExcludedTypeLoadError(tuple, str, data)
 
             try:
@@ -235,9 +228,9 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
             [
                 DumperRequest(
                     loc_stack=request.loc_stack.append_with(
-                        LocMap(
-                            TypeHintLoc(type=tp.source),
-                            GenericParamLoc(generic_pos=i),
+                        GenericParamLoc(
+                            type=tp.source,
+                            generic_pos=i,
                         ),
                     ),
                 )
@@ -277,7 +270,7 @@ def dt_all_dumper(data):
             for dumper, field in zip(dumpers, data):
                 try:
                     yield dumper(field)
-                except Exception as e:  # pylint: disable=broad-exception-caught
+                except Exception as e:
                     errors.append(append_trail(e, idx))
 
                 idx += 1
@@ -309,7 +302,7 @@ def dt_first_dumper(data):
             for dumper, field in zip(dumpers, data):
                 try:
                     yield dumper(field)
-                except Exception as e:  # pylint: disable=broad-exception-caught
+                except Exception as e:
                     append_trail(e, idx)
                     raise
                 idx += 1
diff --git a/src/adaptix/_internal/morphing/dict_provider.py b/src/adaptix/_internal/morphing/dict_provider.py
index 74f97cd8..5036a033 100644
--- a/src/adaptix/_internal/morphing/dict_provider.py
+++ b/src/adaptix/_internal/morphing/dict_provider.py
@@ -8,16 +8,9 @@
 from ..definitions import DebugTrail
 from ..morphing.provider_template import DumperProvider, LoaderProvider
 from ..provider.essential import Mediator
+from ..provider.location import GenericParamLoc
 from ..provider.provider_template import for_predicate
-from ..provider.request_cls import (
-    DebugTrailRequest,
-    GenericParamLoc,
-    LocatedRequest,
-    LocMap,
-    TypeHintLoc,
-    get_type_from_request,
-    try_normalize_type,
-)
+from ..provider.request_cls import DebugTrailRequest, LocatedRequest, get_type_from_request, try_normalize_type
 from ..struct_trail import ItemKey, append_trail, render_trail_as_note
 from ..type_tools import BaseNormType
 from .load_error import AggregateLoadError, LoadError, TypeLoadError
@@ -38,9 +31,9 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         key_loader = mediator.mandatory_provide(
             LoaderRequest(
                 loc_stack=request.loc_stack.append_with(
-                    LocMap(
-                        TypeHintLoc(type=key.source),
-                        GenericParamLoc(generic_pos=0),
+                    GenericParamLoc(
+                        type=key.source,
+                        generic_pos=0,
                     ),
                 ),
             ),
@@ -49,9 +42,9 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         value_loader = mediator.mandatory_provide(
             LoaderRequest(
                 loc_stack=request.loc_stack.append_with(
-                    LocMap(
-                        TypeHintLoc(type=value.source),
-                        GenericParamLoc(generic_pos=1),
+                    GenericParamLoc(
+                        type=value.source,
+                        generic_pos=1,
                     ),
                 ),
             ),
@@ -132,7 +125,7 @@ def dict_loader_dt_all(data):
                     loaded_key = key_loader(k)
                 except LoadError as e:
                     errors.append(append_trail(e, ItemKey(k)))
-                except Exception as e:  # pylint: disable=broad-exception-caught
+                except Exception as e:
                     errors.append(append_trail(e, ItemKey(k)))
                     has_unexpected_error = True
 
@@ -140,7 +133,7 @@ def dict_loader_dt_all(data):
                     loaded_value = value_loader(v)
                 except LoadError as e:
                     errors.append(append_trail(e, k))
-                except Exception as e:  # pylint: disable=broad-exception-caught
+                except Exception as e:
                     errors.append(append_trail(e, k))
                     has_unexpected_error = True
 
@@ -167,9 +160,9 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         key_dumper = mediator.mandatory_provide(
             DumperRequest(
                 loc_stack=request.loc_stack.append_with(
-                    LocMap(
-                        TypeHintLoc(type=key.source),
-                        GenericParamLoc(generic_pos=0),
+                    GenericParamLoc(
+                        type=key.source,
+                        generic_pos=0,
                     ),
                 ),
             ),
@@ -178,9 +171,9 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         value_dumper = mediator.mandatory_provide(
             DumperRequest(
                 loc_stack=request.loc_stack.append_with(
-                    LocMap(
-                        TypeHintLoc(type=value.source),
-                        GenericParamLoc(generic_pos=1),
+                    GenericParamLoc(
+                        type=value.source,
+                        generic_pos=1,
                     ),
                 ),
             ),
@@ -252,12 +245,12 @@ def dict_dumper_dt_all(data: Mapping):
             for k, v in data.items():
                 try:
                     dumped_key = key_dumper(k)
-                except Exception as e:  # pylint: disable=broad-exception-caught
+                except Exception as e:
                     errors.append(append_trail(e, ItemKey(k)))
 
                 try:
                     dumped_value = value_dumper(v)
-                except Exception as e:  # pylint: disable=broad-exception-caught
+                except Exception as e:
                     errors.append(append_trail(e, k))
 
                 if not errors:
@@ -289,7 +282,7 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         dict_type_hint = Dict[key.source, value.source]  # type: ignore[misc, name-defined]
         dict_loader = self._DICT_PROVIDER.apply_provider(
             mediator,
-            replace(request, loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(dict_type_hint))),
+            replace(request, loc_stack=request.loc_stack.replace_last_type(dict_type_hint)),
         )
         default_factory = self.default_factory
 
@@ -304,5 +297,5 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
 
         return self._DICT_PROVIDER.apply_provider(
             mediator,
-            replace(request, loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(dict_type_hint))),
+            replace(request, loc_stack=request.loc_stack.replace_last_type(dict_type_hint)),
         )
diff --git a/src/adaptix/_internal/morphing/enum_provider.py b/src/adaptix/_internal/morphing/enum_provider.py
index 71478f20..4b1af0b2 100644
--- a/src/adaptix/_internal/morphing/enum_provider.py
+++ b/src/adaptix/_internal/morphing/enum_provider.py
@@ -12,7 +12,7 @@
 from ..provider.essential import CannotProvide, Mediator
 from ..provider.loc_stack_filtering import DirectMediator, LastLocMapChecker
 from ..provider.provider_template import for_predicate
-from ..provider.request_cls import LocMap, StrictCoercionRequest, TypeHintLoc, get_type_from_request
+from ..provider.request_cls import StrictCoercionRequest, TypeHintLoc, get_type_from_request
 from ..type_tools import is_subclass_soft, normalize_type
 from .load_error import (
     BadVariantLoadError,
@@ -141,9 +141,7 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         value_loader = mediator.mandatory_provide(
             LoaderRequest(
                 loc_stack=request.loc_stack.append_with(
-                    LocMap(
-                        TypeHintLoc(type=self._value_type),
-                    ),
+                    TypeHintLoc(type=self._value_type),
                 ),
             ),
         )
@@ -161,9 +159,7 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         value_dumper = mediator.mandatory_provide(
             DumperRequest(
                 loc_stack=request.loc_stack.append_with(
-                    LocMap(
-                        TypeHintLoc(type=self._value_type),
-                    ),
+                    TypeHintLoc(type=self._value_type),
                 ),
             ),
         )
@@ -193,7 +189,7 @@ def _make_loader(self, enum):
         if value_to_member is None:
             def enum_exact_loader(data):
                 # since MyEnum(MyEnum.MY_CASE) == MyEnum.MY_CASE
-                if type(data) is enum:  # pylint: disable=unidiomatic-typecheck
+                if type(data) is enum:
                     raise BadVariantLoadError(variants, data)
 
                 try:
@@ -219,7 +215,6 @@ def _get_exact_value_to_member(self, enum: Type[Enum]) -> Optional[Mapping[Any,
         except TypeError:
             return None
 
-        # pylint: disable=comparison-with-callable,protected-access
         if getattr(enum._missing_, "__func__", None) != Enum._missing_.__func__:  # type: ignore[attr-defined]
             return None
 
@@ -250,7 +245,7 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
             )
 
         def flag_loader(data):
-            if type(data) is not int:   # pylint: disable=unidiomatic-typecheck  # noqa: E721
+            if type(data) is not int: # noqa: E721
                 raise TypeLoadError(int, data)
 
             if data < 0 or data > flag_mask:
@@ -258,7 +253,6 @@ def flag_loader(data):
 
             # data already has been validated for all edge cases
             # so enum lookup cannot raise an error
-
             return enum(data)
 
         return flag_loader
diff --git a/src/adaptix/_internal/morphing/facade/provider.py b/src/adaptix/_internal/morphing/facade/provider.py
index a3cece03..516a2224 100644
--- a/src/adaptix/_internal/morphing/facade/provider.py
+++ b/src/adaptix/_internal/morphing/facade/provider.py
@@ -417,7 +417,6 @@ def validator(
     error: Union[str, Callable[[Any], LoadError], None] = None,
     chain: Chain = Chain.LAST,
 ) -> Provider:
-    # pylint: disable=C3001
     exception_factory = (
         (lambda x: ValidationLoadError(error, x))
         if error is None or isinstance(error, str) else
diff --git a/src/adaptix/_internal/morphing/facade/retort.py b/src/adaptix/_internal/morphing/facade/retort.py
index 6ef8d4de..3134fd3c 100644
--- a/src/adaptix/_internal/morphing/facade/retort.py
+++ b/src/adaptix/_internal/morphing/facade/retort.py
@@ -13,7 +13,7 @@
 from ...provider.essential import Provider, Request
 from ...provider.loc_stack_filtering import P
 from ...provider.provider_template import ValueProvider
-from ...provider.request_cls import DebugTrailRequest, LocMap, LocStack, StrictCoercionRequest, TypeHintLoc
+from ...provider.request_cls import DebugTrailRequest, LocStack, StrictCoercionRequest, TypeHintLoc
 from ...provider.shape_provider import BUILTIN_SHAPE_PROVIDER
 from ...retort.operating_retort import OperatingRetort
 from ...struct_trail import render_trail_as_note
@@ -197,7 +197,6 @@ def replace(
         strict_coercion: Optional[bool] = None,
         debug_trail: Optional[DebugTrail] = None,
     ) -> AR:
-        # pylint: disable=protected-access
         with self._clone() as clone:
             if strict_coercion is not None:
                 clone._strict_coercion = strict_coercion
@@ -208,7 +207,6 @@ def replace(
         return clone
 
     def extend(self: AR, *, recipe: Iterable[Provider]) -> AR:
-        # pylint: disable=protected-access
         with self._clone() as clone:
             clone._inc_instance_recipe = (
                 tuple(recipe) + clone._inc_instance_recipe
@@ -233,7 +231,7 @@ def get_loader(self, tp: Type[T]) -> Loader[T]:
 
     def _make_loader(self, tp: Type[T]) -> Loader[T]:
         loader_ = self._facade_provide(
-            LoaderRequest(loc_stack=LocStack(LocMap(TypeHintLoc(type=tp)))),
+            LoaderRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
             error_message=f"Cannot produce loader for type {tp!r}",
         )
         if self._debug_trail == DebugTrail.FIRST:
@@ -259,7 +257,7 @@ def get_dumper(self, tp: Type[T]) -> Dumper[T]:
 
     def _make_dumper(self, tp: Type[T]) -> Dumper[T]:
         dumper_ = self._facade_provide(
-            DumperRequest(loc_stack=LocStack(LocMap(TypeHintLoc(type=tp)))),
+            DumperRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
             error_message=f"Cannot produce dumper for type {tp!r}",
         )
         if self._debug_trail == DebugTrail.FIRST:
diff --git a/src/adaptix/_internal/morphing/generic_provider.py b/src/adaptix/_internal/morphing/generic_provider.py
index ddfc4e96..100d459e 100644
--- a/src/adaptix/_internal/morphing/generic_provider.py
+++ b/src/adaptix/_internal/morphing/generic_provider.py
@@ -11,12 +11,11 @@
 from ..definitions import DebugTrail
 from ..feature_requirement import HAS_PY_39
 from ..provider.essential import CannotProvide, Mediator
+from ..provider.location import GenericParamLoc
 from ..provider.provider_template import for_predicate
 from ..provider.request_cls import (
     DebugTrailRequest,
-    GenericParamLoc,
     LocatedRequest,
-    LocMap,
     LocStack,
     StrictCoercionRequest,
     TypeHintLoc,
@@ -35,7 +34,7 @@
 class NewTypeUnwrappingProvider(StaticProvider):
     @static_provision_action
     def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Loader:
-        loc = request.last_map.get_or_raise(TypeHintLoc, CannotProvide)
+        loc = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide)
 
         if not is_new_type(loc.type):
             raise CannotProvide
@@ -43,7 +42,7 @@ def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Lo
         return mediator.delegating_provide(
             replace(
                 request,
-                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(type=loc.type.__supertype__)),
+                loc_stack=request.loc_stack.replace_last_type(loc.type.__supertype__),
             ),
         )
 
@@ -51,7 +50,7 @@ def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Lo
 class TypeHintTagsUnwrappingProvider(StaticProvider):
     @static_provision_action
     def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Loader:
-        loc = request.last_map.get_or_raise(TypeHintLoc, CannotProvide)
+        loc = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide)
         norm = try_normalize_type(loc.type)
         unwrapped = strip_tags(norm)
         if unwrapped.source == loc.type:  # type has not changed, continue search
@@ -60,7 +59,7 @@ def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Lo
         return mediator.delegating_provide(
             replace(
                 request,
-                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(type=unwrapped.source)),
+                loc_stack=request.loc_stack.replace_last_type(unwrapped.source),
             ),
         )
 
@@ -68,7 +67,7 @@ def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Lo
 class TypeAliasUnwrappingProvider(StaticProvider):
     @static_provision_action
     def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Loader:
-        loc = request.last_map.get_or_raise(TypeHintLoc, CannotProvide)
+        loc = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide)
         norm = try_normalize_type(loc.type)
         if not isinstance(norm, NormTypeAlias):
             raise CannotProvide
@@ -77,13 +76,13 @@ def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Lo
         return mediator.delegating_provide(
             replace(
                 request,
-                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(type=unwrapped)),
+                loc_stack=request.loc_stack.replace_last_type(unwrapped),
             ),
         )
 
 
 def _is_exact_zero_or_one(arg):
-    return type(arg) is int and arg in (0, 1)  # pylint: disable=unidiomatic-typecheck  # noqa: E721
+    return type(arg) is int and arg in (0, 1)  # noqa: E721
 
 
 @dataclass
@@ -120,9 +119,7 @@ def _fetch_enum_loaders(
         requests = [
             LoaderRequest(
                 loc_stack=request.loc_stack.append_with(
-                    LocMap(
-                        TypeHintLoc(type=enum_cls),
-                    ),
+                    TypeHintLoc(type=enum_cls),
                 ),
             ) for enum_cls in enum_classes
         ]
@@ -137,9 +134,7 @@ def _fetch_enum_dumpers(
         requests = [
             DumperRequest(
                 loc_stack=request.loc_stack.append_with(
-                    LocMap(
-                        TypeHintLoc(type=enum_cls),
-                    ),
+                    TypeHintLoc(type=enum_cls),
                 ),
             ) for enum_cls in enum_classes
         ]
@@ -259,9 +254,9 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
             not_none_loader = mediator.mandatory_provide(
                 LoaderRequest(
                     loc_stack=request.loc_stack.append_with(
-                        LocMap(
-                            TypeHintLoc(type=not_none.source),
-                            GenericParamLoc(generic_pos=0),
+                        GenericParamLoc(
+                            type=not_none.source,
+                            generic_pos=0,
                         ),
                     ),
                 ),
@@ -277,9 +272,9 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
             [
                 LoaderRequest(
                     loc_stack=request.loc_stack.append_with(
-                        LocMap(
-                            TypeHintLoc(type=tp.source),
-                            GenericParamLoc(generic_pos=i),
+                        GenericParamLoc(
+                            type=tp.source,
+                            generic_pos=i,
                         ),
                     ),
                 )
@@ -347,7 +342,7 @@ def union_loader_dt_all(data):
                     result = loader(data)
                 except LoadError as e:
                     errors.append(e)
-                except Exception as e:  # pylint: disable=broad-exception-caught
+                except Exception as e:
                     errors.append(e)
                     has_unexpected_error = True
                 else:
@@ -375,9 +370,9 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
             not_none_dumper = mediator.mandatory_provide(
                 DumperRequest(
                     loc_stack=request.loc_stack.append_with(
-                        LocMap(
-                            TypeHintLoc(type=not_none.source),
-                            GenericParamLoc(generic_pos=0),
+                        GenericParamLoc(
+                            type=not_none.source,
+                            generic_pos=0,
                         ),
                     ),
                 ),
@@ -399,9 +394,9 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
             [
                 DumperRequest(
                     loc_stack=request.loc_stack.append_with(
-                        LocMap(
-                            TypeHintLoc(type=tp.source),
-                            GenericParamLoc(generic_pos=i),
+                        GenericParamLoc(
+                            type=tp.source,
+                            generic_pos=i,
                         ),
                     ),
                 )
@@ -433,7 +428,7 @@ def optional_dumper(data):
 
 
 def path_like_dumper(data):
-    return data.__fspath__()  # pylint: disable=unnecessary-dunder-call
+    return data.__fspath__()
 
 
 @for_predicate(PathLike[str] if HAS_PY_39 else PathLike)
@@ -443,7 +438,7 @@ class PathLikeProvider(LoaderProvider, DumperProvider):
     def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         return mediator.mandatory_provide(
             LoaderRequest(
-                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(type=self._impl)),
+                loc_stack=request.loc_stack.replace_last_type(self._impl),
             ),
             lambda x: f"Cannot create loader for {PathLike}. Loader for {Path} cannot be created",
         )
diff --git a/src/adaptix/_internal/morphing/iterable_provider.py b/src/adaptix/_internal/morphing/iterable_provider.py
index 517f62d8..abdca17b 100644
--- a/src/adaptix/_internal/morphing/iterable_provider.py
+++ b/src/adaptix/_internal/morphing/iterable_provider.py
@@ -8,14 +8,12 @@
 from ..definitions import DebugTrail
 from ..morphing.provider_template import DumperProvider, LoaderProvider
 from ..provider.essential import CannotProvide, Mediator
+from ..provider.location import GenericParamLoc
 from ..provider.provider_template import for_predicate
 from ..provider.request_cls import (
     DebugTrailRequest,
-    GenericParamLoc,
     LocatedRequest,
-    LocMap,
     StrictCoercionRequest,
-    TypeHintLoc,
     get_type_from_request,
     try_normalize_type,
 )
@@ -75,9 +73,9 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         arg_loader = mediator.mandatory_provide(
             LoaderRequest(
                 loc_stack=request.loc_stack.append_with(
-                    LocMap(
-                        TypeHintLoc(type=arg),
-                        GenericParamLoc(generic_pos=0),
+                    GenericParamLoc(
+                        type=arg,
+                        generic_pos=0,
                     ),
                 ),
             ),
@@ -119,7 +117,7 @@ def iter_loader_dt_all(iterable):
                     yield loader(el)
                 except LoadError as e:
                     errors.append(append_trail(e, idx))
-                except Exception as e:  # pylint: disable=broad-exception-caught
+                except Exception as e:
                     errors.append(append_trail(e, idx))
                     has_unexpected_error = True
 
@@ -170,7 +168,7 @@ def _get_dt_sc_loader(self, iter_factory, iter_mapper):
         def iter_loader_dt_sc(data):
             if isinstance(data, CollectionsMapping):
                 raise ExcludedTypeLoadError(Iterable, Mapping, data)
-            if type(data) is str:  # pylint: disable=unidiomatic-typecheck # noqa: E721
+            if type(data) is str:  # noqa: E721
                 raise ExcludedTypeLoadError(Iterable, str, data)
 
             try:
@@ -186,7 +184,7 @@ def _get_dt_disable_sc_loader(self, iter_factory, arg_loader):
         def iter_loader_sc(data):
             if isinstance(data, CollectionsMapping):
                 raise ExcludedTypeLoadError(Iterable, Mapping, data)
-            if type(data) is str:  # pylint: disable=unidiomatic-typecheck # noqa: E721
+            if type(data) is str:  # noqa: E721
                 raise ExcludedTypeLoadError(Iterable, str, data)
 
             try:
@@ -216,9 +214,9 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         arg_dumper = mediator.mandatory_provide(
             DumperRequest(
                 loc_stack=request.loc_stack.append_with(
-                    LocMap(
-                        TypeHintLoc(type=arg),
-                        GenericParamLoc(generic_pos=0),
+                    GenericParamLoc(
+                        type=arg,
+                        generic_pos=0,
                     ),
                 ),
             ),
@@ -264,7 +262,7 @@ def iter_dumper_dt_all(iterable):
             for el in iterable:
                 try:
                     yield dumper(el)
-                except Exception as e:  # pylint: disable=broad-exception-caught
+                except Exception as e:
                     errors.append(append_trail(e, idx))
 
                 idx += 1
diff --git a/src/adaptix/_internal/morphing/load_error.py b/src/adaptix/_internal/morphing/load_error.py
index 708387ac..020c567c 100644
--- a/src/adaptix/_internal/morphing/load_error.py
+++ b/src/adaptix/_internal/morphing/load_error.py
@@ -5,14 +5,14 @@
 
 from ..common import TypeHint, VarTuple
 from ..compat import CompatExceptionGroup
-from ..utils import with_module
+from ..utils import fix_dataclass_from_builtin, with_module
 
 
 def _str_by_fields(cls):
     template = ", ".join("%s={self.%s!r}" % (fld.name, fld.name) for fld in dataclasses.fields(cls))  # noqa: UP031
     body = f'def __str__(self):\n    return f"{template}"'
     ns = {}
-    exec(body, ns, ns)  # noqa: S102  # pylint: disable=exec-used
+    exec(body, ns, ns)  # noqa: S102
     cls.__str__ = ns["__str__"]
     return cls
 
@@ -34,6 +34,7 @@ def custom_exception(cls=None, /, *, str_by_fields: bool = True, public_module:
 
 @custom_exception(str_by_fields=False)
 @dataclass(eq=False, init=False)
+@fix_dataclass_from_builtin
 class LoadError(Exception):
     """The base class for the exceptions that are raised
     when the loader gets invalid input data
diff --git a/src/adaptix/_internal/morphing/model/basic_gen.py b/src/adaptix/_internal/morphing/model/basic_gen.py
index 5b9d5490..24324b1d 100644
--- a/src/adaptix/_internal/morphing/model/basic_gen.py
+++ b/src/adaptix/_internal/morphing/model/basic_gen.py
@@ -22,7 +22,7 @@
 from ...code_tools.utils import get_literal_expr
 from ...model_tools.definitions import InputField, OutputField
 from ...provider.essential import CannotProvide, Mediator
-from ...provider.request_cls import LocatedRequest, LocStack, TypeHintLoc
+from ...provider.request_cls import LocatedRequest, LocStack
 from ...provider.static_provider import StaticProvider, static_provision_action
 from .crown_definitions import (
     BaseCrown,
@@ -85,12 +85,9 @@ def hook(data: CodeGenHookData):
     @property
     def code_pairs(self):
         return [
-            (request.loc_stack[-2].loc_map[TypeHintLoc].type, hook_data.source)
+            (request.loc_stack[-2].loc.type, hook_data.source)
             for request, hook_data in self.list
-            if (
-                len(request.loc_stack) >= 2  # noqa: PLR2004
-                and request.loc_stack[-2].loc_map.has(TypeHintLoc)
-            )
+            if len(request.loc_stack) >= 2  # noqa: PLR2004
         ]
 
     @property
diff --git a/src/adaptix/_internal/morphing/model/dumper_gen.py b/src/adaptix/_internal/morphing/model/dumper_gen.py
index 79664ac3..698eea94 100644
--- a/src/adaptix/_internal/morphing/model/dumper_gen.py
+++ b/src/adaptix/_internal/morphing/model/dumper_gen.py
@@ -163,7 +163,7 @@ def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
         builder = CodeBuilder()
         with builder(f"def {closure_name}(data):"):
             builder.extend(body_builder)
-        return builder.string(), namespace.constants
+        return builder.string(), namespace.all_constants
 
     def _is_extra_target(self, field: OutputField) -> bool:
         return field.id in self._extra_targets
diff --git a/src/adaptix/_internal/morphing/model/dumper_provider.py b/src/adaptix/_internal/morphing/model/dumper_provider.py
index 48e066e2..7a105375 100644
--- a/src/adaptix/_internal/morphing/model/dumper_provider.py
+++ b/src/adaptix/_internal/morphing/model/dumper_provider.py
@@ -1,6 +1,6 @@
 from typing import Mapping
 
-from adaptix._internal.provider.fields import output_field_to_loc_map
+from adaptix._internal.provider.fields import output_field_to_loc
 
 from ...code_tools.compiler import BasicClosureCompiler, ClosureCompiler
 from ...code_tools.name_sanitizer import BuiltinNameSanitizer, NameSanitizer
@@ -8,7 +8,7 @@
 from ...definitions import DebugTrail
 from ...model_tools.definitions import OutputShape
 from ...provider.essential import Mediator
-from ...provider.request_cls import DebugTrailRequest, TypeHintLoc
+from ...provider.request_cls import DebugTrailRequest
 from ...provider.shape_provider import OutputShapeRequest, provide_generic_resolved_shape
 from ..provider_template import DumperProvider
 from ..request_cls import DumperRequest
@@ -63,11 +63,7 @@ def _fetch_model_identity(
         shape: OutputShape,
         name_layout: OutputNameLayout,
     ) -> str:
-        return (
-            repr(request.last_map[TypeHintLoc].type)
-            if request.last_map.has(TypeHintLoc) else
-            "<unknown model>"
-        )
+        return repr(request.last_loc.type)
 
     def _create_model_dumper_gen(
         self,
@@ -86,12 +82,10 @@ def _create_model_dumper_gen(
         )
 
     def _request_to_view_string(self, request: DumperRequest) -> str:
-        if request.last_map.has(TypeHintLoc):
-            tp = request.last_map[TypeHintLoc].type
-            if isinstance(tp, type):
-                return tp.__name__
-            return str(tp)
-        return ""
+        tp = request.last_loc.type
+        if isinstance(tp, type):
+            return tp.__name__
+        return str(tp)
 
     def _merge_view_string(self, *fragments: str) -> str:
         return "_".join(filter(None, fragments))
@@ -128,7 +122,7 @@ def _fetch_field_dumpers(
     ) -> Mapping[str, Dumper]:
         dumpers = mediator.mandatory_provide_by_iterable(
             [
-                DumperRequest(loc_stack=request.loc_stack.append_with(output_field_to_loc_map(field)))
+                DumperRequest(loc_stack=request.loc_stack.append_with(output_field_to_loc(field)))
                 for field in shape.fields
             ],
             lambda: "Cannot create dumper for model. Dumpers for some fields cannot be created",
diff --git a/src/adaptix/_internal/morphing/model/loader_gen.py b/src/adaptix/_internal/morphing/model/loader_gen.py
index a602cddd..36ab10ad 100644
--- a/src/adaptix/_internal/morphing/model/loader_gen.py
+++ b/src/adaptix/_internal/morphing/model/loader_gen.py
@@ -286,7 +286,7 @@ def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
         builder = CodeBuilder()
         with builder(f"def {closure_name}(data):"):
             builder.extend(state.builder)
-        return builder.string(), namespace.constants
+        return builder.string(), namespace.all_constants
 
     def _gen_header(self, state: GenState):
         header_builder = CodeBuilder()
diff --git a/src/adaptix/_internal/morphing/model/loader_provider.py b/src/adaptix/_internal/morphing/model/loader_provider.py
index be296f7a..fb20240b 100644
--- a/src/adaptix/_internal/morphing/model/loader_provider.py
+++ b/src/adaptix/_internal/morphing/model/loader_provider.py
@@ -1,6 +1,6 @@
 from typing import AbstractSet, Mapping
 
-from adaptix._internal.provider.fields import input_field_to_loc_map
+from adaptix._internal.provider.fields import input_field_to_loc
 
 from ...code_tools.compiler import BasicClosureCompiler, ClosureCompiler
 from ...code_tools.name_sanitizer import BuiltinNameSanitizer, NameSanitizer
@@ -8,7 +8,7 @@
 from ...definitions import DebugTrail
 from ...model_tools.definitions import InputShape
 from ...provider.essential import Mediator
-from ...provider.request_cls import DebugTrailRequest, StrictCoercionRequest, TypeHintLoc
+from ...provider.request_cls import DebugTrailRequest, StrictCoercionRequest
 from ...provider.shape_provider import InputShapeRequest, provide_generic_resolved_shape
 from ..model.loader_gen import BuiltinModelLoaderGen, ModelLoaderProps
 from ..provider_template import LoaderProvider
@@ -75,11 +75,7 @@ def _fetch_model_identity(
         shape: InputShape,
         name_layout: InputNameLayout,
     ) -> str:
-        return (
-            repr(request.last_map[TypeHintLoc].type)
-            if request.last_map.has(TypeHintLoc) else
-            repr(shape.constructor)
-        )
+        return repr(request.last_loc.type)
 
     def _create_model_loader_gen(
         self,
@@ -104,12 +100,10 @@ def _create_model_loader_gen(
         )
 
     def _request_to_view_string(self, request: LoaderRequest) -> str:
-        if request.last_map.has(TypeHintLoc):
-            tp = request.last_map[TypeHintLoc].type
-            if isinstance(tp, type):
-                return tp.__name__
-            return str(tp)
-        return ""
+        tp = request.last_loc.type
+        if isinstance(tp, type):
+            return tp.__name__
+        return str(tp)
 
     def _merge_view_string(self, *fragments: str) -> str:
         return "_".join(filter(None, fragments))
@@ -147,7 +141,7 @@ def _fetch_field_loaders(
     ) -> Mapping[str, Loader]:
         loaders = mediator.mandatory_provide_by_iterable(
             [
-                LoaderRequest(loc_stack=request.loc_stack.append_with(input_field_to_loc_map(field)))
+                LoaderRequest(loc_stack=request.loc_stack.append_with(input_field_to_loc(field)))
                 for field in shape.fields
             ],
             lambda: "Cannot create loader for model. Loaders for some fields cannot be created",
diff --git a/src/adaptix/_internal/morphing/name_layout/component.py b/src/adaptix/_internal/morphing/name_layout/component.py
index 5b05410f..0a89b285 100644
--- a/src/adaptix/_internal/morphing/name_layout/component.py
+++ b/src/adaptix/_internal/morphing/name_layout/component.py
@@ -15,7 +15,7 @@
 )
 from ...name_style import NameStyle, convert_snake_style
 from ...provider.essential import CannotProvide, Mediator, Provider
-from ...provider.fields import field_to_loc_map
+from ...provider.fields import field_to_loc
 from ...provider.loc_stack_filtering import LocStackChecker
 from ...provider.overlay_schema import Overlay, Schema, provide_schema
 from ...provider.request_cls import LocatedRequest
@@ -98,7 +98,7 @@ def apply_lsc(
     loc_stack_checker: LocStackChecker,
     field: BaseField,
 ) -> bool:
-    loc_stack = request.loc_stack.append_with(field_to_loc_map(field))
+    loc_stack = request.loc_stack.append_with(field_to_loc(field))
     return loc_stack_checker.check_loc_stack(mediator, loc_stack)
 
 
@@ -138,7 +138,7 @@ def _map_fields(
                         shape=request.shape,
                         field=field,
                         generated_key=generated_key,
-                        loc_stack=request.loc_stack.append_with(field_to_loc_map(field)),
+                        loc_stack=request.loc_stack.append_with(field_to_loc(field)),
                     ),
                 )
             except CannotProvide:
@@ -179,12 +179,10 @@ def _validate_structure(
         prefix_groups = get_prefix_groups([path for field, path in fields_to_paths if path is not None])
         if prefix_groups:
             details = ". ".join(
-                # pylint: disable=consider-using-f-string
                 "Path {prefix} (field {prefix_field!r}) is prefix of {paths}".format(
                     prefix=list(prefix),
                     prefix_field=paths_to_fields[prefix][0].id,
                     paths=", ".join(
-                        # pylint: disable=consider-using-f-string
                         "{path} (field {path_field!r})".format(  # noqa: UP032
                             path=list(path),
                             path_field=paths_to_fields[path][0].id,
diff --git a/src/adaptix/_internal/morphing/provider_template.py b/src/adaptix/_internal/morphing/provider_template.py
index 53b228e5..83d50a77 100644
--- a/src/adaptix/_internal/morphing/provider_template.py
+++ b/src/adaptix/_internal/morphing/provider_template.py
@@ -5,7 +5,6 @@
 from ..provider.essential import CannotProvide, Mediator
 from ..provider.loc_stack_filtering import ExactOriginLSC
 from ..provider.provider_template import ProviderWithAttachableLSC
-from ..provider.request_cls import TypeHintLoc
 from ..provider.static_provider import static_provision_action
 from ..type_tools import normalize_type
 from .request_cls import DumperRequest, LoaderRequest
@@ -49,7 +48,7 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
 
         return mediator.mandatory_provide(
             LoaderRequest(
-                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(type=self._impl)),
+                loc_stack=request.loc_stack.replace_last_type(self._impl),
             ),
             lambda x: f"Cannot create loader for union. Loader for {self._impl} cannot be created",
         )
@@ -60,7 +59,7 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
 
         return mediator.mandatory_provide(
             DumperRequest(
-                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(type=self._impl)),
+                loc_stack=request.loc_stack.replace_last_type(self._impl),
             ),
             lambda x: f"Cannot create dumper for union. Dumper for {self._impl} cannot be created",
         )
diff --git a/src/adaptix/_internal/provider/essential.py b/src/adaptix/_internal/provider/essential.py
index a32368ac..955ca42f 100644
--- a/src/adaptix/_internal/provider/essential.py
+++ b/src/adaptix/_internal/provider/essential.py
@@ -51,7 +51,6 @@ def __init__(
         is_terminal: bool = False,
         is_demonstrative: bool = False,
     ):
-        # pylint: disable=super-init-not-called
         # Parameter `message` is saved by `__new__` of CompatExceptionGroup
         self.is_terminal = is_terminal
         self.is_demonstrative = is_demonstrative
@@ -65,7 +64,7 @@ def __new__(
             is_terminal: bool = False,
             is_demonstrative: bool = False,
         ):
-            return super().__new__(cls, message, exceptions)  # type: ignore[arg-type]
+            return super().__new__(cls, message, exceptions)
 
     def derive(self, excs: Sequence[CannotProvide]) -> "AggregateCannotProvide":  # type: ignore[override]
         return AggregateCannotProvide(
diff --git a/src/adaptix/_internal/provider/fields.py b/src/adaptix/_internal/provider/fields.py
index 23fb343d..30ee21ef 100644
--- a/src/adaptix/_internal/provider/fields.py
+++ b/src/adaptix/_internal/provider/fields.py
@@ -1,55 +1,32 @@
-from ..model_tools.definitions import BaseField, InputField, OutputField
-from .request_cls import FieldLoc, InputFieldLoc, LocMap, OutputFieldLoc, TypeHintLoc
-
+from typing import Union
 
-def input_field_to_loc_map(field: InputField) -> LocMap:
-    return LocMap(
-        TypeHintLoc(
-            type=field.type,
-        ),
-        FieldLoc(
-            field_id=field.id,
-            default=field.default,
-            metadata=field.metadata,
-        ),
-        InputFieldLoc(
-            is_required=field.is_required,
-        ),
-    )
+from ..model_tools.definitions import BaseField, InputField, OutputField
+from .location import InputFieldLoc, OutputFieldLoc
 
 
-def output_field_to_loc_map(field: OutputField) -> LocMap:
-    return LocMap(
-        TypeHintLoc(
-            type=field.type,
-        ),
-        FieldLoc(
-            field_id=field.id,
-            default=field.default,
-            metadata=field.metadata,
-        ),
-        OutputFieldLoc(
-            accessor=field.accessor,
-        ),
+def input_field_to_loc(field: InputField) -> InputFieldLoc:
+    return InputFieldLoc(
+        type=field.type,
+        field_id=field.id,
+        default=field.default,
+        metadata=field.metadata,
+        is_required=field.is_required,
     )
 
 
-def base_field_to_loc_map(field: BaseField) -> LocMap:
-    return LocMap(
-        TypeHintLoc(
-            type=field.type,
-        ),
-        FieldLoc(
-            field_id=field.id,
-            default=field.default,
-            metadata=field.metadata,
-        ),
+def output_field_to_loc(field: OutputField) -> OutputFieldLoc:
+    return OutputFieldLoc(
+        type=field.type,
+        field_id=field.id,
+        default=field.default,
+        metadata=field.metadata,
+        accessor=field.accessor,
     )
 
 
-def field_to_loc_map(field: BaseField) -> LocMap:
+def field_to_loc(field: BaseField) -> Union[InputFieldLoc, OutputFieldLoc]:
     if isinstance(field, InputField):
-        return input_field_to_loc_map(field)
+        return input_field_to_loc(field)
     if isinstance(field, OutputField):
-        return output_field_to_loc_map(field)
-    return base_field_to_loc_map(field)
+        return output_field_to_loc(field)
+    raise TypeError
diff --git a/src/adaptix/_internal/provider/loc_stack_filtering.py b/src/adaptix/_internal/provider/loc_stack_filtering.py
index 9ca4fe73..753e42b4 100644
--- a/src/adaptix/_internal/provider/loc_stack_filtering.py
+++ b/src/adaptix/_internal/provider/loc_stack_filtering.py
@@ -21,7 +21,8 @@
 )
 from ..type_tools.normalize_type import NotSubscribedError
 from .essential import CannotProvide, Request
-from .request_cls import FieldLoc, GenericParamLoc, Location, LocStack, TypeHintLoc
+from .location import FieldLoc, GenericParamLoc, TypeHintLoc
+from .request_cls import LocStack
 
 T = TypeVar("T")
 
@@ -119,7 +120,7 @@ def _reduce(self, elements: Iterable[bool], /) -> bool:
 
 
 class LastLocMapChecker(LocStackChecker, ABC):
-    _expected_location: ClassVar[Type[Location]]
+    _expected_location: ClassVar[type]
 
     def __init_subclass__(cls, **kwargs):
         param_list = list(inspect.signature(cls._check_location).parameters.values())
@@ -127,9 +128,9 @@ def __init_subclass__(cls, **kwargs):
 
     @final
     def check_loc_stack(self, mediator: DirectMediator, loc_stack: LocStack) -> bool:
-        loc_map = loc_stack[-1]
-        if loc_map.has(self._expected_location):
-            return self._check_location(mediator, loc_map[self._expected_location])
+        last_loc = loc_stack.last
+        if last_loc.is_castable(self._expected_location):
+            return self._check_location(mediator, last_loc)
         return False
 
     @abstractmethod
@@ -206,11 +207,19 @@ def check_loc_stack(self, mediator: DirectMediator, loc_stack: LocStack) -> bool
             return False
 
         for i, checker in enumerate(reversed(self.loc_stack_checkers), start=0):
-            if not checker.check_loc_stack(mediator, loc_stack[:len(loc_stack) - i]):
+            if not checker.check_loc_stack(mediator, loc_stack.reversed_slice(i)):
                 return False
         return True
 
 
+@dataclass(frozen=True)
+class LocStackSizeChecker(LocStackChecker):
+    expected_size: int
+
+    def check_loc_stack(self, mediator: DirectMediator, loc_stack: LocStack) -> bool:
+        return len(loc_stack) == self.expected_size
+
+
 class AnyLocStackChecker(LocStackChecker):
     def check_loc_stack(self, mediator: DirectMediator, loc_stack: LocStack) -> bool:
         return True
@@ -286,7 +295,7 @@ def _from_lsc(cls: Type[Pat], lsc: LocStackChecker) -> Pat:
 
     def _extend_stack(self: Pat, elements: Iterable[LocStackChecker]) -> Pat:
         self_copy = copy(self)
-        self_copy._stack = self._stack + tuple(elements)  # pylint: disable=protected-access
+        self_copy._stack = self._stack + tuple(elements)
         return self_copy
 
     def __getattr__(self: Pat, item: str) -> Pat:
diff --git a/src/adaptix/_internal/provider/location.py b/src/adaptix/_internal/provider/location.py
new file mode 100644
index 00000000..2f26e0db
--- /dev/null
+++ b/src/adaptix/_internal/provider/location.py
@@ -0,0 +1,82 @@
+from dataclasses import dataclass, field
+from typing import Any, Callable, Mapping, Type, TypeVar, Union
+
+from ..common import TypeHint
+from ..model_tools.definitions import Accessor, Default
+
+T = TypeVar("T")
+
+
+class _BaseLoc:
+    def cast_or_raise(
+        self,
+        tp: Type[T],
+        exception_factory: Callable[[], Union[BaseException, Type[BaseException]]],
+    ) -> T:
+        if type(self) in _CAST_SOURCES[tp]:
+            return self  # type: ignore[return-value]
+        raise exception_factory()
+
+    def cast(self, tp: Type[T]) -> T:
+        return self.cast_or_raise(tp, lambda: TypeError(f"Can not cast {self} to {tp}"))
+
+    def is_castable(self, tp: Type[T]) -> bool:
+        return type(self) in _CAST_SOURCES[tp]
+
+
+@dataclass(frozen=True)
+class _TypeHintLoc(_BaseLoc):
+    type: TypeHint
+
+
+@dataclass(frozen=True)
+class _FieldLoc(_TypeHintLoc):
+    field_id: str
+    default: Default
+    metadata: Mapping[Any, Any] = field(hash=False)
+
+
+@dataclass(frozen=True)
+class _InputFieldLoc(_FieldLoc):
+    is_required: bool
+
+
+@dataclass(frozen=True)
+class _OutputFieldLoc(_FieldLoc):
+    accessor: Accessor
+
+
+@dataclass(frozen=True)
+class _GenericParamLoc(_TypeHintLoc):
+    generic_pos: int
+
+
+class TypeHintLoc(_TypeHintLoc):
+    pass
+
+
+class FieldLoc(_FieldLoc):
+    pass
+
+
+class InputFieldLoc(_InputFieldLoc):
+    pass
+
+
+class OutputFieldLoc(_OutputFieldLoc):
+    pass
+
+
+class GenericParamLoc(_GenericParamLoc):
+    pass
+
+
+_CAST_SOURCES = {
+    TypeHintLoc: {TypeHintLoc, FieldLoc, InputFieldLoc, OutputFieldLoc, GenericParamLoc},
+    FieldLoc: {FieldLoc, InputFieldLoc, OutputFieldLoc},
+    InputFieldLoc: (InputFieldLoc, ),
+    OutputFieldLoc: (OutputFieldLoc, ),
+    GenericParamLoc: (GenericParamLoc, ),
+}
+
+AnyLoc = Union[TypeHintLoc, FieldLoc, InputFieldLoc, OutputFieldLoc, GenericParamLoc]
diff --git a/src/adaptix/_internal/provider/overlay_schema.py b/src/adaptix/_internal/provider/overlay_schema.py
index e4842197..b718ed62 100644
--- a/src/adaptix/_internal/provider/overlay_schema.py
+++ b/src/adaptix/_internal/provider/overlay_schema.py
@@ -6,7 +6,7 @@
 from ..utils import Omitted
 from .essential import CannotProvide, Mediator
 from .provider_wrapper import Chain
-from .request_cls import LocatedRequest, LocStack, TypeHintLoc
+from .request_cls import LocatedRequest, LocStack
 from .static_provider import StaticProvider, static_provision_action
 
 
@@ -27,7 +27,7 @@ class Overlay(Generic[Sc]):
     _mergers: ClassVar[Optional[Mapping[str, Merger]]]
 
     def __init_subclass__(cls, *args, **kwargs):
-        for base in cls.__orig_bases__:  # pylint: disable=no-member
+        for base in cls.__orig_bases__:
             if strip_alias(base) == Overlay:
                 cls._schema_cls = base.__args__[0]
                 break
@@ -89,13 +89,12 @@ def provide_schema(overlay: Type[Overlay[Sc]], mediator: Mediator, loc_stack: Lo
             overlay_cls=overlay,
         ),
     )
-    loc_map = loc_stack[-1]
-    if loc_map.has(TypeHintLoc) and isinstance(loc_map[TypeHintLoc].type, type):
-        for parent in loc_map[TypeHintLoc].type.mro()[1:]:
+    if isinstance(loc_stack.last.type, type):
+        for parent in loc_stack.last.type.mro()[1:]:
             try:
                 new_overlay = mediator.delegating_provide(
                     OverlayRequest(
-                        loc_stack=loc_stack.add_to_last_map(TypeHintLoc(type=parent)),
+                        loc_stack=loc_stack.replace_last_type(parent),
                         overlay_cls=overlay,
                     ),
                 )
diff --git a/src/adaptix/_internal/provider/provider_template.py b/src/adaptix/_internal/provider/provider_template.py
index 342c767b..243e54d4 100644
--- a/src/adaptix/_internal/provider/provider_template.py
+++ b/src/adaptix/_internal/provider/provider_template.py
@@ -21,7 +21,6 @@ def decorator(cls: Type[ProviderWithAttachableLSC]):
             raise TypeError(f"Only {ProviderWithAttachableLSC} child is allowed")
 
         # noinspection PyProtectedMember
-        # pylint: disable=protected-access
         cls._loc_stack_checker = create_loc_stack_checker(pred)
         return cls
 
diff --git a/src/adaptix/_internal/provider/request_cls.py b/src/adaptix/_internal/provider/request_cls.py
index 31d65b4a..38de7ef5 100644
--- a/src/adaptix/_internal/provider/request_cls.py
+++ b/src/adaptix/_internal/provider/request_cls.py
@@ -1,57 +1,24 @@
-from dataclasses import dataclass, field
-from typing import Any, Mapping, Tuple, TypeVar
+from dataclasses import dataclass, replace
+from typing import Tuple, TypeVar
 
 from ..common import TypeHint
-from ..datastructures import ClassMap, ImmutableStack
+from ..datastructures import ImmutableStack
 from ..definitions import DebugTrail
-from ..model_tools.definitions import Accessor, Default
 from ..type_tools import BaseNormType, normalize_type
 from ..utils import pairs
 from .essential import CannotProvide, Request
+from .location import AnyLoc, FieldLoc, TypeHintLoc
 
-T = TypeVar("T")
-
-
-@dataclass(frozen=True)
-class Location:
-    pass
-
-
-@dataclass(frozen=True)
-class TypeHintLoc(Location):
-    type: TypeHint
-
-
-@dataclass(frozen=True)
-class FieldLoc(Location):
-    field_id: str
-    default: Default
-    metadata: Mapping[Any, Any] = field(hash=False)
-
-
-@dataclass(frozen=True)
-class InputFieldLoc(Location):
-    is_required: bool
-
-
-@dataclass(frozen=True)
-class OutputFieldLoc(Location):
-    accessor: Accessor
-
+LocStackT = TypeVar("LocStackT", bound="LocStack")
+AnyLocT_co = TypeVar("AnyLocT_co", bound=AnyLoc, covariant=True)
 
-@dataclass(frozen=True)
-class GenericParamLoc(Location):
-    generic_pos: int
 
+class LocStack(ImmutableStack[AnyLocT_co]):
+    def replace_last_type(self: LocStackT, tp: TypeHint, /) -> LocStackT:
+        return self.replace_last(replace(self.last, type=tp))
 
-LocMap = ClassMap[Location]
-LR = TypeVar("LR", bound="LocatedRequest")
-LocStackT = TypeVar("LocStackT", bound="LocStack")
 
-
-class LocStack(ImmutableStack[LocMap]):
-    def add_to_last_map(self: LocStackT, *locs: Location) -> LocStackT:
-        return self.replace_last(self[-1].add(*locs))
+T = TypeVar("T")
 
 
 @dataclass(frozen=True)
@@ -59,12 +26,12 @@ class LocatedRequest(Request[T]):
     loc_stack: LocStack
 
     @property
-    def last_map(self) -> LocMap:
-        return self.loc_stack[-1]
+    def last_loc(self) -> AnyLoc:
+        return self.loc_stack.last
 
 
 def get_type_from_request(request: LocatedRequest) -> TypeHint:
-    return request.last_map.get_or_raise(TypeHintLoc, CannotProvide).type
+    return request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide).type
 
 
 def try_normalize_type(tp: TypeHint) -> BaseNormType:
@@ -82,8 +49,8 @@ class DebugTrailRequest(LocatedRequest[DebugTrail]):
     pass
 
 
-def find_owner_with_field(stack: LocStack) -> Tuple[LocMap, LocMap]:
-    for next_loc_map, prev_loc_map in pairs(reversed(stack)):
-        if next_loc_map.has(FieldLoc):
-            return prev_loc_map, next_loc_map
+def find_owner_with_field(stack: LocStack) -> Tuple[TypeHintLoc, FieldLoc]:
+    for next_loc, prev_loc in pairs(reversed(stack)):
+        if next_loc.is_castable(FieldLoc):
+            return prev_loc, next_loc
     raise ValueError
diff --git a/src/adaptix/_internal/provider/shape_provider.py b/src/adaptix/_internal/provider/shape_provider.py
index 5934344f..dff50769 100644
--- a/src/adaptix/_internal/provider/shape_provider.py
+++ b/src/adaptix/_internal/provider/shape_provider.py
@@ -4,15 +4,15 @@
 
 from ..common import TypeHint
 from ..model_tools.definitions import (
-    ClarifiedIntrospectionImpossible,
+    ClarifiedIntrospectionError,
     DescriptorAccessor,
     InputShape,
-    IntrospectionImpossible,
+    IntrospectionError,
     OutputField,
     OutputShape,
-    PackageIsTooOld,
     Shape,
     ShapeIntrospector,
+    TooOldPackageError,
 )
 from ..model_tools.introspection.attrs import get_attrs_shape
 from ..model_tools.introspection.class_init import get_class_init_shape
@@ -49,16 +49,16 @@ def __repr__(self):
     def _get_shape(self, tp) -> Shape:
         try:
             return self._introspector(tp)
-        except PackageIsTooOld as e:
+        except TooOldPackageError as e:
             raise CannotProvide(message=e.requirement.fail_reason, is_demonstrative=True) from e
-        except ClarifiedIntrospectionImpossible as e:
+        except ClarifiedIntrospectionError as e:
             raise CannotProvide(message=e.description, is_demonstrative=True) from e
-        except IntrospectionImpossible as e:
+        except IntrospectionError as e:
             raise CannotProvide from e
 
     @static_provision_action
     def _provide_input_shape(self, mediator: Mediator, request: InputShapeRequest) -> InputShape:
-        loc = request.last_map.get_or_raise(TypeHintLoc, CannotProvide)
+        loc = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide)
         shape = self._get_shape(loc.type)
         if shape.input is None:
             raise CannotProvide
@@ -66,7 +66,7 @@ def _provide_input_shape(self, mediator: Mediator, request: InputShapeRequest) -
 
     @static_provision_action
     def _provide_output_shape(self, mediator: Mediator, request: OutputShapeRequest) -> OutputShape:
-        loc = request.last_map.get_or_raise(TypeHintLoc, CannotProvide)
+        loc = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide)
         shape = self._get_shape(loc.type)
         if shape.output is None:
             raise CannotProvide
@@ -104,7 +104,7 @@ def __init__(
 
     @static_provision_action
     def _provide_output_shape(self, mediator: Mediator[OutputShape], request: OutputShapeRequest) -> OutputShape:
-        tp = request.last_map.get_or_raise(TypeHintLoc, CannotProvide).type
+        tp = request.last_loc.cast_or_raise(TypeHintLoc, CannotProvide).type
         shape = mediator.provide_from_next()
 
         additional_fields = tuple(
@@ -145,7 +145,7 @@ def __init__(self, mediator: Mediator, initial_request: LocatedRequest[ShapeT]):
     def provide(self) -> ShapeT:
         resolver = GenericResolver(self._get_members)
         members_storage = resolver.get_resolved_members(
-            self._initial_request.last_map[TypeHintLoc].type,
+            self._initial_request.last_loc.type,
         )
         if members_storage.meta is None:
             raise CannotProvide
@@ -162,7 +162,7 @@ def _get_members(self, tp) -> MembersStorage[str, Optional[ShapeT]]:
             shape = self._mediator.delegating_provide(
                 replace(
                     self._initial_request,
-                    loc_stack=self._initial_request.loc_stack.add_to_last_map(TypeHintLoc(type=tp)),
+                    loc_stack=self._initial_request.loc_stack.replace_last_type(tp),
                 ),
             )
         except CannotProvide:
@@ -179,8 +179,6 @@ def _get_members(self, tp) -> MembersStorage[str, Optional[ShapeT]]:
 
 
 def provide_generic_resolved_shape(mediator: Mediator, request: LocatedRequest[ShapeT]) -> ShapeT:
-    if not request.last_map.has(TypeHintLoc):
-        return mediator.delegating_provide(request)
     return ShapeGenericResolver(mediator, request).provide()
 
 
@@ -204,7 +202,7 @@ def _provide_input_shape(self, mediator: Mediator, request: InputShapeRequest) -
         shape = mediator.delegating_provide(
             replace(
                 request,
-                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(self._prototype)),
+                loc_stack=request.loc_stack.replace_last_type(self._prototype),
             ),
         )
         return replace(shape, constructor=self._target)
@@ -218,6 +216,6 @@ def _provide_output_shape(self, mediator: Mediator, request: OutputShapeRequest)
         return mediator.delegating_provide(
             replace(
                 request,
-                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(self._prototype)),
+                loc_stack=request.loc_stack.replace_last_type(self._prototype),
             ),
         )
diff --git a/src/adaptix/_internal/provider/static_provider.py b/src/adaptix/_internal/provider/static_provider.py
index dafdc1ec..d1e92fac 100644
--- a/src/adaptix/_internal/provider/static_provider.py
+++ b/src/adaptix/_internal/provider/static_provider.py
@@ -112,7 +112,7 @@ def __init_subclass__(cls, **kwargs):
         own_spa = _collect_class_own_rc_dict(cls)
 
         parent_rd_dicts = [
-            parent._sp_cls_request_dispatcher.to_dict()  # pylint: disable=no-member
+            parent._sp_cls_request_dispatcher.to_dict()
             for parent in cls.__bases__
             if issubclass(parent, StaticProvider)
         ]
diff --git a/src/adaptix/_internal/retort/base_retort.py b/src/adaptix/_internal/retort/base_retort.py
index 43810348..511765e2 100644
--- a/src/adaptix/_internal/retort/base_retort.py
+++ b/src/adaptix/_internal/retort/base_retort.py
@@ -37,7 +37,7 @@ def __init_subclass__(cls, **kwargs):
         # noinspection PyProtectedMember
         recipe_sum = sum(
             (
-                parent._own_class_recipe  # pylint: disable=E1101
+                parent._own_class_recipe
                 for parent in cls.mro()
                 if isinstance(parent, RetortMeta)
             ),
diff --git a/src/adaptix/_internal/retort/operating_retort.py b/src/adaptix/_internal/retort/operating_retort.py
index e0c70955..7aacc71b 100644
--- a/src/adaptix/_internal/retort/operating_retort.py
+++ b/src/adaptix/_internal/retort/operating_retort.py
@@ -1,11 +1,12 @@
 from abc import ABC
-from typing import Any, Dict, Iterable, List, Mapping, Optional, Type
+from typing import Any, Dict, Iterable, List, Mapping, Optional, Type, Union
 
 from ..common import TypeHint, VarTuple
-from ..conversion.request_cls import CoercerRequest, LinkingRequest
+from ..conversion.request_cls import CoercerRequest, ConversionDestItem, ConversionSourceItem, LinkingRequest
 from ..morphing.request_cls import DumperRequest, LoaderRequest
 from ..provider.essential import AggregateCannotProvide, CannotProvide, Mediator, Provider, Request
-from ..provider.request_cls import FieldLoc, LocatedRequest, LocMap, LocStack, TypeHintLoc, find_owner_with_field
+from ..provider.location import AnyLoc, FieldLoc
+from ..provider.request_cls import LocatedRequest, LocStack
 from ..type_tools import is_parametrized
 from ..utils import copy_exception_dunders, with_module
 from .base_retort import BaseRetort
@@ -26,22 +27,22 @@ class MorphingRecursionResolver(RecursionResolver):
     REQUEST_CLASSES: VarTuple[Type[LocatedRequest]] = (LoaderRequest, DumperRequest)
 
     def __init__(self) -> None:
-        self._loc_map_to_stub: Dict[LocMap, FuncWrapper] = {}
+        self._loc_to_stub: Dict[AnyLoc, FuncWrapper] = {}
 
     def track_recursion(self, request: Request[T]) -> Optional[Any]:
         if not isinstance(request, self.REQUEST_CLASSES):
             return None
 
-        if request.loc_stack.count(request.last_map) == 1:
+        if request.loc_stack.count(request.last_loc) == 1:
             return None
 
         stub = FuncWrapper()
-        self._loc_map_to_stub[request.last_map] = stub
+        self._loc_to_stub[request.last_loc] = stub
         return stub
 
     def process_request_result(self, request: Request[T], result: T) -> None:
-        if isinstance(request, self.REQUEST_CLASSES) and request.last_map in self._loc_map_to_stub:
-            self._loc_map_to_stub.pop(request.last_map).set_func(result)
+        if isinstance(request, self.REQUEST_CLASSES) and request.last_loc in self._loc_to_stub:
+            self._loc_to_stub.pop(request.last_loc).set_func(result)
 
 
 @with_module("adaptix")
@@ -49,48 +50,23 @@ class NoSuitableProvider(Exception):
     def __init__(self, message: str):
         self.message = message
 
+    def __str__(self):
+        return self.message
+
 
 class BuiltinErrorRepresentor(ErrorRepresentor):
     _NO_PROVIDER_DESCRIPTION_METHOD: Mapping[Type[Request], str] = {
         LinkingRequest: "_get_linking_request_description",
-        CoercerRequest: "_get_coercer_request_description",
     }
     _NO_PROVIDER_DESCRIPTION_CONST: Mapping[Type[Request], str] = {
-        LoaderRequest: "There is no provider that can create specified loader",
-        DumperRequest: "There is no provider that can create specified dumper",
+        LoaderRequest: "Cannot find loader",
+        DumperRequest: "Cannot find dumper",
+        CoercerRequest: "Cannot find coercer",
     }
 
     def _get_linking_request_description(self, request: LinkingRequest) -> str:
-        try:
-            dst_owner_loc_map, dst_field_loc_map = find_owner_with_field(request.destination.to_loc_stack())
-        except ValueError:
-            return "Cannot find coercer"
-
-        dst_owner = self._get_type_desc(dst_owner_loc_map[TypeHintLoc].type)
-        dst_field = dst_field_loc_map[FieldLoc].field_id
-        return f"Cannot find paired field of `{dst_owner}.{dst_field}` for linking"
-
-    def _get_coercer_request_description(self, request: CoercerRequest) -> str:
-        try:
-            src_owner_loc_map, src_field_loc_map = find_owner_with_field(request.src.to_loc_stack())
-        except ValueError:
-            return "Cannot find coercer"
-
-        try:
-            dst_owner_loc_map, dst_field_loc_map = find_owner_with_field(request.dst.to_loc_stack())
-        except ValueError:
-            return "Cannot find coercer"
-
-        src_owner = self._get_type_desc(src_owner_loc_map[TypeHintLoc].type)
-        src_field = src_field_loc_map[FieldLoc].field_id
-        src_tp = self._get_type_desc(src_field_loc_map[TypeHintLoc].type)
-        dst_owner = self._get_type_desc(dst_owner_loc_map[TypeHintLoc].type)
-        dst_field = dst_field_loc_map[FieldLoc].field_id
-        dst_tp = self._get_type_desc(dst_field_loc_map[TypeHintLoc].type)
-        return (
-            f"Cannot find coercer for linking"
-            f" `{src_owner}.{src_field}: {src_tp} -> {dst_owner}.{dst_field}: {dst_tp}`"
-        )
+        dst_desc = self._get_loc_stack_desc(request.destination)
+        return f"Cannot find paired field of `{dst_desc}` for linking"
 
     def _get_type_desc(self, tp: TypeHint) -> str:
         if isinstance(tp, type) and not is_parametrized(tp):
@@ -108,39 +84,38 @@ def get_no_provider_description(self, request: Request) -> str:
             return self._NO_PROVIDER_DESCRIPTION_CONST[request_cls]
         return f"There is no provider that can process {request}"
 
-    _LOC_KEYS_ORDER = {fld: idx for idx, fld in enumerate(["type", "field_id"])}
+    def _get_loc_stack_desc(
+        self,
+        loc_stack: LocStack[Union[ConversionSourceItem, ConversionDestItem]],
+    ) -> str:
+        tp_desc = self._get_type_desc(loc_stack.last.type)
+
+        try:
+            owner_loc = loc_stack[-2]
+        except (TypeError, IndexError):
+            return tp_desc
 
-    def _get_loc_stack_context_notes(self, loc_desc: str, field_desc: str, loc_stack: LocStack) -> Iterable[str]:
         try:
-            owner_loc_map, field_loc_map = find_owner_with_field(loc_stack)
-        except ValueError:
-            pass
-        else:
-            owner_type = owner_loc_map[TypeHintLoc].type
-            field_id = field_loc_map[FieldLoc].field_id
-            yield f"Exception was raised while processing {field_desc} {field_id!r} of {owner_type}"
-
-        location_desc = ", ".join(
-            f"{key}={value!r}"
-            for key, value in sorted(
-                (
-                    (key, value)
-                    for loc in loc_stack.last.values()
-                    for key, value in vars(loc).items()
-                ),
-                key=lambda item: self._LOC_KEYS_ORDER.get(item[0], 1000),
-            )
-        )
-        if location_desc:
-            yield f"{loc_desc}: {location_desc}"
+            field_loc = loc_stack.last.cast(FieldLoc)
+        except TypeError:
+            return tp_desc
+        src_owner = self._get_type_desc(owner_loc.type)
+        return f"{src_owner}.{field_loc.field_id}: {tp_desc}"
+
+    def _get_located_request_context_notes(self, request: LocatedRequest) -> Iterable[str]:
+        loc_stack_desc = self._get_loc_stack_desc(request.loc_stack)
+        yield f"Location: `{loc_stack_desc}`"
+
+    def _get_coercer_request_context_notes(self, request: CoercerRequest) -> Iterable[str]:
+        src_desc = self._get_loc_stack_desc(request.src)
+        dst_desc = self._get_loc_stack_desc(request.dst)
+        yield f"Linking: `{src_desc} -> {dst_desc}`"
 
     def get_request_context_notes(self, request: Request) -> Iterable[str]:
         if isinstance(request, LocatedRequest):
-            yield from self._get_loc_stack_context_notes(
-                loc_desc="Location",
-                field_desc="field",
-                loc_stack=request.loc_stack,
-            )
+            yield from self._get_located_request_context_notes(request)
+        elif isinstance(request, CoercerRequest):
+            yield from self._get_coercer_request_context_notes(request)
 
 
 class OperatingRetort(BaseRetort, Provider, ABC):
diff --git a/src/adaptix/_internal/retort/routing.py b/src/adaptix/_internal/retort/routing.py
index cc49983c..cbeebd32 100644
--- a/src/adaptix/_internal/retort/routing.py
+++ b/src/adaptix/_internal/retort/routing.py
@@ -109,7 +109,7 @@ def apply_provider(self, mediator: Mediator[T], request: Request[T]) -> T:
         if not isinstance(request, LocatedRequest):
             raise CannotProvide(f"Request must be instance of {LocatedRequest}")
 
-        loc = request.last_map.get_or_raise(
+        loc = request.last_loc.cast_or_raise(
             TypeHintLoc,
             lambda: CannotProvide(f"Request location must be instance of {TypeHintLoc}"),
         )
diff --git a/src/adaptix/_internal/special_cases_optimization.py b/src/adaptix/_internal/special_cases_optimization.py
index d4f7558e..aa0eba68 100644
--- a/src/adaptix/_internal/special_cases_optimization.py
+++ b/src/adaptix/_internal/special_cases_optimization.py
@@ -3,8 +3,8 @@
 from .model_tools.definitions import DefaultFactory, DefaultFactoryWithSelf, DefaultValue
 from .morphing.model.crown_definitions import Sieve
 
-as_is_stub = lambda x: x  # noqa: E731  # pylint: disable=unnecessary-lambda-assignment
-
+as_is_stub = lambda x: x  # noqa: E731
+as_is_stub_with_ctx = lambda x, ctx: x  # noqa: E731
 
 S = TypeVar("S", bound=Sieve)
 
diff --git a/src/adaptix/_internal/struct_trail.py b/src/adaptix/_internal/struct_trail.py
index e7be1cf6..f2983723 100644
--- a/src/adaptix/_internal/struct_trail.py
+++ b/src/adaptix/_internal/struct_trail.py
@@ -40,7 +40,6 @@ def append_trail(obj: T, trail_element: TrailElement) -> T:
     if an object does not allow adding 3rd-party attributes, do nothing.
     Element inserting to start of the path (it is built in reverse order)
     """
-    # pylint: disable=protected-access
     try:
         # noinspection PyProtectedMember
         trail = obj._adaptix_struct_trail  # type: ignore[attr-defined]
@@ -56,7 +55,6 @@ def extend_trail(obj: T, sub_trail: Reversible[TrailElement]) -> T:
     if an object does not allow adding 3rd-party attributes, do nothing.
     Sub path inserting to start (it is built in reverse order)
     """
-    # pylint: disable=protected-access
     try:
         # noinspection PyProtectedMember
         trail = obj._adaptix_struct_trail  # type: ignore[attr-defined]
@@ -71,7 +69,7 @@ def get_trail(obj: object) -> Trail:
     """Retrieve trail from an object. Trail stores in special private attribute that never be accessed directly"""
     try:
         # noinspection PyProtectedMember
-        return obj._adaptix_struct_trail  # type: ignore[attr-defined]  # pylint: disable=protected-access
+        return obj._adaptix_struct_trail  # type: ignore[attr-defined]
     except AttributeError:
         return deque()
 
diff --git a/src/adaptix/_internal/type_tools/basic_utils.py b/src/adaptix/_internal/type_tools/basic_utils.py
index ffe05dee..5e853d9d 100644
--- a/src/adaptix/_internal/type_tools/basic_utils.py
+++ b/src/adaptix/_internal/type_tools/basic_utils.py
@@ -86,12 +86,15 @@ def is_parametrized(tp: TypeHint) -> bool:
 
 
 def get_type_vars(tp: TypeHint) -> VarTuple[TypeVar]:
-    return getattr(tp, "__parameters__", ())
+    type_vars = getattr(tp, "__parameters__", ())
+    # UnionType object contains descriptor inside `__parameters__`
+    if not isinstance(type_vars, tuple):
+        return ()
+    return type_vars
 
 
 if HAS_PY_312:
     def is_user_defined_generic(tp: TypeHint) -> bool:
-        # pylint: disable=no-member
         return (
             bool(get_type_vars(tp))
             and (
@@ -172,9 +175,7 @@ def get_type_vars_of_parametrized(tp: TypeHint) -> VarTuple[TypeVar]:
 
 if HAS_PY_39:
     def eval_forward_ref(namespace: Dict[str, Any], forward_ref: ForwardRef):
-        # pylint: disable=protected-access
         return forward_ref._evaluate(namespace, None, frozenset())
 else:
     def eval_forward_ref(namespace: Dict[str, Any], forward_ref: ForwardRef):
-        # pylint: disable=protected-access
         return forward_ref._evaluate(namespace, None)  # type: ignore[call-arg]
diff --git a/src/adaptix/_internal/type_tools/constants.py b/src/adaptix/_internal/type_tools/constants.py
index 7b036030..e2963997 100644
--- a/src/adaptix/_internal/type_tools/constants.py
+++ b/src/adaptix/_internal/type_tools/constants.py
@@ -1,3 +1,4 @@
+# mypy: disable-error-code="dict-item"
 import collections
 import concurrent.futures
 import queue
@@ -14,22 +15,22 @@
 _AnyStr_co = TypeVar("_AnyStr_co", str, bytes, covariant=True)
 
 BUILTIN_ORIGIN_TO_TYPEVARS: Mapping[type, VarTuple[TypeVar]] = {
-    re.Pattern: (_AnyStrT, ),  # type: ignore[dict-item]
-    re.Match: (_AnyStrT, ),  # type: ignore[dict-item]
-    PathLike: (_AnyStr_co, ),  # type: ignore[dict-item]
-    type: (_T1,),  # type: ignore[dict-item]
-    list: (_T1,),  # type: ignore[dict-item]
-    set: (_T1,),  # type: ignore[dict-item]
-    frozenset: (_T1_co, ),  # type: ignore[dict-item]
-    collections.Counter: (_T1,),  # type: ignore[dict-item]
-    collections.deque: (_T1,),  # type: ignore[dict-item]
-    dict: (_T1, _T2),  # type: ignore[dict-item]
-    collections.defaultdict: (_T1, _T2),  # type: ignore[dict-item]
-    collections.OrderedDict: (_T1, _T2),  # type: ignore[dict-item]
-    collections.ChainMap: (_T1, _T2),  # type: ignore[dict-item]
-    queue.Queue: (_T1, ),  # type: ignore[dict-item]
-    queue.PriorityQueue: (_T1, ),  # type: ignore[dict-item]
-    queue.LifoQueue: (_T1, ),  # type: ignore[dict-item]
-    queue.SimpleQueue: (_T1, ),  # type: ignore[dict-item]
-    concurrent.futures.Future: (_T1, ),  # type: ignore[dict-item]
+    re.Pattern: (_AnyStrT, ),
+    re.Match: (_AnyStrT, ),
+    PathLike: (_AnyStr_co, ),
+    type: (_T1,),
+    list: (_T1,),
+    set: (_T1,),
+    frozenset: (_T1_co, ),
+    collections.Counter: (_T1,),
+    collections.deque: (_T1,),
+    dict: (_T1, _T2),
+    collections.defaultdict: (_T1, _T2),
+    collections.OrderedDict: (_T1, _T2),
+    collections.ChainMap: (_T1, _T2),
+    queue.Queue: (_T1, ),
+    queue.PriorityQueue: (_T1, ),
+    queue.LifoQueue: (_T1, ),
+    queue.SimpleQueue: (_T1, ),
+    concurrent.futures.Future: (_T1, ),
 }
diff --git a/src/adaptix/_internal/type_tools/normalize_type.py b/src/adaptix/_internal/type_tools/normalize_type.py
index d65a3bfe..b0fa0b23 100644
--- a/src/adaptix/_internal/type_tools/normalize_type.py
+++ b/src/adaptix/_internal/type_tools/normalize_type.py
@@ -1,4 +1,3 @@
-# pylint: disable=inconsistent-return-statements,comparison-with-callable
 # ruff: noqa: RET503
 import dataclasses
 import sys
@@ -220,7 +219,7 @@ class Constraints:
     value: VarTuple[BaseNormType]
 
 
-TypeVarLimit = Union[Bound, Constraints]  # pylint: disable=invalid-name
+TypeVarLimit = Union[Bound, Constraints]
 
 
 class NormTV(BaseNormType):
@@ -507,7 +506,6 @@ def __init__(self, implicit_params_getter: ImplicitParamsGetter):
         self._namespace: Optional[Dict[str, Any]] = None
 
     def _with_namespace(self: TN, namespace: Dict[str, Any]) -> TN:
-        # pylint: disable=protected-access
         self_copy = copy(self)
         self_copy._namespace = namespace
         return self_copy
@@ -603,7 +601,6 @@ def _get_bound(self, type_var) -> Bound:
 
     @_aspect_storage.add
     def _norm_type_var(self, tp, origin, args):
-        # pylint: disable=protected-access
         if isinstance(origin, TypeVar):
             namespaced = self._with_module_namespace(origin.__module__)
             limit = (
@@ -636,13 +633,13 @@ def _norm_param_spec(self, tp, origin, args):
             namespaced = self._with_module_namespace(origin.__module__)
             return NormTV(
                 var=origin,
-                limit=namespaced._get_bound(origin),  # pylint: disable=protected-access
+                limit=namespaced._get_bound(origin),
                 source=tp,
             )
 
     @_aspect_storage.add(condition=HAS_TV_SYNTAX)
     def _norm_type_alias_type(self, tp, origin, args):
-        if isinstance(origin, typing.TypeAliasType):  # pylint: disable=no-member
+        if isinstance(origin, typing.TypeAliasType):
             return NormTypeAlias(
                 origin,
                 self._norm_iter(args),
diff --git a/src/adaptix/_internal/utils.py b/src/adaptix/_internal/utils.py
index b80cf4f6..2d246b45 100644
--- a/src/adaptix/_internal/utils.py
+++ b/src/adaptix/_internal/utils.py
@@ -20,7 +20,7 @@
     overload,
 )
 
-from .feature_requirement import HAS_NATIVE_EXC_GROUP, HAS_PY_310
+from .feature_requirement import HAS_NATIVE_EXC_GROUP, HAS_PY_310, HAS_PY_311
 
 C = TypeVar("C", bound="Cloneable")
 
@@ -37,7 +37,7 @@ def _clone(self: C) -> Generator[C, Any, Any]:
         try:
             yield self_copy
         finally:
-            self_copy._calculate_derived()  # pylint: disable=W0212
+            self_copy._calculate_derived()
 
 
 class ForbiddingDescriptor:
@@ -74,7 +74,7 @@ def _singleton_deepcopy(self, memo):
 
 
 def _singleton_new(cls):
-    return cls._instance  # pylint: disable=protected-access
+    return cls._instance
 
 
 class SingletonMeta(type):
@@ -100,7 +100,7 @@ def __call__(cls):
 T = TypeVar("T")
 
 if HAS_PY_310:
-    pairs = itertools.pairwise  # pylint: disable=invalid-name
+    pairs = itertools.pairwise
 else:
     def pairs(iterable: Iterable[T]) -> Iterable[Tuple[T, T]]:  # type: ignore[no-redef]
         it = iter(iterable)
@@ -208,6 +208,16 @@ def decorator(cls):
     return decorator
 
 
+def fix_dataclass_from_builtin(cls: ClassT) -> ClassT:
+    """Dataclass decorator fails on python 3.8-3.10
+    when you run python with -OO flags
+    trying to get signature from Exception class.
+    """
+    if not HAS_PY_311 and cls.__doc__ is None:
+        cls.__doc__ = "This is stub documentation generated by fix_dataclass_from_builtin function"
+    return cls
+
+
 def create_deprecated_alias_getter(module_name, old_name_to_new_name):
     def deprecated_alias_getter(name):
         if name not in old_name_to_new_name:
diff --git a/src/adaptix/conversion/__init__.py b/src/adaptix/conversion/__init__.py
index 0c47c7a1..3efa3334 100644
--- a/src/adaptix/conversion/__init__.py
+++ b/src/adaptix/conversion/__init__.py
@@ -3,6 +3,7 @@
     allow_unlinked_optional,
     coercer,
     forbid_unlinked_optional,
+    from_param,
     link,
     link_constant,
 )
@@ -17,6 +18,7 @@
     "coercer",
     "allow_unlinked_optional",
     "forbid_unlinked_optional",
+    "from_param",
     "AdornedConversionRetort",
     "FilledConversionRetort",
     "ConversionRetort",
diff --git a/tox.ini b/tox.ini
index f2e9ab50..68c012f1 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,5 @@
 [vars]
 # Do not forget to update paths at .pre-commit-config.yaml
-lint_pylint = src/ scripts/ examples/ benchmarks/benchmarks/pybench/                docs/custom_ext/
-lint_bandit = src/ scripts/ examples/ benchmarks/benchmarks/pybench/ docs/examples/ docs/custom_ext/
 lint_mypy   = src/ scripts/ examples/ benchmarks/benchmarks/pybench/ docs/examples/ docs/custom_ext/
 lint_all    = src/ scripts/ examples/ benchmarks/benchmarks/pybench/ docs/examples/ docs/custom_ext/ tests/
 
@@ -47,7 +45,5 @@ commands =
   mypy {[vars]lint_mypy}
   pre-commit run --all-files
   python scripts/astpath_lint.py {[vars]lint_all}
-  bandit -r -c pyproject.toml --quiet {[vars]lint_bandit}
-  pylint {[vars]lint_pylint}
   # vulture {[vars]lint_all}   # currently it creates too many false-positive alerts
   # TODO: add more linters


Test patch:
diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml
index ab9900cd..75125051 100644
--- a/.github/workflows/lint_and_test.yml
+++ b/.github/workflows/lint_and_test.yml
@@ -31,7 +31,7 @@ jobs:
           python-version: "3.11"
 
       - name: Install Just
-        uses: extractions/setup-just@v1
+        uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6
         with:
           just-version: 1.14.0
 
@@ -72,7 +72,7 @@ jobs:
           python-version: ${{ matrix.python_version.setup }}
 
       - name: Install Just
-        uses: extractions/setup-just@v1
+        uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6
         with:
           just-version: 1.14.0
 
@@ -107,7 +107,7 @@ jobs:
     needs: testing
     permissions:
       pull-requests: write
-      contents: write
+
     if: ${{ github.event_name == 'pull_request' }}
 
     steps:
@@ -121,8 +121,15 @@ jobs:
 
       - name: Coverage comment
         id: coverage_comment
-        uses: py-cov-action/python-coverage-comment-action@v3
+        uses: py-cov-action/python-coverage-comment-action@b16205b76b824c17afe95a014fb22e58b4f239cb
         with:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           MERGE_COVERAGE_FILES: true
           MINIMUM_GREEN: 90
+
+      - name: Store Pull Request comment to be posted (for external pr)
+        uses: actions/upload-artifact@v4
+        if: steps.coverage_comment.outputs.COMMENT_FILE_WRITTEN == 'true'
+        with:
+          name: python-coverage-comment-action
+          path: python-coverage-comment-action.txt
diff --git a/.github/workflows/test_all_oses.yml b/.github/workflows/test_all_oses.yml
index 844b1193..38f27145 100644
--- a/.github/workflows/test_all_oses.yml
+++ b/.github/workflows/test_all_oses.yml
@@ -12,7 +12,7 @@ concurrency:
 
 jobs:
   testing:
-    name: Run tests
+    name: Testing (${{ matrix.python_version.tox }}, ${{ matrix.os }})
     runs-on: ${{ matrix.os }}
 
     strategy:
@@ -39,7 +39,7 @@ jobs:
           python-version: ${{ matrix.python_version.setup }}
 
       - name: Install Just
-        uses: extractions/setup-just@v1
+        uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6
         with:
           just-version: 1.14.0
 
@@ -49,4 +49,6 @@ jobs:
 
       - name: Run tests
         run:
-          just test-on ${{ matrix.python_version.tox }}
+          just inv test-on-ci
+            --py-target ${{ matrix.python_version.tox }}
+            --cov-output .coverage.${{ matrix.python_version.tox }}
diff --git a/requirements/raw/test_extra_new.txt b/requirements/raw/test_extra_new.txt
index 270dca8c..9d59fbfa 100644
--- a/requirements/raw/test_extra_new.txt
+++ b/requirements/raw/test_extra_new.txt
@@ -1,3 +1,3 @@
 -r test_extra_none.txt
 attrs==23.2.0
-sqlalchemy==2.0.27
+sqlalchemy==2.0.29
diff --git a/requirements/test_extra_new.txt b/requirements/test_extra_new.txt
index 96badd3d..84ae08ac 100644
--- a/requirements/test_extra_new.txt
+++ b/requirements/test_extra_new.txt
@@ -28,7 +28,7 @@ pytest==7.4.2
     # via -r requirements/raw/test_extra_none.txt
 pytz==2023.3.post1
     # via dirty-equals
-sqlalchemy==2.0.27
+sqlalchemy==2.0.29
     # via
     #   -r requirements/raw/test_extra_new.txt
     #   -r requirements/raw/test_extra_none.txt
diff --git a/tests/integration/conversion/conftest.py b/tests/integration/conversion/conftest.py
index 42253467..88505da5 100644
--- a/tests/integration/conversion/conftest.py
+++ b/tests/integration/conversion/conftest.py
@@ -1,12 +1,3 @@
-import pytest
 from tests_helpers import pretty_typehint_test_id
 
-from .local_helpers import FactoryWay
-
-
-@pytest.fixture(params=FactoryWay.params())
-def factory_way(request):
-    return request.param
-
-
 pytest_make_parametrize_id = pretty_typehint_test_id
diff --git a/tests/integration/conversion/local_helpers.py b/tests/integration/conversion/local_helpers.py
deleted file mode 100644
index e348eb92..00000000
--- a/tests/integration/conversion/local_helpers.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from enum import Enum
-
-import pytest
-
-
-class FactoryWay(Enum):
-    IMPL_CONVERTER = "impl_converter"
-    GET_CONVERTER = "get_converter"
-
-    @classmethod
-    def params(cls):
-        return [pytest.param(way, id=way.value) for way in cls]
diff --git a/tests/integration/conversion/test_basics.py b/tests/integration/conversion/test_basics.py
index 511b985b..1f455104 100644
--- a/tests/integration/conversion/test_basics.py
+++ b/tests/integration/conversion/test_basics.py
@@ -3,9 +3,7 @@
 from tests_helpers import ModelSpec, exclude_model_spec, only_generic_models, requires
 
 from adaptix._internal.feature_requirement import HAS_ANNOTATED
-from adaptix.conversion import get_converter, impl_converter
-
-from .local_helpers import FactoryWay
+from adaptix.conversion import impl_converter
 
 
 @exclude_model_spec(ModelSpec.TYPED_DICT)
@@ -25,25 +23,22 @@ class DestModel(*model_spec.bases):
     assert convert(SourceModel(field1=1, field2=2), DestModel) == DestModel(field1=1, field2=2)
 
 
-def test_copy(model_spec, factory_way):
+def test_copy(model_spec):
     @model_spec.decorator
     class ExampleAny(*model_spec.bases):
         field1: Any
         field2: Any
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def copy(a: ExampleAny) -> ExampleAny:
-            ...
-    else:
-        copy = get_converter(ExampleAny, ExampleAny)
+    @impl_converter
+    def copy(a: ExampleAny) -> ExampleAny:
+        ...
 
     obj1 = ExampleAny(field1=1, field2=2)
     assert copy(obj1) == obj1
     assert copy(obj1) is not obj1
 
 
-def test_same_shape(src_model_spec, dst_model_spec, factory_way):
+def test_same_shape(src_model_spec, dst_model_spec):
     @src_model_spec.decorator
     class SourceModel(*src_model_spec.bases):
         field1: Any
@@ -54,12 +49,9 @@ class DestModel(*dst_model_spec.bases):
         field1: Any
         field2: Any
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel)
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=1, field2=2)) == DestModel(field1=1, field2=2)
 
@@ -82,7 +74,7 @@ def convert(a: SourceModel, field2: Any) -> DestModel:
     assert convert(SourceModel(field1=1, field2=2), field2=3) == DestModel(field1=1, field2=3)
 
 
-def test_upcast(src_model_spec, dst_model_spec, factory_way):
+def test_upcast(src_model_spec, dst_model_spec):
     @src_model_spec.decorator
     class SourceModel(*src_model_spec.bases):
         field1: Any
@@ -94,12 +86,9 @@ class DestModel(*dst_model_spec.bases):
         field1: Any
         field2: Any
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel)
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=1, field2=2, field3=3)) == DestModel(field1=1, field2=2)
 
@@ -123,7 +112,7 @@ def convert(a: SourceModel, field3: Any) -> DestModel:
     assert convert(SourceModel(field1=1, field2=2), field3=3) == DestModel(field1=1, field2=2, field3=3)
 
 
-def test_nested(src_model_spec, dst_model_spec, factory_way):
+def test_nested(src_model_spec, dst_model_spec):
     @src_model_spec.decorator
     class SourceModelNested(*src_model_spec.bases):
         field1: Any
@@ -144,12 +133,9 @@ class DestModel(*dst_model_spec.bases):
         field2: Any
         nested: DestModelNested
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel)
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(
         SourceModel(
@@ -164,7 +150,7 @@ def convert(a: SourceModel) -> DestModel:
     )
 
 
-def test_same_nested(src_model_spec, dst_model_spec, factory_way):
+def test_same_nested(src_model_spec, dst_model_spec):
     @src_model_spec.decorator
     class SourceModelNested(*src_model_spec.bases):
         field1: Any
@@ -187,12 +173,9 @@ class DestModel(*dst_model_spec.bases):
         nested1: DestModelNested
         nested2: DestModelNested
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel)
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(
         SourceModel(
@@ -210,7 +193,7 @@ def convert(a: SourceModel) -> DestModel:
 
 
 @requires(HAS_ANNOTATED)
-def test_annotated_ignoring(src_model_spec, dst_model_spec, factory_way):
+def test_annotated_ignoring(src_model_spec, dst_model_spec):
     from typing import Annotated
 
     @src_model_spec.decorator
@@ -223,12 +206,9 @@ class DestModel(*dst_model_spec.bases):
         field1: Any
         field2: Any
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel)
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=1, field2=2)) == DestModel(field1=1, field2=2)
 
@@ -237,7 +217,7 @@ def convert(a: SourceModel) -> DestModel:
 
 
 @only_generic_models
-def test_same_parametrized_generics(src_model_spec, dst_model_spec, factory_way):
+def test_same_parametrized_generics(src_model_spec, dst_model_spec):
     @src_model_spec.decorator
     class SourceModel(*src_model_spec.bases, Generic[T]):
         field1: Any
@@ -248,18 +228,15 @@ class DestModel(*dst_model_spec.bases, Generic[T]):
         field1: Any
         field2: T
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def convert(a: SourceModel[int]) -> DestModel[int]:
-            ...
-    else:
-        convert = get_converter(SourceModel[int], DestModel[int])
+    @impl_converter
+    def convert(a: SourceModel[int]) -> DestModel[int]:
+        ...
 
     assert convert(SourceModel(field1=1, field2=2)) == DestModel(field1=1, field2=2)
 
 
 @only_generic_models
-def test_non_parametrized_generics(src_model_spec, dst_model_spec, factory_way):
+def test_non_parametrized_generics(src_model_spec, dst_model_spec):
     @src_model_spec.decorator
     class SourceModel(*src_model_spec.bases, Generic[T]):
         field1: Any
@@ -270,11 +247,8 @@ class DestModel(*dst_model_spec.bases, Generic[T]):
         field1: Any
         field2: T
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel)
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=1, field2=2)) == DestModel(field1=1, field2=2)
diff --git a/tests/integration/conversion/test_coercer.py b/tests/integration/conversion/test_coercer.py
index 8b36b14e..c06ee968 100644
--- a/tests/integration/conversion/test_coercer.py
+++ b/tests/integration/conversion/test_coercer.py
@@ -1,10 +1,13 @@
-from typing import Any, Optional, Union
+import typing
+from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional, Set, Tuple, Union
 
 import pytest
+from tests_helpers import cond_list
 
-from adaptix.conversion import coercer, get_converter, impl_converter
-
-from .local_helpers import FactoryWay
+from adaptix import P
+from adaptix._internal.conversion.facade.provider import from_param, link
+from adaptix._internal.feature_requirement import HAS_ANNOTATED
+from adaptix.conversion import coercer, impl_converter
 
 
 @pytest.mark.parametrize(
@@ -14,61 +17,51 @@
         pytest.param(lambda x: int(x), id="lambda"),
     ],
 )
-def test_simple(src_model_spec, dst_model_spec, factory_way, func):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_simple(model_spec, func):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: str
         field2: str
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: int
         field2: int
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter(recipe=[coercer(str, int, func=func)])
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel, recipe=[coercer(str, int, func=func)])
+    @impl_converter(recipe=[coercer(str, int, func=func)])
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1="1", field2="2")) == DestModel(field1=1, field2=2)
 
 
-def test_model_priority(src_model_spec, dst_model_spec, factory_way):
-    @src_model_spec.decorator
-    class SourceModelNested(*src_model_spec.bases):
+def test_model_priority(model_spec):
+    @model_spec.decorator
+    class SourceModelNested(*model_spec.bases):
         field1: Any
 
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: Any
         field2: Any
         nested: SourceModelNested
 
-    @dst_model_spec.decorator
-    class DestModelNested(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModelNested(*model_spec.bases):
         field1: Any
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: Any
         field2: Any
         nested: DestModelNested
 
     def my_coercer(a: SourceModelNested) -> DestModelNested:
-        return DestModelNested(field1=src_model_spec.get_field(a, "field1") + 10)
-
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter(recipe=[coercer(SourceModelNested, DestModelNested, func=my_coercer)])
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(
-            SourceModel,
-            DestModel,
-            recipe=[coercer(SourceModelNested, DestModelNested, func=my_coercer)],
-        )
+        return DestModelNested(field1=model_spec.get_field(a, "field1") + 10)
+
+    @impl_converter(recipe=[coercer(SourceModelNested, DestModelNested, func=my_coercer)])
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(
         SourceModel(
@@ -83,44 +76,38 @@ def convert(a: SourceModel) -> DestModel:
     )
 
 
-def test_any_dest(src_model_spec, dst_model_spec, factory_way):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_any_dest(model_spec):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: str
         field2: str
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: Any
         field2: Any
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel)
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1="1", field2="2")) == DestModel(field1="1", field2="2")
 
 
-def test_subclass_builtin(src_model_spec, dst_model_spec, factory_way):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_subclass_builtin(model_spec):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: bool
         field2: bool
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: int
         field2: int
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel)
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=False, field2=True)) == DestModel(field1=False, field2=True)
 
@@ -133,55 +120,323 @@ def convert(a: SourceModel) -> DestModel:
         pytest.param(Union[int, str], Union[int, str, None]),
     ],
 )
-def test_union_subcase(src_model_spec, dst_model_spec, factory_way, src_tp, dst_tp):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_union_subcase(model_spec, src_tp, dst_tp):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: int
         field2: src_tp
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: int
         field2: dst_tp
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel)
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=1, field2=2)) == DestModel(field1=1, field2=2)
 
 
 @pytest.mark.parametrize(
-    ["src_tp", "dst_tp", "value"],
+    ["src_tp", "dst_tp", "src_value", "dst_value"],
+    [
+        pytest.param(Optional[int], Optional[int], 10, 10),
+        pytest.param(Optional[int], Optional[int], None, None),
+        pytest.param(Optional[str], Optional[str], "abc", "abc"),
+        pytest.param(Optional[str], Optional[str], None, None),
+        pytest.param(Optional[bool], Optional[int], True, True),
+        pytest.param(Optional[str], Optional[int], "123", 123),
+        *cond_list(
+            HAS_ANNOTATED,
+            lambda: [
+                pytest.param(Optional[typing.Annotated[int, "meta"]], Optional[int], 123, 123),
+                pytest.param(Optional[int], Optional[typing.Annotated[int, "meta"]], 123, 123),
+                pytest.param(typing.Annotated[Optional[int], "meta"], Optional[int], 123, 123),
+                pytest.param(Optional[int], typing.Annotated[Optional[int], "meta"], 123, 123),
+            ],
+        ),
+    ],
+)
+def test_optional(model_spec, src_tp, dst_tp, src_value, dst_value):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
+        field1: int
+        field2: src_tp
+
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
+        field1: int
+        field2: dst_tp
+
+    @impl_converter(recipe=[coercer(str, int, func=int)])
+    def convert(a: SourceModel) -> DestModel:
+        ...
+
+    assert convert(SourceModel(field1=1, field2=src_value)) == DestModel(field1=1, field2=dst_value)
+
+
+def test_optional_with_model(model_spec):
+    @model_spec.decorator
+    class SourceModelInner(*model_spec.bases):
+        data: int
+
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
+        field1: int
+        field2: Optional[SourceModelInner]
+
+    @model_spec.decorator
+    class DestModelInner(*model_spec.bases):
+        data: int
+
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
+        field1: int
+        field2: Optional[DestModelInner]
+
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
+
+    assert (
+        convert(SourceModel(field1=1, field2=SourceModelInner(data=2)))
+        ==
+        DestModel(field1=1, field2=DestModelInner(data=2))
+    )
+    assert convert(SourceModel(field1=1, field2=None)) == DestModel(field1=1, field2=None)
+
+
+def test_optional_with_model_and_ctx(model_spec):
+    @model_spec.decorator
+    class SourceModelInner(*model_spec.bases):
+        data: int
+
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
+        field1: int
+        field2: Optional[SourceModelInner]
+
+    @model_spec.decorator
+    class DestModelInner(*model_spec.bases):
+        data: int
+        extra1: str
+
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
+        field1: int
+        field2: Optional[DestModelInner]
+
+    @impl_converter(recipe=[link(from_param("extra1"), P[DestModelInner].extra1)])
+    def convert(a: SourceModel, extra1: str) -> DestModel:
+        ...
+
+    assert (
+        convert(SourceModel(field1=1, field2=SourceModelInner(data=2)), "e1")
+        ==
+        DestModel(field1=1, field2=DestModelInner(data=2, extra1="e1"))
+    )
+    assert convert(SourceModel(field1=1, field2=None), "e1") == DestModel(field1=1, field2=None)
+
+
+@pytest.mark.parametrize(
+    ["src_tp", "dst_tp", "src_value", "dst_value"],
+    [
+        pytest.param(List[int], List[int], [1, 2, 3], [1, 2, 3]),
+        pytest.param(List[str], List[int], ["1", "2", "3"], [1, 2, 3]),
+        pytest.param(List[int], Tuple[int, ...], [1, 2, 3], (1, 2, 3)),
+        pytest.param(List[str], Tuple[int, ...], ["1", "2", "3"], (1, 2, 3)),
+        pytest.param(Set[int], Tuple[int, ...], {1, 2, 3}, (1, 2, 3)),
+        pytest.param(List[int], Iterable[int], [1, 2, 3], (1, 2, 3)),
+        pytest.param(Iterable[int], Iterable[int], [1, 2, 3], (1, 2, 3)),
+    ],
+)
+def test_iterable(model_spec, src_tp, dst_tp, src_value, dst_value):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
+        field1: int
+        field2: src_tp
+
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
+        field1: int
+        field2: dst_tp
+
+    @impl_converter(recipe=[coercer(str, int, func=int)])
+    def convert(a: SourceModel) -> DestModel:
+        ...
+
+    assert convert(SourceModel(field1=1, field2=src_value)) == DestModel(field1=1, field2=dst_value)
+
+
+def test_iterable_with_model(model_spec):
+    @model_spec.decorator
+    class SourceModelInner(*model_spec.bases):
+        data: int
+
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
+        field1: int
+        field2: List[SourceModelInner]
+
+    @model_spec.decorator
+    class DestModelInner(*model_spec.bases):
+        data: int
+
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
+        field1: int
+        field2: List[DestModelInner]
+
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
+
+    assert (
+        convert(SourceModel(field1=1, field2=[SourceModelInner(data=1), SourceModelInner(data=2)]))
+        ==
+        DestModel(field1=1, field2=[DestModelInner(data=1), DestModelInner(data=2)])
+    )
+
+
+def test_iterable_with_model_and_ctx(model_spec):
+    @model_spec.decorator
+    class SourceModelInner(*model_spec.bases):
+        data: int
+
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
+        field1: int
+        field2: List[SourceModelInner]
+
+    @model_spec.decorator
+    class DestModelInner(*model_spec.bases):
+        data: int
+        extra1: str
+
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
+        field1: int
+        field2: List[DestModelInner]
+
+    @impl_converter(recipe=[link(from_param("extra1"), P[DestModelInner].extra1)])
+    def convert(a: SourceModel, extra1: str) -> DestModel:
+        ...
+
+    assert (
+        convert(SourceModel(field1=1, field2=[SourceModelInner(data=1), SourceModelInner(data=2)]), "e1")
+        ==
+        DestModel(field1=1, field2=[DestModelInner(data=1, extra1="e1"), DestModelInner(data=2, extra1="e1")])
+    )
+
+
+def test_iterable_on_top_level():
+    @impl_converter(recipe=[coercer(str, int, func=int)])
+    def convert(a: List[str]) -> List[int]:
+        ...
+
+    assert convert(["1", "2", "3"]) == [1, 2, 3]
+
+
+def test_iterable_of_models_on_top_level(model_spec):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
+        v: int
+
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
+        v: int
+
+    @impl_converter(recipe=[coercer(str, int, func=int)])
+    def convert(a: List[SourceModel]) -> List[DestModel]:
+        ...
+
+    assert convert([SourceModel(v=1), SourceModel(v=2)]) == [DestModel(v=1), DestModel(v=2)]
+
+
+@pytest.mark.parametrize(
+    ["src_tp", "dst_tp", "src_value", "dst_value"],
     [
-        pytest.param(Optional[int], Optional[int], 10),
-        pytest.param(Optional[int], Optional[int], None),
-        pytest.param(Optional[str], Optional[str], "abc"),
-        pytest.param(Optional[str], Optional[str], None),
-        pytest.param(Optional[bool], Optional[int], True),
-        pytest.param(Optional[bool], Optional[int], None),
+        pytest.param(Dict[int, int], Dict[int, int], {1: 1, 2: 2, 3: 3}, {1: 1, 2: 2, 3: 3}),
+        pytest.param(Dict[str, str], Dict[int, int], {"1": "1", "2": "2", "3": "3"}, {1: 1, 2: 2, 3: 3}),
+        pytest.param(Mapping[str, str], Dict[int, int], {"1": "1", "2": "2", "3": "3"}, {1: 1, 2: 2, 3: 3}),
+        pytest.param(Mapping[str, str], MutableMapping[int, int], {"1": "1", "2": "2", "3": "3"}, {1: 1, 2: 2, 3: 3}),
     ],
 )
-def test_optional(src_model_spec, dst_model_spec, factory_way, src_tp, dst_tp, value):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_dict(model_spec, src_tp, dst_tp, src_value, dst_value):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: int
         field2: src_tp
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: int
         field2: dst_tp
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel)
+    @impl_converter(recipe=[coercer(str, int, func=int)])
+    def convert(a: SourceModel) -> DestModel:
+        ...
+
+    assert convert(SourceModel(field1=1, field2=src_value)) == DestModel(field1=1, field2=dst_value)
+
+
+def test_dict_with_model(model_spec):
+    @model_spec.decorator
+    class SourceModelInner(*model_spec.bases):
+        data: int
+
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
+        field1: int
+        field2: Dict[str, SourceModelInner]
+
+    @model_spec.decorator
+    class DestModelInner(*model_spec.bases):
+        data: int
+
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
+        field1: int
+        field2: Dict[str, DestModelInner]
+
+    @impl_converter
+    def convert(a: SourceModel) -> DestModel:
+        ...
+
+    assert (
+        convert(SourceModel(field1=1, field2={"a": SourceModelInner(data=1), "b": SourceModelInner(data=2)}))
+        ==
+        DestModel(field1=1, field2={"a": DestModelInner(data=1), "b": DestModelInner(data=2)})
+    )
+
+
+def test_dict_with_model_and_ctx(model_spec):
+    @model_spec.decorator
+    class SourceModelInner(*model_spec.bases):
+        data: int
 
-    assert convert(SourceModel(field1=1, field2=value)) == DestModel(field1=1, field2=value)
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
+        field1: int
+        field2: Dict[str, SourceModelInner]
+
+    @model_spec.decorator
+    class DestModelInner(*model_spec.bases):
+        data: int
+        extra1: str
+
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
+        field1: int
+        field2: Dict[str, DestModelInner]
+
+    @impl_converter(recipe=[link(from_param("extra1"), P[DestModelInner].extra1)])
+    def convert(a: SourceModel, extra1: str) -> DestModel:
+        ...
 
+    assert (
+        convert(SourceModel(field1=1, field2={"a": SourceModelInner(data=1), "b": SourceModelInner(data=2)}), "e1")
+        ==
+        DestModel(field1=1, field2={"a": DestModelInner(data=1, extra1="e1"), "b": DestModelInner(data=2, extra1="e1")})
+    )
diff --git a/tests/integration/conversion/test_linking.py b/tests/integration/conversion/test_linking.py
index aa72c398..e58fd62a 100644
--- a/tests/integration/conversion/test_linking.py
+++ b/tests/integration/conversion/test_linking.py
@@ -1,61 +1,54 @@
 from typing import Any
 
-from adaptix._internal.conversion.facade.provider import link_constant
-from adaptix.conversion import coercer, get_converter, impl_converter, link
+from adaptix import P
+from adaptix._internal.conversion.facade.provider import from_param, link_constant
+from adaptix.conversion import coercer, impl_converter, link
 
-from .local_helpers import FactoryWay
 
-
-def test_field_rename(src_model_spec, dst_model_spec, factory_way):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_field_rename(model_spec):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: Any
         field2_src: Any
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: Any
         field2_dst: Any
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter(recipe=[link("field2_src", "field2_dst")])
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel, recipe=[link("field2_src", "field2_dst")])
+    @impl_converter(recipe=[link("field2_src", "field2_dst")])
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=1, field2_src=2)) == DestModel(field1=1, field2_dst=2)
 
 
-def test_field_swap(src_model_spec, dst_model_spec, factory_way):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_field_swap(model_spec):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: Any
         field2: Any
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: Any
         field2: Any
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter(recipe=[link("field1", "field2"), link("field2", "field1")])
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel, recipe=[link("field1", "field2"), link("field2", "field1")])
+    @impl_converter(recipe=[link("field1", "field2"), link("field2", "field1")])
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=1, field2=2)) == DestModel(field1=2, field2=1)
 
 
-def test_downcast(src_model_spec, dst_model_spec):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_downcast(model_spec):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: Any
         field2: Any
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: Any
         field2: Any
         field3: Any
@@ -67,33 +60,30 @@ def convert(a: SourceModel, field4: Any) -> DestModel:
     assert convert(SourceModel(field1=1, field2=2), field4=3) == DestModel(field1=1, field2=2, field3=3)
 
 
-def test_nested(src_model_spec, dst_model_spec, factory_way):
-    @src_model_spec.decorator
-    class SourceModelNested(*src_model_spec.bases):
+def test_nested(model_spec):
+    @model_spec.decorator
+    class SourceModelNested(*model_spec.bases):
         field1_src: Any
 
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: Any
         field2: Any
         nested: SourceModelNested
 
-    @dst_model_spec.decorator
-    class DestModelNested(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModelNested(*model_spec.bases):
         field1_dst: Any
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: Any
         field2: Any
         nested: DestModelNested
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter(recipe=[link("field1_src", "field1_dst")])
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel, recipe=[link("field1_src", "field1_dst")])
+    @impl_converter(recipe=[link("field1_src", "field1_dst")])
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(
         SourceModel(
@@ -108,33 +98,30 @@ def convert(a: SourceModel) -> DestModel:
     )
 
 
-def test_nested_several(src_model_spec, dst_model_spec, factory_way):
-    @src_model_spec.decorator
-    class SourceModelNested(*src_model_spec.bases):
+def test_nested_several(model_spec):
+    @model_spec.decorator
+    class SourceModelNested(*model_spec.bases):
         field1_src: Any
 
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1_src: Any
         field2: Any
         nested: SourceModelNested
 
-    @dst_model_spec.decorator
-    class DestModelNested(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModelNested(*model_spec.bases):
         field1_dst: Any
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1_dst: Any
         field2: Any
         nested: DestModelNested
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter(recipe=[link("field1_src", "field1_dst")])
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel, recipe=[link("field1_src", "field1_dst")])
+    @impl_converter(recipe=[link("field1_src", "field1_dst")])
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(
         SourceModel(
@@ -149,93 +136,113 @@ def convert(a: SourceModel) -> DestModel:
     )
 
 
-def test_coercer(src_model_spec, dst_model_spec, factory_way):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_coercer(model_spec):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: Any
         field2_src: int
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: Any
         field2_dst: str
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter(recipe=[link("field2_src", "field2_dst", coercer=str)])
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel, recipe=[link("field2_src", "field2_dst", coercer=str)])
+    @impl_converter(recipe=[link("field2_src", "field2_dst", coercer=str)])
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=1, field2_src=2)) == DestModel(field1=1, field2_dst="2")
 
 
-def test_coercer_priority(src_model_spec, dst_model_spec, factory_way):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_coercer_priority(model_spec):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: Any
         field2_src: int
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: Any
         field2_dst: str
 
-    recipe = [
-        coercer(int, str, func=str),
-        link("field2_src", "field2_dst", coercer=lambda x: str(x + 1)),
-    ]
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter(recipe=recipe)
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel, recipe=recipe)
+    @impl_converter(
+        recipe=[
+            coercer(int, str, func=str),
+            link("field2_src", "field2_dst", coercer=lambda x: str(x + 1)),
+        ],
+    )
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=1, field2_src=2)) == DestModel(field1=1, field2_dst="3")
 
 
-def test_link_to_constant_value(src_model_spec, dst_model_spec, factory_way):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_link_to_constant_value(model_spec):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: Any
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: Any
         field2: str
 
-    recipe = [
-        link_constant("field2", value="abc"),
-    ]
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter(recipe=recipe)
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel, recipe=recipe)
+    @impl_converter(recipe=[link_constant("field2", value="abc")])
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=1)) == DestModel(field1=1, field2="abc")
 
 
-def test_link_to_constant_factory(src_model_spec, dst_model_spec, factory_way):
-    @src_model_spec.decorator
-    class SourceModel(*src_model_spec.bases):
+def test_link_to_constant_factory(model_spec):
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
         field1: Any
 
-    @dst_model_spec.decorator
-    class DestModel(*dst_model_spec.bases):
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
         field1: Any
         field2: list
 
-    recipe = [
-        link_constant("field2", factory=list),
-    ]
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter(recipe=recipe)
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel, recipe=recipe)
+    @impl_converter(recipe=[link_constant("field2", factory=list)])
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     assert convert(SourceModel(field1=1)) == DestModel(field1=1, field2=[])
+
+
+def test_from_param(model_spec):
+    @model_spec.decorator
+    class SourceModelNested(*model_spec.bases):
+        a: Any
+        b: Any
+
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
+        nested: SourceModelNested
+
+    @model_spec.decorator
+    class DestModelNested(*model_spec.bases):
+        a: Any
+        b: Any
+
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
+        nested: DestModelNested
+        b: Any
+
+    @impl_converter(
+        recipe=[
+            link(from_param("a"), P[DestModel].b),
+            link(P[SourceModel].a, P[DestModel].a),
+        ],
+    )
+    def convert(m: SourceModel, a: Any) -> DestModel:
+        ...
+
+    assert (
+        convert(SourceModel(nested=SourceModelNested(a=1, b=2)), a=3)
+        ==
+        DestModel(nested=DestModelNested(a=1, b=2), b=3)
+    )
+
diff --git a/tests/integration/conversion/test_policy.py b/tests/integration/conversion/test_policy.py
index d89215e0..75808681 100644
--- a/tests/integration/conversion/test_policy.py
+++ b/tests/integration/conversion/test_policy.py
@@ -1,12 +1,10 @@
 from tests_helpers import ModelSpec, exclude_model_spec
 
-from adaptix.conversion import allow_unlinked_optional, get_converter, impl_converter
-
-from .local_helpers import FactoryWay
+from adaptix.conversion import allow_unlinked_optional, impl_converter
 
 
 @exclude_model_spec(ModelSpec.TYPED_DICT)
-def test_unbound_optional(src_model_spec, dst_model_spec, factory_way):
+def test_unlinked_optional(src_model_spec, dst_model_spec):
     @src_model_spec.decorator
     class SourceModel(*src_model_spec.bases):
         field1: str
@@ -18,12 +16,9 @@ class DestModel(*dst_model_spec.bases):
         field2: str
         wild: str = ""
 
-    if factory_way == FactoryWay.IMPL_CONVERTER:
-        @impl_converter(recipe=[allow_unlinked_optional("wild")])
-        def convert(a: SourceModel) -> DestModel:
-            ...
-    else:
-        convert = get_converter(SourceModel, DestModel, recipe=[allow_unlinked_optional("wild")])
+    @impl_converter(recipe=[allow_unlinked_optional("wild")])
+    def convert(a: SourceModel) -> DestModel:
+        ...
 
     if dst_model_spec.kind == ModelSpec.SQLALCHEMY:
         # sqlalchemy set default after flush
diff --git a/tests/tests_helpers/setup.py b/tests/tests_helpers/setup.py
index 4055cc68..822217d9 100644
--- a/tests/tests_helpers/setup.py
+++ b/tests/tests_helpers/setup.py
@@ -1,7 +1,7 @@
-from setuptools import setup
+from setuptools import find_packages, setup
 
 setup(
     name="tests_helpers",
     version="0.0.0",
-    py_modules=["tests_helpers"],
+    packages=find_packages("."),
 )
diff --git a/tests/tests_helpers/tests_helpers/__init__.py b/tests/tests_helpers/tests_helpers/__init__.py
new file mode 100644
index 00000000..1ffd89a9
--- /dev/null
+++ b/tests/tests_helpers/tests_helpers/__init__.py
@@ -0,0 +1,28 @@
+from .misc import (
+    ATTRS_WITH_ALIAS,
+    ByTrailSelector,
+    DebugCtx,
+    FailedRequirement,
+    PlaceholderProvider,
+    TestRetort,
+    cond_list,
+    create_sa_engine,
+    full_match,
+    load_namespace,
+    load_namespace_keeping_module,
+    parametrize_bool,
+    pretty_typehint_test_id,
+    raises_exc,
+    requires,
+    with_cause,
+    with_notes,
+    with_trail,
+)
+from .model_spec import (
+    ModelSpec,
+    ModelSpecSchema,
+    exclude_model_spec,
+    only_generic_models,
+    parametrize_model_spec,
+    sqlalchemy_equals,
+)
diff --git a/tests/tests_helpers/tests_helpers.py b/tests/tests_helpers/tests_helpers/misc.py
similarity index 61%
rename from tests/tests_helpers/tests_helpers.py
rename to tests/tests_helpers/tests_helpers/misc.py
index 3e8e041c..a0c62ec5 100644
--- a/tests/tests_helpers/tests_helpers.py
+++ b/tests/tests_helpers/tests_helpers/misc.py
@@ -5,45 +5,27 @@
 import sys
 from contextlib import contextmanager
 from dataclasses import dataclass, is_dataclass
-from enum import Enum
-from operator import getitem
 from pathlib import Path
 from types import ModuleType, SimpleNamespace
-from typing import (
-    Any,
-    Callable,
-    Dict,
-    Generator,
-    List,
-    Mapping,
-    NamedTuple,
-    Optional,
-    Reversible,
-    Type,
-    TypedDict,
-    TypeVar,
-    Union,
-)
+from typing import Any, Callable, Dict, Generator, List, Optional, Reversible, Type, TypeVar, Union
 from uuid import uuid4
 
 import pytest
-from _pytest.python import Metafunc
 from sqlalchemy import Engine, create_engine
 
 from adaptix import AdornedRetort, CannotProvide, DebugTrail, Mediator, NoSuitableProvider, Provider, Request
 from adaptix._internal.compat import CompatExceptionGroup
-from adaptix._internal.feature_requirement import HAS_ATTRS_PKG, HAS_PY_311, DistributionVersionRequirement, Requirement
+from adaptix._internal.feature_requirement import DistributionVersionRequirement, Requirement
 from adaptix._internal.morphing.model.basic_gen import CodeGenAccumulator
 from adaptix._internal.struct_trail import TrailElement, extend_trail, render_trail_as_note
-from adaptix._internal.type_tools import get_all_type_hints, is_parametrized
+from adaptix._internal.type_tools import is_parametrized
 from adaptix._internal.utils import add_note
 from adaptix.struct_trail import get_trail
 
-T = TypeVar("T")
-
-
 ATTRS_WITH_ALIAS = DistributionVersionRequirement("attrs", "22.2.0")
 
+T = TypeVar("T")
+
 
 def requires(requirement: Requirement):
     def wrapper(func):
@@ -259,122 +241,3 @@ def _evaluate(self) -> bool:
     @property
     def fail_reason(self) -> str:
         return self._fail_reason
-
-
-def sqlalchemy_equals(self, other):
-    if type(self) is not type(other):
-        return False
-
-    self_state = vars(self).copy()
-    self_state.pop("_sa_instance_state")
-    other_state = vars(other).copy()
-    other_state.pop("_sa_instance_state")
-    return self_state == other_state
-
-
-class ModelSpec(Enum):
-    DATACLASS = "dataclass"
-    TYPED_DICT = "typed_dict"
-    NAMED_TUPLE = "named_tuple"
-    ATTRS = "attrs"
-    SQLALCHEMY = "sqlalchemy"
-
-    @classmethod
-    def default_requirements(cls):
-        return {
-            cls.ATTRS: HAS_ATTRS_PKG,
-        }
-
-
-@dataclass
-class ModelSpecSchema:
-    decorator: Any
-    bases: Any
-    get_field: Callable[[Any, str], Any]
-    kind: ModelSpec
-
-
-def create_sqlalchemy_decorator():
-    from sqlalchemy import String
-    from sqlalchemy.orm import Mapped, mapped_column, registry
-    from sqlalchemy.sql.base import NO_ARG
-
-    def sqlalchemy_decorator(cls):
-        reg = registry()
-
-        cls.__tablename__ = cls.__name__
-        cls.__annotations__ = {
-            key: Mapped[value]
-            for key, value in get_all_type_hints(cls).items()
-        }
-        for idx, key in enumerate(cls.__annotations__.keys()):
-            setattr(
-                cls,
-                key,
-                mapped_column(
-                    String(),
-                    primary_key=idx == 0,
-                    default=getattr(cls, key) if hasattr(cls, key) else NO_ARG,
-                ),
-            )
-
-        cls.__eq__ = sqlalchemy_equals
-        return reg.mapped(cls)
-
-    return sqlalchemy_decorator
-
-
-def model_spec_to_schema(spec: ModelSpec):
-    if spec == ModelSpec.DATACLASS:
-        return ModelSpecSchema(decorator=dataclass, bases=(), get_field=getattr, kind=spec)
-    if spec == ModelSpec.TYPED_DICT:
-        return ModelSpecSchema(decorator=lambda x: x, bases=(TypedDict,), get_field=getitem, kind=spec)
-    if spec == ModelSpec.NAMED_TUPLE:
-        return ModelSpecSchema(decorator=lambda x: x, bases=(NamedTuple,), get_field=getattr, kind=spec)
-    if spec == ModelSpec.ATTRS:
-        from attrs import define
-        return ModelSpecSchema(decorator=define, bases=(), get_field=getattr, kind=spec)
-    if spec == ModelSpec.SQLALCHEMY:
-        return ModelSpecSchema(decorator=create_sqlalchemy_decorator(), bases=(), get_field=getattr, kind=spec)
-    raise ValueError
-
-
-def exclude_model_spec(first_spec: ModelSpec, *other_specs: ModelSpec):
-    specs = [first_spec, *other_specs]
-
-    def decorator(func):
-        func.adaptix_exclude_model_spec = specs
-        return func
-
-    return decorator
-
-
-GENERIC_MODELS_REQUIREMENTS: Mapping[ModelSpec, Requirement] = {
-    ModelSpec.TYPED_DICT: HAS_PY_311,
-    ModelSpec.NAMED_TUPLE: HAS_PY_311,
-    ModelSpec.SQLALCHEMY: FailedRequirement("SQLAlchemy models can not be generic"),
-}
-
-
-def only_generic_models(test_func):
-    test_func.adaptix_model_spec_requirements = GENERIC_MODELS_REQUIREMENTS
-
-
-def parametrize_model_spec(fixture_name: str, metafunc: Metafunc) -> None:
-    if fixture_name not in metafunc.fixturenames:
-        return
-
-    excluded = getattr(metafunc.function, "adaptix_exclude_model_spec", [])
-    requirements = {
-        **ModelSpec.default_requirements(),
-        **getattr(metafunc.module, "adaptix_model_spec_requirements", {}),
-        **getattr(metafunc.function, "adaptix_model_spec_requirements", {}),
-    }
-    metafunc.parametrize(
-        fixture_name,
-        [
-            pytest.param(model_spec_to_schema(spec), id=spec.value)
-            for spec in ModelSpec
-            if spec not in excluded and (spec not in requirements or bool(requirements[spec]))
-        ],
-    )
diff --git a/tests/tests_helpers/tests_helpers/model_spec.py b/tests/tests_helpers/tests_helpers/model_spec.py
new file mode 100644
index 00000000..68a222b4
--- /dev/null
+++ b/tests/tests_helpers/tests_helpers/model_spec.py
@@ -0,0 +1,134 @@
+from dataclasses import dataclass
+from enum import Enum
+from operator import getitem
+from types import ModuleType
+from typing import Any, Callable, Mapping, NamedTuple, TypedDict, Union
+
+import pytest
+from _pytest.python import Metafunc
+
+from adaptix._internal.feature_requirement import HAS_ATTRS_PKG, HAS_PY_311, HAS_SQLALCHEMY_PKG, Requirement
+from adaptix._internal.type_tools import get_all_type_hints
+
+from .misc import FailedRequirement
+
+
+def sqlalchemy_equals(self, other):
+    if type(self) is not type(other):
+        return False
+
+    self_state = vars(self).copy()
+    self_state.pop("_sa_instance_state")
+    other_state = vars(other).copy()
+    other_state.pop("_sa_instance_state")
+    return self_state == other_state
+
+
+class ModelSpec(Enum):
+    DATACLASS = "dataclass"
+    TYPED_DICT = "typed_dict"
+    NAMED_TUPLE = "named_tuple"
+    ATTRS = "attrs"
+    SQLALCHEMY = "sqlalchemy"
+
+    @classmethod
+    def default_requirements(cls):
+        return {
+            cls.ATTRS: HAS_ATTRS_PKG,
+            cls.SQLALCHEMY: HAS_SQLALCHEMY_PKG,
+        }
+
+
+@dataclass
+class ModelSpecSchema:
+    decorator: Any
+    bases: Any
+    get_field: Callable[[Any, str], Any]
+    kind: ModelSpec
+
+
+def create_sqlalchemy_decorator():
+    from sqlalchemy import String
+    from sqlalchemy.orm import Mapped, mapped_column, registry
+    from sqlalchemy.sql.base import NO_ARG
+
+    def sqlalchemy_decorator(cls):
+        reg = registry()
+
+        cls.__tablename__ = cls.__name__
+        cls.__annotations__ = {
+            key: Mapped[value]
+            for key, value in get_all_type_hints(cls).items()
+        }
+        for idx, key in enumerate(cls.__annotations__.keys()):
+            setattr(
+                cls,
+                key,
+                mapped_column(
+                    String(),
+                    primary_key=idx == 0,
+                    default=getattr(cls, key) if hasattr(cls, key) else NO_ARG,
+                ),
+            )
+
+        cls.__eq__ = sqlalchemy_equals
+        return reg.mapped(cls)
+
+    return sqlalchemy_decorator
+
+
+def model_spec_to_schema(spec: ModelSpec):
+    if spec == ModelSpec.DATACLASS:
+        return ModelSpecSchema(decorator=dataclass, bases=(), get_field=getattr, kind=spec)
+    if spec == ModelSpec.TYPED_DICT:
+        return ModelSpecSchema(decorator=lambda x: x, bases=(TypedDict,), get_field=getitem, kind=spec)
+    if spec == ModelSpec.NAMED_TUPLE:
+        return ModelSpecSchema(decorator=lambda x: x, bases=(NamedTuple,), get_field=getattr, kind=spec)
+    if spec == ModelSpec.ATTRS:
+        from attrs import define
+        return ModelSpecSchema(decorator=define, bases=(), get_field=getattr, kind=spec)
+    if spec == ModelSpec.SQLALCHEMY:
+        return ModelSpecSchema(decorator=create_sqlalchemy_decorator(), bases=(), get_field=getattr, kind=spec)
+    raise ValueError
+
+
+def exclude_model_spec(first_spec: ModelSpec, *other_specs: ModelSpec):
+    specs = [first_spec, *other_specs]
+
+    def decorator(func):
+        func.adaptix_exclude_model_spec = specs
+        return func
+
+    return decorator
+
+
+GENERIC_MODELS_REQUIREMENTS: Mapping[ModelSpec, Requirement] = {
+    ModelSpec.TYPED_DICT: HAS_PY_311,
+    ModelSpec.NAMED_TUPLE: HAS_PY_311,
+    ModelSpec.SQLALCHEMY: FailedRequirement("SQLAlchemy models can not be generic"),
+}
+
+
+def only_generic_models(obj: Union[Callable, ModuleType]):
+    obj.adaptix_model_spec_requirements = GENERIC_MODELS_REQUIREMENTS
+    return obj
+
+
+def parametrize_model_spec(fixture_name: str, metafunc: Metafunc) -> None:
+    if fixture_name not in metafunc.fixturenames:
+        return
+
+    excluded = getattr(metafunc.function, "adaptix_exclude_model_spec", [])
+    requirements = {
+        **ModelSpec.default_requirements(),
+        **getattr(metafunc.module, "adaptix_model_spec_requirements", {}),
+        **getattr(metafunc.function, "adaptix_model_spec_requirements", {}),
+    }
+    metafunc.parametrize(
+        fixture_name,
+        [
+            pytest.param(model_spec_to_schema(spec), id=spec.value)
+            for spec in ModelSpec
+            if spec not in excluded and (spec not in requirements or bool(requirements[spec]))
+        ],
+    )
diff --git a/tests/unit/model_tools/introspection/test_attrs.py b/tests/unit/model_tools/introspection/test_attrs.py
index 57717d70..c81a3b83 100644
--- a/tests/unit/model_tools/introspection/test_attrs.py
+++ b/tests/unit/model_tools/introspection/test_attrs.py
@@ -14,7 +14,7 @@
     DefaultValue,
     InputField,
     InputShape,
-    IntrospectionImpossible,
+    IntrospectionError,
     NoDefault,
     OutputField,
     OutputShape,
@@ -796,10 +796,10 @@ def test_not_attrs():
     class IAmDataclass:
         foo: int
 
-    with pytest.raises(IntrospectionImpossible):
+    with pytest.raises(IntrospectionError):
         get_attrs_shape(IAmDataclass)
 
-    with pytest.raises(IntrospectionImpossible):
+    with pytest.raises(IntrospectionError):
         get_attrs_shape(Tuple[IAmDataclass, int])
 
 
diff --git a/tests/unit/model_tools/introspection/test_callable.py b/tests/unit/model_tools/introspection/test_callable.py
index e5f15a17..121c0931 100644
--- a/tests/unit/model_tools/introspection/test_callable.py
+++ b/tests/unit/model_tools/introspection/test_callable.py
@@ -11,7 +11,7 @@
     DefaultValue,
     InputField,
     InputShape,
-    IntrospectionImpossible,
+    IntrospectionError,
     NoDefault,
     Param,
     ParamKind,
@@ -22,7 +22,7 @@
 
 
 def test_introspection_impossible():
-    pytest.raises(IntrospectionImpossible, lambda: get_callable_shape(Tuple))
+    pytest.raises(IntrospectionError, lambda: get_callable_shape(Tuple))
 
 
 def test_simple():
diff --git a/tests/unit/model_tools/introspection/test_class_init.py b/tests/unit/model_tools/introspection/test_class_init.py
index 2b43e732..8f0c851b 100644
--- a/tests/unit/model_tools/introspection/test_class_init.py
+++ b/tests/unit/model_tools/introspection/test_class_init.py
@@ -11,7 +11,7 @@
     DefaultValue,
     InputField,
     InputShape,
-    IntrospectionImpossible,
+    IntrospectionError,
     NoDefault,
     Param,
     ParamKind,
@@ -261,7 +261,7 @@ def __init__(self, a, b, *args):
             self.b = b
             self.args = args
 
-    with pytest.raises(IntrospectionImpossible):
+    with pytest.raises(IntrospectionError):
         get_class_init_shape(HasVarArg)
 
 
diff --git a/tests/unit/morphing/generic_provider/test_union_provider.py b/tests/unit/morphing/generic_provider/test_union_provider.py
index 573eac34..286fac14 100644
--- a/tests/unit/morphing/generic_provider/test_union_provider.py
+++ b/tests/unit/morphing/generic_provider/test_union_provider.py
@@ -206,7 +206,7 @@ def test_bad_optional_dumping(retort, debug_trail):
                     is_demonstrative=True,
                     is_terminal=True,
                 ),
-                f"Location: type={Union[int, Callable[[int], str]]}",
+                f"Location: `{str(Union[int, Callable[[int], str]]).replace('typing.', '', 1)}`",
             ),
         ),
         func=lambda: (
diff --git a/tests/unit/morphing/model/shape_provider/local_helpers.py b/tests/unit/morphing/model/shape_provider/local_helpers.py
index dc8e5df8..a9182045 100644
--- a/tests/unit/morphing/model/shape_provider/local_helpers.py
+++ b/tests/unit/morphing/model/shape_provider/local_helpers.py
@@ -1,7 +1,7 @@
 from typing import Mapping
 
 from adaptix import Retort, TypeHint
-from adaptix._internal.provider.request_cls import LocMap, LocStack, TypeHintLoc
+from adaptix._internal.provider.request_cls import LocStack, TypeHintLoc
 from adaptix._internal.provider.shape_provider import (
     InputShapeRequest,
     OutputShapeRequest,
@@ -15,14 +15,14 @@ def assert_fields_types(tp: TypeHint, expected: Mapping[str, TypeHint]) -> None:
 
     input_shape = provide_generic_resolved_shape(
         mediator,
-        InputShapeRequest(loc_stack=LocStack(LocMap(TypeHintLoc(type=tp)))),
+        InputShapeRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
     )
     input_field_types = {field.id: field.type for field in input_shape.fields}
     assert input_field_types == expected
 
     output_shape = provide_generic_resolved_shape(
         mediator,
-        OutputShapeRequest(loc_stack=LocStack(LocMap(TypeHintLoc(type=tp)))),
+        OutputShapeRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
     )
     output_field_types = {field.id: field.type for field in output_shape.fields}
     assert output_field_types == expected
diff --git a/tests/unit/morphing/model/shape_provider/test_generic_resolving.py b/tests/unit/morphing/model/shape_provider/test_generic_resolving.py
index cdc800fd..f6741d7c 100644
--- a/tests/unit/morphing/model/shape_provider/test_generic_resolving.py
+++ b/tests/unit/morphing/model/shape_provider/test_generic_resolving.py
@@ -1,14 +1,9 @@
+import sys
 from typing import Any, Dict, Generic, List, Tuple, TypeVar
 
 import pytest
-from tests_helpers import (
-    GENERIC_MODELS_REQUIREMENTS,
-    ModelSpec,
-    cond_list,
-    exclude_model_spec,
-    load_namespace_keeping_module,
-    requires,
-)
+from tests_helpers import ModelSpec, cond_list, exclude_model_spec, load_namespace_keeping_module, requires
+from tests_helpers.model_spec import only_generic_models
 
 from adaptix import CannotProvide, Retort
 from adaptix._internal.feature_requirement import (
@@ -20,7 +15,7 @@
     HAS_TV_TUPLE,
     IS_PYPY,
 )
-from adaptix._internal.provider.request_cls import LocMap, LocStack, TypeHintLoc
+from adaptix._internal.provider.request_cls import LocStack, TypeHintLoc
 from adaptix._internal.provider.shape_provider import (
     InputShapeRequest,
     OutputShapeRequest,
@@ -34,7 +29,7 @@
 V = TypeVar("V")
 
 
-adaptix_model_spec_requirements = GENERIC_MODELS_REQUIREMENTS
+only_generic_models(sys.modules[__name__])
 
 
 @pytest.fixture(
@@ -270,13 +265,13 @@ def test_not_a_model(tp):
     with pytest.raises(CannotProvide):
         provide_generic_resolved_shape(
             mediator,
-            InputShapeRequest(loc_stack=LocStack(LocMap(TypeHintLoc(type=tp)))),
+            InputShapeRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
         )
 
     with pytest.raises(CannotProvide):
         provide_generic_resolved_shape(
             mediator,
-            OutputShapeRequest(loc_stack=LocStack(LocMap(TypeHintLoc(type=tp)))),
+            OutputShapeRequest(loc_stack=LocStack(TypeHintLoc(type=tp))),
         )
 
 
diff --git a/tests/unit/morphing/name_layout/test_provider.py b/tests/unit/morphing/name_layout/test_provider.py
index 99a9719a..b25591f8 100644
--- a/tests/unit/morphing/name_layout/test_provider.py
+++ b/tests/unit/morphing/name_layout/test_provider.py
@@ -64,7 +64,7 @@
 from adaptix._internal.morphing.request_cls import DumperRequest, LoaderRequest
 from adaptix._internal.provider.loc_stack_filtering import P
 from adaptix._internal.provider.provider_template import ValueProvider
-from adaptix._internal.provider.request_cls import LocMap, LocStack, TypeHintLoc
+from adaptix._internal.provider.request_cls import LocStack, TypeHintLoc
 from adaptix._internal.provider.shape_provider import InputShapeRequest, OutputShapeRequest
 
 
@@ -85,21 +85,6 @@ def stub(*args, **kwargs):
     pass
 
 
-class StubClass:
-    pass
-
-
-class StubClass2:
-    pass
-
-
-TYPE_HINT_LOC_MAP = LocMap(
-    TypeHintLoc(
-        type=StubClass,
-    ),
-)
-
-
 @dataclass
 class Stub:
     pass
@@ -107,7 +92,6 @@ class Stub:
 
 def make_layouts(
     *fields_or_providers: Union[TestField, Provider],
-    loc_map: LocMap = TYPE_HINT_LOC_MAP,
 ) -> Layouts:
     fields = [element for element in fields_or_providers if isinstance(element, TestField)]
     providers = [element for element in fields_or_providers if isinstance(element, Provider)]
@@ -174,24 +158,27 @@ def make_layouts(
     )
     retort.get_loader(Stub)
     retort.get_dumper(Stub)
+    loc = TypeHintLoc(
+        type=Stub,
+    )
     inp_request = InputNameLayoutRequest(
-        loc_stack=LocStack(loc_map),
+        loc_stack=LocStack(loc),
         shape=input_shape,
     )
     out_request = OutputNameLayoutRequest(
-        loc_stack=LocStack(loc_map),
+        loc_stack=LocStack(loc),
         shape=output_shape,
     )
     inp_name_layout = retort.provide(inp_request)
     out_name_layout = retort.provide(out_request)
     retort.provide(
         LoaderRequest(
-            loc_stack=LocStack(loc_map),
+            loc_stack=LocStack(loc),
         ),
     )
     retort.provide(
         DumperRequest(
-            loc_stack=LocStack(loc_map),
+            loc_stack=LocStack(loc),
         ),
     )
     return Layouts(inp_name_layout, out_name_layout)
@@ -926,13 +913,13 @@ def test_extra_at_list():
                                 is_terminal=True,
                                 is_demonstrative=True,
                             ),
-                            "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                            f"Location: `{Stub.__qualname__}`",
                         ),
                     ],
                     is_terminal=True,
                     is_demonstrative=True,
                 ),
-                "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                f"Location: `{Stub.__qualname__}`",
             ),
         ),
         lambda: make_layouts(
@@ -946,7 +933,6 @@ def test_extra_at_list():
                 extra_out="b",
             ),
             DEFAULT_NAME_MAPPING,
-            loc_map=TYPE_HINT_LOC_MAP,
         ),
     )
 
@@ -965,13 +951,13 @@ def test_required_field_skip():
                                 is_terminal=True,
                                 is_demonstrative=True,
                             ),
-                            "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                            f"Location: `{Stub.__qualname__}`",
                         ),
                     ],
                     is_terminal=True,
                     is_demonstrative=True,
                 ),
-                "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                f"Location: `{Stub.__qualname__}`",
             ),
         ),
         lambda: make_layouts(
@@ -981,7 +967,6 @@ def test_required_field_skip():
                 skip=["a"],
             ),
             DEFAULT_NAME_MAPPING,
-            loc_map=TYPE_HINT_LOC_MAP,
         ),
     )
 
@@ -1000,13 +985,13 @@ def test_inconsistent_path_elements():
                                 is_terminal=True,
                                 is_demonstrative=True,
                             ),
-                            "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                            f"Location: `{Stub.__qualname__}`",
                         ),
                     ],
                     is_terminal=True,
                     is_demonstrative=True,
                 ),
-                "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                f"Location: `{Stub.__qualname__}`",
             ),
         ),
         lambda: make_layouts(
@@ -1019,7 +1004,6 @@ def test_inconsistent_path_elements():
                 },
             ),
             DEFAULT_NAME_MAPPING,
-            loc_map=TYPE_HINT_LOC_MAP,
         ),
     )
 
@@ -1038,13 +1022,13 @@ def test_duplicated_path():
                                 is_terminal=True,
                                 is_demonstrative=True,
                             ),
-                            "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                            f"Location: `{Stub.__qualname__}`",
                         ),
                     ],
                     is_terminal=True,
                     is_demonstrative=True,
                 ),
-                "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                f"Location: `{Stub.__qualname__}`",
             ),
         ),
         lambda: make_layouts(
@@ -1057,7 +1041,6 @@ def test_duplicated_path():
                 },
             ),
             DEFAULT_NAME_MAPPING,
-            loc_map=TYPE_HINT_LOC_MAP,
         ),
     )
 
@@ -1076,13 +1059,13 @@ def test_optional_field_at_list():
                                 is_terminal=True,
                                 is_demonstrative=True,
                             ),
-                            "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                            f"Location: `{Stub.__qualname__}`",
                         ),
                     ],
                     is_terminal=True,
                     is_demonstrative=True,
                 ),
-                "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                f"Location: `{Stub.__qualname__}`",
             ),
         ),
         lambda: make_layouts(
@@ -1095,7 +1078,6 @@ def test_optional_field_at_list():
                 },
             ),
             DEFAULT_NAME_MAPPING,
-            loc_map=TYPE_HINT_LOC_MAP,
         ),
     )
 
@@ -1115,13 +1097,13 @@ def test_one_path_is_prefix_of_another():
                                 is_terminal=True,
                                 is_demonstrative=True,
                             ),
-                            "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                            f"Location: `{Stub.__qualname__}`",
                         ),
                     ],
                     is_terminal=True,
                     is_demonstrative=True,
                 ),
-                "Location: type=<class 'tests.unit.morphing.name_layout.test_provider.Stub'>",
+                f"Location: `{Stub.__qualname__}`",
             ),
         ),
         lambda: make_layouts(
@@ -1136,7 +1118,6 @@ def test_one_path_is_prefix_of_another():
                 },
             ),
             DEFAULT_NAME_MAPPING,
-            loc_map=TYPE_HINT_LOC_MAP,
         ),
     )
 
diff --git a/tests/unit/morphing/test_enum_provider.py b/tests/unit/morphing/test_enum_provider.py
index 5b73e50b..04c42379 100644
--- a/tests/unit/morphing/test_enum_provider.py
+++ b/tests/unit/morphing/test_enum_provider.py
@@ -259,7 +259,7 @@ def test_flag_by_exact_value_loader_creation_fail(strict_coercion, debug_trail):
                         is_terminal=True,
                         is_demonstrative=True,
                     ),
-                    f"Location: type={FlagEnumWithSkippedBit}",
+                    f"Location: `{FlagEnumWithSkippedBit.__name__}`",
                 ),
             ),
         ),
@@ -275,7 +275,7 @@ def test_flag_by_exact_value_loader_creation_fail(strict_coercion, debug_trail):
                         is_terminal=True,
                         is_demonstrative=True,
                     ),
-                    f"Location: type={FlagEnumWithNegativeValue}",
+                    f"Location: `{FlagEnumWithNegativeValue.__name__}`",
                 ),
             ),
         ),
diff --git a/tests/unit/provider/test_loc_stack_filtering.py b/tests/unit/provider/test_loc_stack_filtering.py
index d6085c72..67c60cdf 100644
--- a/tests/unit/provider/test_loc_stack_filtering.py
+++ b/tests/unit/provider/test_loc_stack_filtering.py
@@ -19,7 +19,8 @@
     OriginSubclassLSC,
     create_loc_stack_checker,
 )
-from adaptix._internal.provider.request_cls import FieldLoc, GenericParamLoc, LocMap, LocStack, TypeHintLoc
+from adaptix._internal.provider.location import FieldLoc, GenericParamLoc, TypeHintLoc
+from adaptix._internal.provider.request_cls import LocStack
 from adaptix._internal.type_tools import normalize_type
 
 
@@ -64,53 +65,53 @@ def create_mediator():
     [
         param_result(
             LocStack(
-                LocMap(TypeHintLoc(int)),
+                TypeHintLoc(int),
             ),
             result=False,
             id="1-item",
         ),
         param_result(
             LocStack(
-                LocMap(TypeHintLoc(int)),
-                LocMap(TypeHintLoc(str)),
+                TypeHintLoc(int),
+                TypeHintLoc(str),
             ),
             result=False,
             id="2-items",
         ),
         param_result(
             LocStack(
-                LocMap(TypeHintLoc(int)),
-                LocMap(TypeHintLoc(str)),
-                LocMap(TypeHintLoc(bool)),
+                TypeHintLoc(int),
+                TypeHintLoc(str),
+                TypeHintLoc(bool),
             ),
             result=True,
             id="ok",
         ),
         param_result(
             LocStack(
-                LocMap(TypeHintLoc(int)),
-                LocMap(TypeHintLoc(str)),
-                LocMap(TypeHintLoc(str)),
+                TypeHintLoc(int),
+                TypeHintLoc(str),
+                TypeHintLoc(str),
             ),
             result=False,
             id="last-is-bad",
         ),
         param_result(
             LocStack(
-                LocMap(TypeHintLoc(int)),
-                LocMap(TypeHintLoc(int)),
-                LocMap(TypeHintLoc(str)),
-                LocMap(TypeHintLoc(bool)),
+                TypeHintLoc(int),
+                TypeHintLoc(int),
+                TypeHintLoc(str),
+                TypeHintLoc(bool),
             ),
             result=True,
             id="extra-stack-matched-with-prev",
         ),
         param_result(
             LocStack(
-                LocMap(TypeHintLoc(str)),
-                LocMap(TypeHintLoc(int)),
-                LocMap(TypeHintLoc(str)),
-                LocMap(TypeHintLoc(bool)),
+                TypeHintLoc(str),
+                TypeHintLoc(int),
+                TypeHintLoc(str),
+                TypeHintLoc(bool),
             ),
             result=True,
             id="extra-stack",
@@ -133,29 +134,29 @@ def test_stack_end_rc(loc_stack, result, context):
     [
         param_result(
             LocStack(
-                LocMap(TypeHintLoc(bool)),
-                LocMap(TypeHintLoc(int)),
-                LocMap(TypeHintLoc(str)),
-                LocMap(TypeHintLoc(bool)),
+                TypeHintLoc(bool),
+                TypeHintLoc(int),
+                TypeHintLoc(str),
+                TypeHintLoc(bool),
             ),
             result=True,
             id="ok",
         ),
         param_result(
             LocStack(
-                LocMap(TypeHintLoc(int)),
-                LocMap(TypeHintLoc(str)),
-                LocMap(TypeHintLoc(bool)),
+                TypeHintLoc(int),
+                TypeHintLoc(str),
+                TypeHintLoc(bool),
             ),
             result=False,
             id="too-small",
         ),
         param_result(
             LocStack(
-                LocMap(TypeHintLoc(str)),
-                LocMap(TypeHintLoc(int)),
-                LocMap(TypeHintLoc(str)),
-                LocMap(TypeHintLoc(bool)),
+                TypeHintLoc(str),
+                TypeHintLoc(int),
+                TypeHintLoc(str),
+                TypeHintLoc(bool),
             ),
             result=False,
             id="bad",
@@ -184,14 +185,12 @@ class WithUserName:
     user_name: str
 
 
-def field_loc_map(name: str, tp: TypeHint) -> LocMap:
-    return LocMap(
-        TypeHintLoc(tp),
-        FieldLoc(
-            field_id=name,
-            default=NoDefault(),
-            metadata={},
-        ),
+def field_loc_map(name: str, tp: TypeHint) -> FieldLoc:
+    return FieldLoc(
+        type=tp,
+        field_id=name,
+        default=NoDefault(),
+        metadata={},
     )
 
 
@@ -200,20 +199,20 @@ def field_loc_map(name: str, tp: TypeHint) -> LocMap:
     [
         param_result(
             P[int],
-            LocStack(LocMap(TypeHintLoc(int))),
+            LocStack(TypeHintLoc(int)),
             result=True,
             id="int-ok",
         ),
         param_result(
             P[int],
-            LocStack(LocMap(TypeHintLoc(str))),
+            LocStack(TypeHintLoc(str)),
             result=False,
             id="int-fail",
         ),
         param_result(
             P[WithUserName].user_name,
             LocStack(
-                LocMap(TypeHintLoc(WithUserName)),
+                TypeHintLoc(WithUserName),
                 field_loc_map("user_name", str),
             ),
             result=True,
@@ -222,7 +221,7 @@ def field_loc_map(name: str, tp: TypeHint) -> LocMap:
         param_result(
             P[WithUserName].user_name,
             LocStack(
-                LocMap(TypeHintLoc(WithUserName)),
+                TypeHintLoc(WithUserName),
                 field_loc_map("user_name", int),
             ),
             result=True,
@@ -231,7 +230,7 @@ def field_loc_map(name: str, tp: TypeHint) -> LocMap:
         param_result(
             P[WithUserName].user_id,
             LocStack(
-                LocMap(TypeHintLoc(WithUserName)),
+                TypeHintLoc(WithUserName),
                 field_loc_map("user_name", int),
             ),
             result=False,
@@ -240,7 +239,7 @@ def field_loc_map(name: str, tp: TypeHint) -> LocMap:
         param_result(
             P[WithUserName] + P.user_name,
             LocStack(
-                LocMap(TypeHintLoc(WithUserName)),
+                TypeHintLoc(WithUserName),
                 field_loc_map("user_name", int),
             ),
             result=True,
@@ -249,8 +248,8 @@ def field_loc_map(name: str, tp: TypeHint) -> LocMap:
         param_result(
             P[Dict].generic_arg(0, str),
             LocStack(
-                LocMap(TypeHintLoc(Dict)),
-                LocMap(TypeHintLoc(str), GenericParamLoc(0)),
+                TypeHintLoc(Dict),
+                GenericParamLoc(str, 0),
             ),
             result=True,
             id="generic-arg-ok",
@@ -258,8 +257,8 @@ def field_loc_map(name: str, tp: TypeHint) -> LocMap:
         param_result(
             P[Dict].generic_arg(0, str),
             LocStack(
-                LocMap(TypeHintLoc(Dict)),
-                LocMap(TypeHintLoc(str), GenericParamLoc(1)),
+                TypeHintLoc(Dict),
+                GenericParamLoc(str, 1),
             ),
             result=False,
             id="generic-arg-fail",
diff --git a/tests/unit/provider/test_overlay_schema.py b/tests/unit/provider/test_overlay_schema.py
index bb43f134..ff3ad08a 100644
--- a/tests/unit/provider/test_overlay_schema.py
+++ b/tests/unit/provider/test_overlay_schema.py
@@ -7,7 +7,7 @@
 from adaptix import Chain, Mediator, Omittable, Omitted, Provider, Request, bound
 from adaptix._internal.common import VarTuple
 from adaptix._internal.provider.overlay_schema import Overlay, OverlayProvider, Schema, provide_schema
-from adaptix._internal.provider.request_cls import LocMap, LocStack, TypeHintLoc
+from adaptix._internal.provider.request_cls import LocStack, TypeHintLoc
 from adaptix._internal.provider.static_provider import StaticProvider, static_provision_action
 
 
@@ -50,6 +50,18 @@ def provide_overlay_schema(recipe: Iterable[Provider], provide_action: Callable[
     return retort.provide(SampleRequest())
 
 
+class MyClass1:
+    pass
+
+
+class MyClass2(MyClass1):
+    pass
+
+
+def provide_myclass1(mediator):
+    return provide_schema(MyOverlay, mediator, LocStack(TypeHintLoc(object)))
+
+
 def test_simple():
     assert provide_overlay_schema(
         recipe=[
@@ -63,7 +75,7 @@ def test_simple():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap())),
+        provide_action=provide_myclass1,
     ) == MySchema(
         number=1,
         char_list=("a", "b"),
@@ -92,7 +104,7 @@ def test_chaining():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap())),
+        provide_action=provide_myclass1,
     ) == MySchema(
         number=1,
         char_list=("a", "b", "c", "d"),
@@ -119,7 +131,7 @@ def test_chaining():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap())),
+        provide_action=provide_myclass1,
     ) == MySchema(
         number=2,
         char_list=("c", "d", "a", "b"),
@@ -146,7 +158,7 @@ def test_chaining():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap())),
+        provide_action=provide_myclass1,
     ) == MySchema(
         number=2,
         char_list=("a", "b", "c", "d"),
@@ -173,7 +185,7 @@ def test_chaining():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap())),
+        provide_action=provide_myclass1,
     ) == MySchema(
         number=1,
         char_list=("c", "d"),
@@ -200,21 +212,13 @@ def test_chaining():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap())),
+        provide_action=provide_myclass1,
     ) == MySchema(
         number=2,
         char_list=("c", "d"),
     )
 
 
-class MyClass1:
-    pass
-
-
-class MyClass2(MyClass1):
-    pass
-
-
 def test_typehint_location():
     assert provide_overlay_schema(
         recipe=[
@@ -243,7 +247,7 @@ def test_typehint_location():
                 ),
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap(TypeHintLoc(type=MyClass2)))),
+        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(TypeHintLoc(type=MyClass2))),
     ) == MySchema(
         number=1,
         char_list=("a", "b", "c", "d"),
@@ -288,7 +292,7 @@ def test_typehint_location():
                 ),
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap(TypeHintLoc(type=MyClass2)))),
+        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(TypeHintLoc(type=MyClass2))),
     ) == MySchema(
         number=1,
         char_list=("a", "b", "c", "d", "e", "f"),
@@ -321,5 +325,5 @@ def test_omitted_fields():
                     chain=None,
                 ),
             ],
-            provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap())),
+            provide_action=provide_myclass1,
         )
diff --git a/tests/unit/retort/test_operating_retort.py b/tests/unit/retort/test_operating_retort.py
index 0521b917..d33b72cf 100644
--- a/tests/unit/retort/test_operating_retort.py
+++ b/tests/unit/retort/test_operating_retort.py
@@ -1,7 +1,8 @@
 from dataclasses import dataclass
 from typing import List
 
-from tests_helpers import raises_exc, requires, with_cause, with_notes
+import pytest
+from tests_helpers import cond_list, raises_exc, with_cause, with_notes
 
 from adaptix import AggregateCannotProvide, CannotProvide, NoSuitableProvider, Retort
 from adaptix._internal.feature_requirement import HAS_STD_CLASSES_GENERICS
@@ -26,29 +27,25 @@ class Stub:
                     [
                         with_notes(
                             CannotProvide(
-                                "There is no provider that can create specified loader",
+                                "Cannot find loader",
                                 is_terminal=False,
                                 is_demonstrative=True,
                             ),
-                            f"Exception was raised while processing field 'f2' of {Stub}",
-                            "Location: type=<class 'memoryview'>, field_id='f2',"
-                            " default=NoDefault(), metadata=mappingproxy({}), is_required=True",
+                            f"Location: `{Stub.__qualname__}.f2: memoryview`",
                         ),
                         with_notes(
                             CannotProvide(
-                                "There is no provider that can create specified loader",
+                                "Cannot find loader",
                                 is_terminal=False,
                                 is_demonstrative=True,
                             ),
-                            f"Exception was raised while processing field 'f3' of {Stub}",
-                            "Location: type=<class 'memoryview'>, field_id='f3',"
-                            " default=NoDefault(), metadata=mappingproxy({}), is_required=True",
+                            f"Location: `{Stub.__qualname__}.f3: memoryview`",
                         ),
                     ],
                     is_terminal=True,
                     is_demonstrative=True,
                 ),
-                f"Location: type={Stub}",
+                f"Location: `{Stub.__qualname__}`",
             ),
         ),
         lambda: retort.get_loader(Stub),
@@ -73,31 +70,25 @@ class Stub:
                     [
                         with_notes(
                             CannotProvide(
-                                "There is no provider that can create specified dumper",
+                                "Cannot find dumper",
                                 is_terminal=False,
                                 is_demonstrative=True,
                             ),
-                            f"Exception was raised while processing field 'f2' of {Stub}",
-                            "Location: type=<class 'memoryview'>, field_id='f2',"
-                            " default=NoDefault(), metadata=mappingproxy({}),"
-                            " accessor=DescriptorAccessor(attr_name='f2', access_error=None)",
+                            f"Location: `{Stub.__qualname__}.f2: memoryview`",
                         ),
                         with_notes(
                             CannotProvide(
-                                "There is no provider that can create specified dumper",
+                                "Cannot find dumper",
                                 is_terminal=False,
                                 is_demonstrative=True,
                             ),
-                            f"Exception was raised while processing field 'f3' of {Stub}",
-                            "Location: type=<class 'memoryview'>, field_id='f3',"
-                            " default=NoDefault(), metadata=mappingproxy({}),"
-                            " accessor=DescriptorAccessor(attr_name='f3', access_error=None)",
+                            f"Location: `{Stub.__qualname__}.f3: memoryview`",
                         ),
                     ],
                     is_terminal=True,
                     is_demonstrative=True,
                 ),
-                f"Location: type={Stub}",
+                f"Location: `{Stub.__qualname__}`",
             ),
         ),
         lambda: retort.get_dumper(Stub),
@@ -124,15 +115,25 @@ class BookDTO:
                 f" -> {BookDTO.__module__}.{BookDTO.__qualname__}>",
             ),
             AggregateCannotProvide(
-                "Linkings for some fields are not found",
+                "Cannot create top-level coercer",
                 [
                     with_notes(
-                        CannotProvide(
-                            f"Cannot find paired field of `{BookDTO.__qualname__}.author` for linking",
-                            is_terminal=False,
+                        AggregateCannotProvide(
+                            "Cannot create coercer for models. Linkings for some fields are not found",
+                            [
+                                with_notes(
+                                    CannotProvide(
+                                        f"Cannot find paired field of `{BookDTO.__qualname__}.author: str` for linking",
+                                        is_terminal=False,
+                                        is_demonstrative=True,
+                                    ),
+                                    "Note: This is a required field, so it must take value",
+                                ),
+                            ],
+                            is_terminal=True,
                             is_demonstrative=True,
                         ),
-                        "Note: This is a required filed, so it must take value",
+                        f"Linking: `{Book.__qualname__} -> {BookDTO.__qualname__}`",
                     ),
                 ],
                 is_terminal=True,
@@ -163,17 +164,27 @@ class BookDTO:
                 f" -> {BookDTO.__module__}.{BookDTO.__qualname__}>",
             ),
             AggregateCannotProvide(
-                "Linkings for some fields are not found",
+                "Cannot create top-level coercer",
                 [
                     with_notes(
-                        CannotProvide(
-                            f"Cannot find paired field of `{BookDTO.__qualname__}.author` for linking",
-                            is_terminal=False,
+                        AggregateCannotProvide(
+                            "Cannot create coercer for models. Linkings for some fields are not found",
+                            [
+                                with_notes(
+                                    CannotProvide(
+                                        f"Cannot find paired field of `{BookDTO.__qualname__}.author: str` for linking",
+                                        is_terminal=False,
+                                        is_demonstrative=True,
+                                    ),
+                                    "Note: Current policy forbids unlinked optional fields,"
+                                    " so you need to link it to another field"
+                                    " or explicitly confirm the desire to skipping using `allow_unlinked_optional`",
+                                ),
+                            ],
+                            is_terminal=True,
                             is_demonstrative=True,
                         ),
-                        "Note: Current policy forbids unlinked optional fields,"
-                        " so you need to link it to another field"
-                        " or explicitly confirm the desire to skipping using `allow_unlinked_optional`",
+                        f"Linking: `{Book.__qualname__} -> {BookDTO.__qualname__}`",
                     ),
                 ],
                 is_terminal=True,
@@ -205,51 +216,25 @@ class BookDTO:
                 f" -> {BookDTO.__module__}.{BookDTO.__qualname__}>",
             ),
             AggregateCannotProvide(
-                "Coercers for some linkings are not found",
+                "Cannot create top-level coercer",
                 [
-                    CannotProvide(
-                        f"Cannot find coercer for linking"
-                        f" `{Book.__qualname__}.author: int -> {BookDTO.__qualname__}.author: str`",
-                        is_terminal=False,
-                        is_demonstrative=True,
-                    ),
-                ],
-                is_terminal=True,
-                is_demonstrative=True,
-            ),
-        ),
-        lambda: get_converter(Book, BookDTO),
-    )
-
-
-def test_cannot_produce_converter_no_coercer_complex_type():
-    @dataclass
-    class Book:
-        title: str
-        price: int
-        authors: List[str]
-
-    @dataclass
-    class BookDTO:
-        title: str
-        price: int
-        authors: List[int]
-
-    raises_exc(
-        with_cause(
-            NoSuitableProvider(
-                f"Cannot produce converter for"
-                f" <Signature (src: {Book.__module__}.{Book.__qualname__}, /)"
-                f" -> {BookDTO.__module__}.{BookDTO.__qualname__}>",
-            ),
-            AggregateCannotProvide(
-                "Coercers for some linkings are not found",
-                [
-                    CannotProvide(
-                        f"Cannot find coercer for linking"
-                        f" `{Book.__qualname__}.authors: List[str] -> {BookDTO.__qualname__}.authors: List[int]`",
-                        is_terminal=False,
-                        is_demonstrative=True,
+                    with_notes(
+                        AggregateCannotProvide(
+                            "Cannot create coercer for models. Coercers for some linkings are not found",
+                            [
+                                with_notes(
+                                    CannotProvide(
+                                        "Cannot find coercer",
+                                        is_terminal=False,
+                                        is_demonstrative=True,
+                                    ),
+                                    f"Linking: `{Book.__qualname__}.author: int -> {BookDTO.__qualname__}.author: str`",
+                                ),
+                            ],
+                            is_terminal=True,
+                            is_demonstrative=True,
+                        ),
+                        f"Linking: `{Book.__qualname__} -> {BookDTO.__qualname__}`",
                     ),
                 ],
                 is_terminal=True,
@@ -260,19 +245,25 @@ class BookDTO:
     )
 
 
-@requires(HAS_STD_CLASSES_GENERICS)
-def test_cannot_produce_converter_no_coercer_complex_builtin_type():
+@pytest.mark.parametrize(
+    ["list_tp", "list_tp_name"],
+    [
+        pytest.param(List, "List"),
+        *cond_list(HAS_STD_CLASSES_GENERICS, [pytest.param(list, "list")]),
+    ],
+)
+def test_cannot_produce_converter_no_coercer_complex_type(list_tp, list_tp_name):
     @dataclass
     class Book:
         title: str
         price: int
-        authors: list[str]
+        authors: list_tp[str]
 
     @dataclass
     class BookDTO:
         title: str
         price: int
-        authors: list[int]
+        authors: list_tp[int]
 
     raises_exc(
         with_cause(
@@ -282,13 +273,37 @@ class BookDTO:
                 f" -> {BookDTO.__module__}.{BookDTO.__qualname__}>",
             ),
             AggregateCannotProvide(
-                "Coercers for some linkings are not found",
+                "Cannot create top-level coercer",
                 [
-                    CannotProvide(
-                        f"Cannot find coercer for linking"
-                        f" `{Book.__qualname__}.authors: list[str] -> {BookDTO.__qualname__}.authors: list[int]`",
-                        is_terminal=False,
-                        is_demonstrative=True,
+                    with_notes(
+                        AggregateCannotProvide(
+                            "Cannot create coercer for models. Coercers for some linkings are not found",
+                            [
+                                with_notes(
+                                    AggregateCannotProvide(
+                                        "Cannot create coercer for iterables. Coercer for element cannot be created",
+                                        [
+                                            with_notes(
+                                                CannotProvide(
+                                                    "Cannot find coercer",
+                                                    is_terminal=False,
+                                                    is_demonstrative=True,
+                                                ),
+                                                "Linking: `str -> int`",
+                                            ),
+                                        ],
+                                        is_terminal=True,
+                                        is_demonstrative=True,
+                                    ),
+                                    f"Linking:"
+                                    f" `{Book.__qualname__}.authors: {list_tp_name}[str]"
+                                    f" -> {BookDTO.__qualname__}.authors: {list_tp_name}[int]`",
+                                ),
+                            ],
+                            is_terminal=True,
+                            is_demonstrative=True,
+                        ),
+                        f"Linking: `{Book.__qualname__} -> {BookDTO.__qualname__}`",
                     ),
                 ],
                 is_terminal=True,