Repository: reagento/adaptix (language: python)

Issue:
Correcting required/optional keys fields of TypedDict
issue #227


Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
index cea703e9..5d45af37 100644
--- a/.flake8
+++ b/.flake8
@@ -41,3 +41,5 @@ per-file-ignores=
 
     *_312.py:E999
     *tests/unit/morphing/model/shape_provider/data_gen_models.py:F821
+
+    tests/unit/provider/test_loc_stack_filtering.py:A001,A002
diff --git a/README.md b/README.md
index 3d154143..7347024f 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 <div align="center">
   <picture>
-    <source media="(prefers-color-scheme: dark)" srcset="docs/logo/adaptix-with-title-dark.png">
-    <source media="(prefers-color-scheme: light)" srcset="docs/logo/adaptix-with-title-light.png">
+    <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">
   </picture>
 
@@ -25,10 +25,11 @@ An extremely flexible and configurable data model conversion library.
 
 Install
 ```bash
-pip install adaptix==3.0.0b1
+pip install adaptix==3.0.0b2
 ```
 
-Use
+Use for model loading and dumping.
+
 ```python
 from dataclasses import dataclass
 
@@ -55,13 +56,52 @@ assert book == Book(title="Fahrenheit 451", price=100)
 assert retort.dump(book) == data
 ```
 
+Use for converting one model to another.
+
+```python
+from dataclasses import dataclass
+
+from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
+
+from adaptix.conversion import get_converter
+
+
+class Base(DeclarativeBase):
+    pass
+
+
+class Book(Base):
+    __tablename__ = 'books'
+
+    id: Mapped[int] = mapped_column(primary_key=True)
+    title: Mapped[str]
+    price: Mapped[int]
+
+
+@dataclass
+class BookDTO:
+    id: int
+    title: str
+    price: int
+
+
+convert_book_to_dto = get_converter(Book, BookDTO)
+
+assert (
+    convert_book_to_dto(Book(id=183, title="Fahrenheit 451", price=100))
+    ==
+    BookDTO(id=183, title="Fahrenheit 451", price=100)
+)
+```
+
 ## Use cases
 
 * Validation and transformation of received data for your API.
+* Conversion between data models and DTOs.
 * Config loading/dumping via codec that produces/takes dict.
 * Storing JSON in a database and representing it as a model inside the application code.
 * Creating API clients that convert a model to JSON sending to the server.
-* Persisting entities in cache storage.
+* Persisting entities at cache storage.
 * Implementing fast and primitive ORM.
 
 ## Advantages
@@ -74,7 +114,8 @@ assert retort.dump(book) == data
 * There is no forced model representation, adaptix can adjust to your needs.
 * Support [dozens](https://adaptix.readthedocs.io/en/latest/loading-and-dumping/specific-types-behavior.html) of types,
   including different model kinds:
-  ``@dataclass``, ``TypedDict``, ``NamedTuple``, and [``attrs``](https://www.attrs.org/en/stable/)
+  ``@dataclass``, ``TypedDict``, ``NamedTuple``,
+  [``attrs``](https://www.attrs.org/en/stable/) and [``sqlalchemy``](https://docs.sqlalchemy.org/en/20/)
 * Working with self-referenced data types (such as linked lists or trees).
 * Saving [path](https://adaptix.readthedocs.io/en/latest/loading-and-dumping/tutorial.html#error-handling)
   where an exception is raised (including unexpected errors).
diff --git a/docs/changelog/changelog_body.rst b/docs/changelog/changelog_body.rst
index a9381fb1..ea221951 100644
--- a/docs/changelog/changelog_body.rst
+++ b/docs/changelog/changelog_body.rst
@@ -1,6 +1,81 @@
 ----------------------------------------------------
 
 
+.. _v3.0.0b2:
+
+`3.0.0b2 <https://github.com/reagento/adaptix/tree/v3.0.0b2>`_ -- 2024-02-16
+============================================================================
+
+.. _v3.0.0b2-Features:
+
+Features
+--------
+
+- New **major** feature is out!
+  Added support for model conversion!
+  Now, you can generate boilerplate converter function by adaptix.
+  See :ref:`conversion tutorial <conversion-tutorial>` for details.
+- Basic support for sqlalchemy models are added!
+- Added enum support inside Literal. `#178 <https://github.com/reagento/adaptix/issues/178>`_
+- Added flags support.
+
+  Now adaptix has two different ways to process flags: :func:`.flag_by_exact_value` (by default)
+  and :func:`.flag_by_member_names`. `#197 <https://github.com/reagento/adaptix/issues/197>`_
+- Added defaultdict support. `#216 <https://github.com/reagento/adaptix/issues/216>`_
+- Added support of mapping for :func:`.enum_by_name` provider. `#223 <https://github.com/reagento/adaptix/issues/223>`_
+- Created the correct path (fixing python bug) for processing ``Required`` and ``NotRequired`` with stringified annotations
+  or ``from __future__ import annotations``. `#227 <https://github.com/reagento/adaptix/issues/227>`_
+
+.. _v3.0.0b2-Breaking Changes:
+
+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``
+  3. method ``RequestPattern.build_request_checker()`` was renamed to ``LocStackPattern.build_loc_stack_checker()``
+
+.. _v3.0.0b2-Deprecations:
+
+Deprecations
+------------
+
+- Standardize names inside :mod:`adaptix.load_error`. Import of old names will emit ``DeprecationWarning``.
+
+  .. list-table::
+     :header-rows: 1
+
+     * - Old name
+       - New name
+     * - ``MsgError``
+       - ``MsgLoadError``
+     * - ``ExtraFieldsError``
+       - ``ExtraFieldsLoadError``
+     * - ``ExtraItemsError``
+       - ``ExtraItemsLoadError``
+     * - ``NoRequiredFieldsError``
+       - ``NoRequiredFieldsLoadError``
+     * - ``NoRequiredItemsError``
+       - ``NoRequiredItemsLoadError``
+     * - ``ValidationError``
+       - ``ValidationLoadError``
+     * - ``BadVariantError``
+       - ``BadVariantLoadError``
+     * - ``DatetimeFormatMismatch``
+       - ``FormatMismatchLoadError``
+
+.. _v3.0.0b2-Bug Fixes:
+
+Bug Fixes
+---------
+
+- Fixed parameter shuffling on skipping optional field. `#229 <https://github.com/reagento/adaptix/issues/229>`_
+
+----------------------------------------------------
+
+
 .. _v3.0.0b1:
 
 `3.0.0b1 <https://github.com/reagento/adaptix/tree/v3.0.0b1>`_ -- 2023-12-16
diff --git a/docs/changelog/fragments/README.rst b/docs/changelog/fragments/README.rst
index 34e251fd..926977af 100644
--- a/docs/changelog/fragments/README.rst
+++ b/docs/changelog/fragments/README.rst
@@ -1,7 +1,7 @@
 This directory contains "news fragments" which are short files that contain a small **ReST**-formatted
 text that will be added to the next ``CHANGELOG``.
 
-The ``CHANGELOG`` will be read by **users**, so this description should be aimed to pytest users
+The ``CHANGELOG`` will be read by **users**, so this description should be aimed to users
 instead of describing internal changes which are only relevant to the developers.
 
 Make sure to use full sentences in the **past or present tense** and use punctuation, examples::
diff --git a/docs/changelog/template.rst.jinja2 b/docs/changelog/template.rst.jinja2
index d09721f7..1382d121 100644
--- a/docs/changelog/template.rst.jinja2
+++ b/docs/changelog/template.rst.jinja2
@@ -22,7 +22,7 @@
 
 {% if definitions[category]['showcontent'] %}
 {% for text, values in sections[section][category].items() %}
-- {{ text }}{% if values %} ({{ values|join(', ') }}){% endif %}
+- {{ text }}{% if values %} {{ values|join(', ') }}{% endif %}
 
 {% endfor %}
 {% else %}
diff --git a/docs/common/installation.rst b/docs/common/installation.rst
new file mode 100644
index 00000000..d2e330ad
--- /dev/null
+++ b/docs/common/installation.rst
@@ -0,0 +1,37 @@
+Just use pip to install the library
+
+.. code-block:: text
+
+    pip install adaptix==3.0.0b2
+
+
+Integrations with 3-rd party libraries are turned on automatically,
+but you can install adaptix with `extras <https://packaging.python.org/en/latest/tutorials/installing-packages/#installing-extras>`_
+to check that versions are compatible.
+
+There are two variants of extras. The first one checks that the version is the same or newer than the last supported,
+the second (strict) additionally checks that the version same or older than the last tested version.
+
+.. list-table::
+   :header-rows: 1
+
+   * - Extras
+     - Versions bound
+   * - ``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``
+
+
+Extras are specified inside square brackets, separating by comma.
+
+So, this is valid installation variants:
+
+.. code-block:: text
+
+   pip install adaptix[attrs-strict]==3.0.0b2
+   pip install adaptix[attrs, sqlalchemy-strict]==3.0.0b2
diff --git a/docs/conf.py b/docs/conf.py
index 0918d2de..863a2a37 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -42,7 +42,6 @@
 extensions = [
     'sphinx_copybutton',
     'sphinx_design',
-    'sphinx.ext.autosectionlabel',
     'sphinx.ext.extlinks',
     'sphinx.ext.autodoc',
     'sphinx.ext.intersphinx',
@@ -106,7 +105,6 @@
 add_function_parentheses = False
 
 repo = git.Repo(search_parent_directories=True)
-sha = repo.head.object.hexsha
 benchmark_data_submodule = next(submodule for submodule in repo.submodules if submodule.name == 'benchmark-data')
 
 extlinks = {
diff --git a/docs/conversion/tutorial.rst b/docs/conversion/tutorial.rst
new file mode 100644
index 00000000..081c3dea
--- /dev/null
+++ b/docs/conversion/tutorial.rst
@@ -0,0 +1,123 @@
+.. _conversion-tutorial:
+
+***********
+Tutorial
+***********
+
+Installation
+=============
+
+.. include:: /common/installation.rst
+
+Introduction
+====================
+
+Building an easily maintainable application requires you to split the code into layers.
+Data between layers should be passed using special data structures.
+It requires creating many converter functions transforming one model into another.
+
+Adaptix helps you avoid writing boilerplate code by generating conversion functions for you.
+
+.. literalinclude:: /examples/conversion/tutorial/tldr.py
+
+The actual signature of ``convert_book_to_dto`` is automatically derived by any type checker and any IDE.
+
+Adaptix can transform between any of the supported models, see :ref:`supported-model-kinds`
+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.
+
+Also, it works for nested models.
+
+.. literalinclude:: /examples/conversion/tutorial/nested.py
+
+Furthermore, there is :func:`.conversion.convert` that can directly convert one model to another,
+but it is quite limited and can not configured, so it won't be considered onwards.
+
+.. dropdown:: Usage of :func:`.conversion.convert`
+
+  .. literalinclude:: /examples/conversion/tutorial/convert_function.py
+
+
+Downcasting
+=============
+
+All source model additional fields not found in the destination model are simply ignored.
+
+.. literalinclude:: /examples/conversion/tutorial/downcasting.py
+
+Upcasting
+=============
+
+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
+
+:func:`.conversion.impl_converter` takes an empty function and generates its body by signature.
+
+``# mypy: disable-error-code="empty-body"`` on the top of the file is needed
+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
+================
+
+If the names of the fields are different, then you have to link them manually.
+
+.. literalinclude:: /examples/conversion/tutorial/fields_linking.py
+
+The first parameter of :func:`.conversion.link` is the predicate describing the field of the source model,
+the second parameter is the pointing to the field of the destination model.
+
+This notation means that the field ``author`` of class ``Book``
+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.
+
+Linking algorithm
+===================
+
+The building of the converter is based on a need to construct the destination model.
+
+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.
+
+After fields are matched adaptix tries to create a coercer
+that transforms data from the source field to the destination type.
+
+Type coercion
+================
+
+By default, there are no implicit coercions.
+
+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)
+
+You can define your own coercion rule.
+
+.. literalinclude:: /examples/conversion/tutorial/type_coercion.py
+
+The first parameter of :func:`.conversion.coercer` is the predicate describing the field of the source model,
+the second parameter is the pointing to the field of the destination model,
+the third parameter is the function that casts source data to the destination type.
+
+Usually, only field types are used as predicates here.
+
+
+Putting together
+===================
+
+Let's explore complex example collecting all features together.
+
+.. literalinclude:: /examples/conversion/tutorial/putting_together.py
diff --git a/docs/examples/conversion/tutorial/__init__.py b/docs/examples/conversion/tutorial/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/examples/conversion/tutorial/convert_function.py b/docs/examples/conversion/tutorial/convert_function.py
new file mode 100644
index 00000000..702330dd
--- /dev/null
+++ b/docs/examples/conversion/tutorial/convert_function.py
@@ -0,0 +1,37 @@
+from dataclasses import dataclass
+
+from adaptix.conversion import convert
+
+
+@dataclass
+class Person:
+    name: str
+
+
+@dataclass
+class Book:
+    title: str
+    price: int
+    author: Person
+
+
+@dataclass
+class PersonDTO:
+    name: str
+
+
+@dataclass
+class BookDTO:
+    title: str
+    price: int
+    author: PersonDTO
+
+
+assert (
+    convert(
+        Book(title="Fahrenheit 451", price=100, author=Person("Ray Bradbury")),
+        BookDTO,
+    )
+    ==
+    BookDTO(title="Fahrenheit 451", price=100, author=PersonDTO("Ray Bradbury"))
+)
diff --git a/docs/examples/conversion/tutorial/downcasting.py b/docs/examples/conversion/tutorial/downcasting.py
new file mode 100644
index 00000000..9a0dc2f8
--- /dev/null
+++ b/docs/examples/conversion/tutorial/downcasting.py
@@ -0,0 +1,43 @@
+from dataclasses import dataclass
+from datetime import date
+
+from adaptix.conversion import get_converter
+
+
+@dataclass
+class Book:
+    title: str
+    price: int
+    author: str
+    release_date: date
+    page_count: int
+    isbn: str
+
+
+@dataclass
+class BookDTO:
+    title: str
+    price: int
+    author: str
+
+
+convert_book_to_dto = get_converter(Book, BookDTO)
+
+assert (
+    convert_book_to_dto(
+        Book(
+            title="Fahrenheit 451",
+            price=100,
+            author="Ray Bradbury",
+            release_date=date(1953, 10, 19),
+            page_count=158,
+            isbn='978-0-7432-4722-1',
+        )
+    )
+    ==
+    BookDTO(
+        title="Fahrenheit 451",
+        price=100,
+        author="Ray Bradbury",
+    )
+)
diff --git a/docs/examples/conversion/tutorial/fields_linking.py b/docs/examples/conversion/tutorial/fields_linking.py
new file mode 100644
index 00000000..bfd43814
--- /dev/null
+++ b/docs/examples/conversion/tutorial/fields_linking.py
@@ -0,0 +1,31 @@
+from dataclasses import dataclass
+
+from adaptix import P
+from adaptix.conversion import get_converter, link
+
+
+@dataclass
+class Book:
+    name: str
+    price: int
+    author: str  # same as BookDTO.writer
+
+
+@dataclass
+class BookDTO:
+    name: str
+    price: int
+    writer: str  # same as Book.author
+
+
+convert_book_to_dto = get_converter(
+    src=Book,
+    dst=BookDTO,
+    recipe=[link(P[Book].author, P[BookDTO].writer)],
+)
+
+assert (
+    convert_book_to_dto(Book(name="Fahrenheit 451", price=100, author="Ray Bradbury"))
+    ==
+    BookDTO(name="Fahrenheit 451", price=100, writer="Ray Bradbury")
+)
diff --git a/docs/examples/conversion/tutorial/nested.py b/docs/examples/conversion/tutorial/nested.py
new file mode 100644
index 00000000..df5289c8
--- /dev/null
+++ b/docs/examples/conversion/tutorial/nested.py
@@ -0,0 +1,38 @@
+from dataclasses import dataclass
+
+from adaptix.conversion import get_converter
+
+
+@dataclass
+class Person:
+    name: str
+
+
+@dataclass
+class Book:
+    title: str
+    price: int
+    author: Person
+
+
+@dataclass
+class PersonDTO:
+    name: str
+
+
+@dataclass
+class BookDTO:
+    title: str
+    price: int
+    author: PersonDTO
+
+
+convert_book_to_dto = get_converter(Book, BookDTO)
+
+assert (
+    convert_book_to_dto(
+        Book(title="Fahrenheit 451", price=100, author=Person("Ray Bradbury"))
+    )
+    ==
+    BookDTO(title="Fahrenheit 451", price=100, author=PersonDTO("Ray Bradbury"))
+)
diff --git a/docs/examples/conversion/tutorial/putting_together.py b/docs/examples/conversion/tutorial/putting_together.py
new file mode 100644
index 00000000..aefa2543
--- /dev/null
+++ b/docs/examples/conversion/tutorial/putting_together.py
@@ -0,0 +1,72 @@
+# mypy: disable-error-code="empty-body"
+from dataclasses import dataclass
+from datetime import date
+from uuid import UUID
+
+from adaptix import P
+from adaptix.conversion import coercer, impl_converter, link
+
+
+@dataclass
+class Author:
+    name: str
+    surname: str
+    birthday: date  # is converted to str
+
+
+@dataclass
+class Book:
+    id: UUID  # is converted to str
+    title: str
+    author: Author  # is renamed to `writer`
+    isbn: str  # this field is ignored
+
+
+@dataclass
+class AuthorDTO:
+    name: str
+    surname: str
+    birthday: str
+
+
+@dataclass
+class BookDTO:
+    id: str
+    title: str
+    writer: AuthorDTO
+    page_count: int  # is taken from `pages_len` param
+    rating: float  # is taken from param with the same name
+
+
+@impl_converter(
+    recipe=[
+        link('pages_len', 'page_count'),
+        link(P[Book].author, P[BookDTO].writer),
+        coercer(UUID, str, func=str),
+        coercer(P[Author].birthday, P[AuthorDTO].birthday, date.isoformat),
+    ]
+)
+def convert_book_to_dto(book: Book, pages_len: int, rating: float) -> BookDTO:
+    ...
+
+
+assert (
+    convert_book_to_dto(
+        book=Book(
+            id=UUID('87000388-94e6-49a4-b51b-320e38577bd9'),
+            isbn='978-0-7432-4722-1',
+            title="Fahrenheit 451",
+            author=Author(name="Ray", surname="Bradbury", birthday=date(1920, 7, 22)),
+        ),
+        pages_len=158,
+        rating=4.8,
+    )
+    ==
+    BookDTO(
+        id='87000388-94e6-49a4-b51b-320e38577bd9',
+        title="Fahrenheit 451",
+        writer=AuthorDTO(name="Ray", surname="Bradbury", birthday='1920-07-22'),
+        page_count=158,
+        rating=4.8,
+    )
+)
diff --git a/docs/examples/conversion/tutorial/tldr.py b/docs/examples/conversion/tutorial/tldr.py
new file mode 100644
index 00000000..9e6e279c
--- /dev/null
+++ b/docs/examples/conversion/tutorial/tldr.py
@@ -0,0 +1,33 @@
+from dataclasses import dataclass
+
+from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
+
+from adaptix.conversion import get_converter
+
+
+class Base(DeclarativeBase):
+    pass
+
+
+class Book(Base):
+    __tablename__ = 'books'
+
+    id: Mapped[int] = mapped_column(primary_key=True)
+    title: Mapped[str]
+    price: Mapped[int]
+
+
+@dataclass
+class BookDTO:
+    id: int
+    title: str
+    price: int
+
+
+convert_book_to_dto = get_converter(Book, BookDTO)
+
+assert (
+    convert_book_to_dto(Book(id=183, title="Fahrenheit 451", price=100))
+    ==
+    BookDTO(id=183, title="Fahrenheit 451", price=100)
+)
diff --git a/docs/examples/conversion/tutorial/type_coercion.py b/docs/examples/conversion/tutorial/type_coercion.py
new file mode 100644
index 00000000..3de0ccae
--- /dev/null
+++ b/docs/examples/conversion/tutorial/type_coercion.py
@@ -0,0 +1,41 @@
+from dataclasses import dataclass
+from uuid import UUID
+
+from adaptix.conversion import coercer, get_converter
+
+
+@dataclass
+class Book:
+    id: UUID
+    title: str
+    author: str
+
+
+@dataclass
+class BookDTO:
+    id: str
+    title: str
+    author: str
+
+
+convert_book_to_dto = get_converter(
+    src=Book,
+    dst=BookDTO,
+    recipe=[coercer(UUID, str, func=str)],
+)
+
+assert (
+    convert_book_to_dto(
+        Book(
+            id=UUID('87000388-94e6-49a4-b51b-320e38577bd9'),
+            title="Fahrenheit 451",
+            author="Ray Bradbury",
+        )
+    )
+    ==
+    BookDTO(
+        id='87000388-94e6-49a4-b51b-320e38577bd9',
+        title="Fahrenheit 451",
+        author="Ray Bradbury",
+    )
+)
diff --git a/docs/examples/conversion/tutorial/upcasting.py b/docs/examples/conversion/tutorial/upcasting.py
new file mode 100644
index 00000000..fe0becc5
--- /dev/null
+++ b/docs/examples/conversion/tutorial/upcasting.py
@@ -0,0 +1,43 @@
+# mypy: disable-error-code="empty-body"
+from dataclasses import dataclass
+
+from adaptix.conversion import impl_converter
+
+
+@dataclass
+class Book:
+    title: str
+    price: int
+    author: str
+
+
+@dataclass
+class BookDTO:
+    title: str
+    price: int
+    author: str
+    page_count: int
+
+
+@impl_converter
+def convert_book_to_dto(book: Book, page_count: int) -> BookDTO:
+    ...
+
+
+assert (
+    convert_book_to_dto(
+        book=Book(
+            title="Fahrenheit 451",
+            price=100,
+            author="Ray Bradbury",
+        ),
+        page_count=158,
+    )
+    ==
+    BookDTO(
+        title="Fahrenheit 451",
+        price=100,
+        author="Ray Bradbury",
+        page_count=158,
+    )
+)
diff --git a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_extra_forbid.py b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_extra_forbid.py
index 5057ef79..2613d5e7 100644
--- a/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_extra_forbid.py
+++ b/docs/examples/loading-and-dumping/extended_usage/unknown_fields_processing/on_loading_extra_forbid.py
@@ -1,7 +1,7 @@
 from dataclasses import dataclass
 
 from adaptix import ExtraForbid, Retort, name_mapping
-from adaptix.load_error import AggregateLoadError, ExtraFieldsError
+from adaptix.load_error import AggregateLoadError, ExtraFieldsLoadError
 
 
 @dataclass
@@ -27,5 +27,5 @@ class Book:
     retort.load(data, Book)
 except AggregateLoadError as e:
     assert len(e.exceptions) == 1
-    assert isinstance(e.exceptions[0], ExtraFieldsError)
+    assert isinstance(e.exceptions[0], ExtraFieldsLoadError)
     assert set(e.exceptions[0].fields) == {"unknown1", "unknown2"}
diff --git a/docs/examples/loading-and-dumping/tutorial/validators.py b/docs/examples/loading-and-dumping/tutorial/validators.py
index 3dbd0f3c..045bbd2c 100644
--- a/docs/examples/loading-and-dumping/tutorial/validators.py
+++ b/docs/examples/loading-and-dumping/tutorial/validators.py
@@ -1,7 +1,7 @@
 from dataclasses import dataclass
 
 from adaptix import P, Retort, validator
-from adaptix.load_error import AggregateLoadError, LoadError, ValidationError
+from adaptix.load_error import AggregateLoadError, LoadError, ValidationLoadError
 
 
 @dataclass
@@ -25,7 +25,7 @@ class Book:
     retort.load(data, Book)
 except AggregateLoadError as e:
     assert len(e.exceptions) == 1
-    assert isinstance(e.exceptions[0], ValidationError)
+    assert isinstance(e.exceptions[0], ValidationLoadError)
     assert e.exceptions[0].msg == "value must be greater or equal 0"
 
 
diff --git a/docs/index.rst b/docs/index.rst
index b7ae4055..4a570d43 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -16,10 +16,18 @@ Adaptix
    loading-and-dumping/extended-usage
    loading-and-dumping/specific-types-behavior
 
+.. toctree::
+   :maxdepth: 2
+   :caption: Conversion:
+
+   conversion/tutorial
+
+
 .. toctree::
    :maxdepth: 2
    :caption: Reference:
 
+   reference/integrations
    reference/examples
    API <reference/api/modules>
    reference/changelog
@@ -31,3 +39,4 @@ Adaptix
 
    GitHub <https://github.com/reagento/adaptix>
    PyPI <https://pypi.org/project/adaptix>
+   Chat <https://t.me/reagento_ru>
diff --git a/docs/loading-and-dumping/specific-types-behavior.rst b/docs/loading-and-dumping/specific-types-behavior.rst
index 61598791..f74fea5b 100644
--- a/docs/loading-and-dumping/specific-types-behavior.rst
+++ b/docs/loading-and-dumping/specific-types-behavior.rst
@@ -112,7 +112,18 @@ timedelta
 Loader accepts instance of ``int``, ``float`` or ``Decimal`` representing seconds,
 dumper serialize value via ``total_seconds`` method.
 
-Enum subclasses
+
+Flag subclasses
+'''''''''''''''''''''''
+
+Flag members by default are represented by their value. Note that flags with skipped
+bits and negative values are not supported, so it is highly recommended to define flag
+values via ``enum.auto()`` instead of manually specifying them.
+Besides, adaptix provides another way to process flags: by list using their names.
+See :func:`.flag_by_member_names` for details.
+
+
+Other Enum subclasses
 '''''''''''''''''''''''
 
 Enum members are represented by their value without any conversion.
@@ -148,7 +159,7 @@ Literal
 Loader accepts only values listed in ``Literal``.
 If ``strict_coercion`` is enabled, the loader will distinguish equal ``bool`` and ``int`` instances,
 otherwise, they will be considered as same values.
-``Enum`` instances will be loaded via its loaders.
+``Enum`` instances will be loaded via its loaders. Enum loaders have a higher priority over others, that is, they will be applied first.
 
 If the input value could be interpreted as several ``Literal`` members, the result will be undefined.
 
@@ -216,6 +227,11 @@ Dict and Mapping
 Loader accepts any other ``Mapping`` and makes ``dict`` instances.
 Dumper also constructs dict with converted keys and values.
 
+DefaultDict
+'''''''''''''''''''''
+Loader makes instances of ``defaultdict`` with the ``default_factory`` parameter set to ``None``.
+To customize this behavior, there are factory :func:`.default_dict` that have :paramref:`.default_dict.default_factory` parameter that can be overridden.
+
 Models
 ''''''''''
 
@@ -226,26 +242,4 @@ Also, the model could be loaded from the list.
 
 Dumper works similarly and produces dict (or list).
 
-Models that are supported out of the box:
-
-- `dataclass <https://docs.python.org/3/library/dataclasses>`_
-- `NamedTuple <https://docs.python.org/3/library/typing.html#typing.NamedTuple>`_
-  (`namedtuple <https://docs.python.org/3/library/collections.html#collections.namedtuple>`_
-  also is supported, but types of all fields will be ``Any``)
-- `TypedDict <https://docs.python.org/3/library/typing.html#typing.TypedDict>`_
-- `attrs <https://www.attrs.org/en/stable/>`_ (``>=21.3.0``)
-
-Arbitrary types also are supported to be loaded by introspection of ``__init__`` method,
-but it can not be dumped.
-
-Known limitations:
-
-- dataclass
-
-  - Signature of custom ``__init__`` method must be same as signature of generated by ``@dataclass``,
-    because there is no way to distinguish them.
-
-
-- ``__init__`` introspection or using :func:`.constructor`
-
-  - Fields of unpacked typed dict (``**kwargs: Unpack[YourTypedDict]``) cannot collide with parameters of function
+See :ref:`supported-model-kinds` for exact list of supported model.
diff --git a/docs/loading-and-dumping/tutorial.rst b/docs/loading-and-dumping/tutorial.rst
index 24166b5f..412b00ab 100644
--- a/docs/loading-and-dumping/tutorial.rst
+++ b/docs/loading-and-dumping/tutorial.rst
@@ -1,37 +1,16 @@
+.. _loading-and-dumping-tutorial:
+
 ***********
 Tutorial
 ***********
 
-Adaptix analyzes your type hints and generates corresponding converters based on the retrieved information.
+Adaptix analyzes your type hints and generates corresponding transformers based on the retrieved information.
 You can flexibly tune the conversion process following DRY principle.
 
 Installation
 =============
 
-Just use pip to install the library
-
-.. code-block:: text
-
-    pip install adaptix==3.0.0b1
-
-
-Integrations with 3-rd party libraries are turned on automatically,
-but you can install adaptix with `extras <https://packaging.python.org/en/latest/tutorials/installing-packages/#installing-extras>`_
-to check that versions are compatible.
-
-There are two variants of extras. The first one checks that the version is the same or newer than the last supported,
-the second (strict) additionally checks that the version same or older than the last tested version.
-
-.. list-table::
-   :header-rows: 1
-
-   * - Extras
-     - Versions bound
-   * - ``attrs``
-     - ``attrs >= 21.3.0``
-   * - ``attrs-strict``
-     - ``attrs >= 21.3.0, <= 23.1.0``
-
+.. include:: /common/installation.rst
 
 Introduction
 ====================
diff --git a/docs/overview.rst b/docs/overview.rst
index 3b462381..8eabe5e0 100644
--- a/docs/overview.rst
+++ b/docs/overview.rst
@@ -16,7 +16,7 @@ Installation
 
 .. code-block:: text
 
-    pip install adaptix==3.0.0b1
+    pip install adaptix==3.0.0b2
 
 
 Example
@@ -24,6 +24,12 @@ Example
 
 .. literalinclude:: /examples/loading-and-dumping/tutorial/tldr.py
    :lines: 2-
+   :caption: Model loading and dumping
+   :name: loading-and-dumping-example
+
+.. literalinclude:: /examples/conversion/tutorial/tldr.py
+   :caption: Conversion one model to another
+   :name: conversion-example
 
 Requirements
 ==================
@@ -35,6 +41,7 @@ Use cases
 ==================
 
 * Validation and transformation of received data for your API.
+* Conversion between data models and DTOs.
 * Config loading/dumping via codec that produces/takes dict.
 * Storing JSON in a database and representing it as a model inside the application code.
 * Creating API clients that convert a model to JSON sending to the server.
@@ -52,4 +59,5 @@ Advantages
 Further reading
 ==================
 
-See :ref:`Tutorial` for details about library usage.
+See :ref:`loading and dumping tutorial <loading-and-dumping-tutorial>`
+and :ref:`conversion tutorial <conversion-tutorial>` for details about library usage.
diff --git a/docs/readme_advantages.md b/docs/readme_advantages.md
index 959d9358..da0ab711 100644
--- a/docs/readme_advantages.md
+++ b/docs/readme_advantages.md
@@ -6,7 +6,8 @@
 * There is no forced model representation, adaptix can adjust to your needs.
 * Support [dozens](https://adaptix.readthedocs.io/en/latest/loading-and-dumping/specific-types-behavior.html) of types,
   including different model kinds:
-  ``@dataclass``, ``TypedDict``, ``NamedTuple``, and [``attrs``](https://www.attrs.org/en/stable/)
+  ``@dataclass``, ``TypedDict``, ``NamedTuple``,
+  [``attrs``](https://www.attrs.org/en/stable/) and [``sqlalchemy``](https://docs.sqlalchemy.org/en/20/)
 * Working with self-referenced data types (such as linked lists or trees).
 * Saving [path](https://adaptix.readthedocs.io/en/latest/loading-and-dumping/tutorial.html#error-handling)
   where an exception is raised (including unexpected errors).
diff --git a/docs/reference/integrations.rst b/docs/reference/integrations.rst
new file mode 100644
index 00000000..d7708fc9
--- /dev/null
+++ b/docs/reference/integrations.rst
@@ -0,0 +1,64 @@
+*******************
+Integrations
+*******************
+
+This article describes how adaptix is workings with other packages and systems.
+
+.. _supported-model-kinds:
+
+Supported model kinds
+=======================
+
+Models are classes that have a predefined set of fields. Adaptix process models in the same, consistent way.
+
+Models that are supported out of the box:
+
+- `dataclass <https://docs.python.org/3/library/dataclasses>`_
+- `NamedTuple <https://docs.python.org/3/library/typing.html#typing.NamedTuple>`_
+  (`namedtuple <https://docs.python.org/3/library/collections.html#collections.namedtuple>`_
+  also is supported, but types of all fields will be ``Any``)
+- `TypedDict <https://docs.python.org/3/library/typing.html#typing.TypedDict>`_
+- `attrs <https://www.attrs.org/en/stable/>`_ (only from ``>=21.3.0``)
+- `sqlalchemy <https://docs.sqlalchemy.org/en/20/>`_ (only from ``>=2.0.0``)
+
+Arbitrary types also are supported to be loaded by introspection of ``__init__`` method,
+but it can not be dumped.
+
+You do not need to do anything to enable support for models from a third-party library.
+Everything just works. But you can install adaptix with certain `extras <https://packaging.python.org/en/latest/tutorials/installing-packages/#installing-extras>`_
+to ensure version compatibility.
+
+
+Known limitations:
+
+- dataclass
+
+  - Signature of custom ``__init__`` method must be same as signature of generated by ``@dataclass``,
+    because there is no way to distinguish them.
+
+
+- ``__init__`` introspection or using :func:`.constructor`
+
+  - Fields of unpacked typed dict (``**kwargs: Unpack[YourTypedDict]``) cannot collide with parameters of function.
+
+- sqlalchemy
+
+  - 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.
+
+  - It does not support registering order of mapped fields by design,
+    so you should use manual mapping to list instead automatic ``as_list=True``.
+
+  - Relationships with custom ``collection_class`` are not supported.
+
+  - All input fields of foreign keys and relationships are considered as optional
+    due to user can pass only relationship instance or only foreign key value.
+
+
+Due to the way Python works with annotations, there is a `bug <https://github.com/python/cpython/issues/97727>`_,
+when field annotation of ``TypedDict`` is stringified or ``from __future__ import annotations`` is placed
+in file ``Required`` and ``NotRequired`` specifiers is ignored
+when ``required_keys`` and ``optional_keys`` is calculated.
+Adaptix takes this into account and processes it properly.
diff --git a/examples/api_division/retort.py b/examples/api_division/retort.py
index c5ef2f02..19cbdad9 100644
--- a/examples/api_division/retort.py
+++ b/examples/api_division/retort.py
@@ -5,7 +5,7 @@
 from phonenumbers import PhoneNumber
 
 from adaptix import Chain, ExtraForbid, ExtraSkip, P, Retort, dumper, enum_by_name, loader, name_mapping, validator
-from adaptix.load_error import ExtraFieldsError, ValueLoadError
+from adaptix.load_error import ExtraFieldsLoadError, ValueLoadError
 
 from .models import Receipt, ReceiptType, RecItem
 from .money import Money, TooPreciseAmount
@@ -51,7 +51,7 @@ def money_loader(data):
 
 def forbid_version_key(data):
     if isinstance(data, dict) and 'version' in data:
-        raise ExtraFieldsError(['version'], data)
+        raise ExtraFieldsLoadError(['version'], data)
     return data
 
 
diff --git a/justfile b/justfile
index a31b9419..14b6d7a6 100644
--- a/justfile
+++ b/justfile
@@ -42,11 +42,11 @@ set windows-powershell := true
 @test-on target:
     tox -e $(tox list --no-desc | grep '^{{ target }}' | sort -r | tr '\n' ',')
 
-@cov:
-    inv cov
-
 inv := "inv -r scripts -c invoke_tasks"
 
+@cov:
+    {{ inv }} cov
+
 @deps-compile:
     {{ inv }} deps-compile
 
diff --git a/pyproject.toml b/pyproject.toml
index c6c937d3..6d99761c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,15 +1,16 @@
 [build-system]
-requires = ['setuptools==69.0.2']
+requires = ['setuptools==69.1.0']
 build-backend = 'setuptools.build_meta'
 
 [project]
 name = 'adaptix'
-version = '3.0.0b1'
+version = '3.0.0b2'
 description = 'An extremely flexible and configurable data model conversion library'
 readme = 'README.md'
 requires-python = '>=3.8'
 dependencies = [
     'exceptiongroup>=1.1.3; python_version<"3.11"',
+    'astunparse>=1.6.3; python_version<="3.8"',
 ]
 
 classifiers = [
@@ -34,14 +35,17 @@ classifiers = [
 
 [project.optional-dependencies]
 attrs = ['attrs >= 21.3.0']
-attrs-strict = ['attrs >= 21.3.0, <= 23.1.0']
+attrs-strict = ['attrs >= 21.3.0, <= 23.2.0']
+sqlalchemy = ['sqlalchemy >= 2.0.0']
+sqlalchemy-strict = ['sqlalchemy >= 2.0.0, <= 2.0.27']
 
 [project.urls]
-'Homepage' = 'https://github.com/reagento/dataclass_factory'
-'Bug Tracker' = 'https://github.com/reagento/dataclass_factory/issues'
+'Homepage' = 'https://github.com/reagento/adaptix'
+'Bug Tracker' = 'https://github.com/reagento/adaptix/issues'
 'Documentation' = 'https://adaptix.readthedocs.io/en/latest/'
-'Source' = 'https://github.com/reagento/dataclass_factory/'
-'Download' = 'https://github.com/reagento/dataclass_factory/#files'
+'Changelog' = 'https://adaptix.readthedocs.io/en/latest/reference/changelog.html'
+'Source' = 'https://github.com/reagento/adaptix/'
+'Download' = 'https://github.com/reagento/adaptix/#files'
 
 [tool.setuptools]
 package-dir = {"" = "src"}
@@ -86,9 +90,9 @@ issue_format = '`#{issue} <https://github.com/reagento/adaptix/issues/{issue}>`_
 directory = 'docs/changelog/fragments'
 
 type = [
+  { name = "Features",         directory = "feature",     showcontent = true },
   { name = "Breaking Changes", directory = "breaking",    showcontent = true },
   { name = "Deprecations",     directory = "deprecation", showcontent = true },
-  { name = "Features",         directory = "feature",     showcontent = true },
   { name = "Bug Fixes",        directory = "bugfix",      showcontent = true },
   { name = "Other",            directory = "other",       showcontent = true },
 ]
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 598bed34..29d90a04 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -18,7 +18,7 @@ astpath==0.9.1
     #   astpath
 astroid==3.0.2
     # via pylint
-attrs==23.1.0
+attrs==23.2.0
     # via
     #   -r requirements/raw/test_extra_new.txt
     #   cattrs
@@ -323,8 +323,10 @@ sphinxcontrib-serializinghtml==1.1.9
     # via sphinx
 sphinxext-opengraph==0.9.1
     # via -r requirements/raw/doc.txt
-sqlalchemy==2.0.23
-    # via -r requirements/raw/test_extra_none.txt
+sqlalchemy==2.0.27
+    # 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
diff --git a/requirements/lint.txt b/requirements/lint.txt
index 9bd18394..adc475da 100644
--- a/requirements/lint.txt
+++ b/requirements/lint.txt
@@ -18,7 +18,7 @@ astpath==0.9.1
     #   astpath
 astroid==3.0.2
     # via pylint
-attrs==23.1.0
+attrs==23.2.0
     # via
     #   -r requirements/raw/test_extra_new.txt
     #   cattrs
@@ -290,8 +290,10 @@ sphinxcontrib-serializinghtml==1.1.9
     # via sphinx
 sphinxext-opengraph==0.9.1
     # via -r requirements/raw/doc.txt
-sqlalchemy==2.0.23
-    # via -r requirements/raw/test_extra_none.txt
+sqlalchemy==2.0.27
+    # 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
diff --git a/src/adaptix/__init__.py b/src/adaptix/__init__.py
index 5fa3feda..3cad9c53 100644
--- a/src/adaptix/__init__.py
+++ b/src/adaptix/__init__.py
@@ -1,17 +1,18 @@
-from adaptix._internal.definitions import DebugTrail
-
 from ._internal.common import Dumper, Loader, TypeHint
+from ._internal.definitions import DebugTrail
 from ._internal.model_tools.introspection.typed_dict import TypedDictAt38Warning
 from ._internal.morphing.facade.func import dump, load
 from ._internal.morphing.facade.provider import (
     as_is_dumper,
     as_is_loader,
-    bound,
     constructor,
+    default_dict,
     dumper,
     enum_by_exact_value,
     enum_by_name,
     enum_by_value,
+    flag_by_exact_value,
+    flag_by_member_names,
     loader,
     name_mapping,
     validator,
@@ -28,17 +29,18 @@
 )
 from ._internal.morphing.name_layout.base import ExtraIn, ExtraOut
 from ._internal.name_style import NameStyle
+from ._internal.provider.facade.provider import bound
 from ._internal.utils import Omittable, Omitted
 from .provider import (
     AggregateCannotProvide,
     CannotProvide,
     Chain,
+    LocStackPattern,
     Mediator,
     P,
     Provider,
     Request,
-    RequestPattern,
-    create_request_checker,
+    create_loc_stack_checker,
 )
 from .retort import NoSuitableProvider
 
@@ -58,7 +60,10 @@
     'enum_by_exact_value',
     'enum_by_name',
     'enum_by_value',
+    'flag_by_exact_value',
+    'flag_by_member_names',
     'name_mapping',
+    'default_dict',
     'AdornedRetort',
     'FilledRetort',
     'Retort',
@@ -78,10 +83,10 @@
     'ExtraSkip',
     'Mediator',
     'NameStyle',
-    'RequestPattern',
+    'LocStackPattern',
     'P',
     'Saturator',
-    'create_request_checker',
+    'create_loc_stack_checker',
     'retort',
     'Provider',
     'NoSuitableProvider',
diff --git a/src/adaptix/_internal/code_generator.py b/src/adaptix/_internal/code_generator.py
deleted file mode 100644
index ab278507..00000000
--- a/src/adaptix/_internal/code_generator.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from abc import ABC, abstractmethod
-
-from .code_tools.code_builder import CodeBuilder
-from .code_tools.context_namespace import ContextNamespace
-
-
-class CodeGenerator(ABC):
-    @abstractmethod
-    def produce_code(self, ctx_namespace: ContextNamespace) -> CodeBuilder:
-        ...
diff --git a/src/adaptix/_internal/code_tools/ast_templater.py b/src/adaptix/_internal/code_tools/ast_templater.py
new file mode 100644
index 00000000..7f27a85f
--- /dev/null
+++ b/src/adaptix/_internal/code_tools/ast_templater.py
@@ -0,0 +1,20 @@
+import ast
+from ast import AST, NodeTransformer
+from typing import Mapping
+
+
+class Substitutor(NodeTransformer):
+    __slots__ = ('substitution', )
+
+    def __init__(self, substitution: Mapping[str, AST]):
+        self._substitution = substitution
+
+    def visit_Name(self, node: ast.Name):  # pylint: disable=invalid-name
+        if node.id in self._substitution:
+            return self._substitution[node.id]
+        return node
+
+
+def ast_substitute(template: str, **kwargs: AST) -> AST:
+    substitution = {f"__{key}__": value for key, value in kwargs.items()}
+    return Substitutor(substitution).generic_visit(ast.parse(template))
diff --git a/src/adaptix/_internal/code_tools/cascade_namespace.py b/src/adaptix/_internal/code_tools/cascade_namespace.py
new file mode 100644
index 00000000..bd378c27
--- /dev/null
+++ b/src/adaptix/_internal/code_tools/cascade_namespace.py
@@ -0,0 +1,72 @@
+from abc import ABC, abstractmethod
+from typing import AbstractSet, Mapping, Optional, Set
+
+from .utils import NAME_TO_BUILTIN
+
+
+class CascadeNamespace(ABC):
+    @abstractmethod
+    def add_constant(self, name: str, value: object) -> None:
+        ...
+
+    @abstractmethod
+    def try_add_constant(self, name: str, value: object) -> bool:
+        ...
+
+    @abstractmethod
+    def register_var(self, name: str) -> None:
+        ...
+
+    @abstractmethod
+    def try_register_var(self, name: str) -> bool:
+        ...
+
+
+class BuiltinCascadeNamespace(CascadeNamespace):
+    __slots__ = ('_constants', '_occupied', '_variables', '_allow_builtins')
+
+    def __init__(
+        self,
+        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._occupied = set() if occupied is None else occupied
+        self._variables: Set[str] = set()
+        self._allow_builtins = allow_builtins
+
+    def try_add_constant(self, name: str, value: object) -> bool:
+        if (
+            name in self._occupied
+            or name in self._variables
+            or (name in NAME_TO_BUILTIN and not self._allow_builtins)
+        ):
+            return False
+        if name in self._constants:
+            return value is self._constants[name]
+        self._constants[name] = value
+        return True
+
+    def try_register_var(self, name: str) -> bool:
+        if (
+            name in self._occupied
+            or name in self._constants
+            or name in self._variables
+            or (name in NAME_TO_BUILTIN and not self._allow_builtins)
+        ):
+            return False
+        self._variables.add(name)
+        return True
+
+    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 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
diff --git a/src/adaptix/_internal/code_tools/context_namespace.py b/src/adaptix/_internal/code_tools/context_namespace.py
deleted file mode 100644
index a96eee9e..00000000
--- a/src/adaptix/_internal/code_tools/context_namespace.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from abc import ABC, abstractmethod
-from typing import Dict, Optional
-
-
-class ContextNamespace(ABC):
-    @abstractmethod
-    def add(self, name: str, value: object) -> None:
-        ...
-
-
-class BuiltinContextNamespace(ContextNamespace):
-    def __init__(self, namespace: Optional[Dict[str, object]] = None):
-        if namespace is None:
-            namespace = {}
-
-        self.dict = namespace
-
-    def add(self, name: str, value: object) -> None:
-        if name in self.dict:
-            if value is self.dict[name]:
-                return
-            raise KeyError(f"Key {name} is duplicated")
-
-        self.dict[name] = value
diff --git a/src/adaptix/_internal/common.py b/src/adaptix/_internal/common.py
index 580c5670..023a3fca 100644
--- a/src/adaptix/_internal/common.py
+++ b/src/adaptix/_internal/common.py
@@ -7,6 +7,7 @@
 Loader = Callable[[Any], V_co]
 Dumper = Callable[[K_contra], Any]
 Converter = Callable[..., Any]
+Coercer = Callable[[Any], Any]
 
 TypeHint = Any
 
diff --git a/src/adaptix/_internal/compat.py b/src/adaptix/_internal/compat.py
index 8218d5c4..eacffd3c 100644
--- a/src/adaptix/_internal/compat.py
+++ b/src/adaptix/_internal/compat.py
@@ -4,3 +4,11 @@
     from exceptiongroup import ExceptionGroup  # type: ignore[no-redef]  # noqa: A004
 
 CompatExceptionGroup = ExceptionGroup
+
+
+try:
+    from ast import unparse
+except ImportError:
+    from astunparse import unparse  # type: ignore[no-redef]
+
+compat_ast_unparse = unparse
diff --git a/src/adaptix/_internal/conversion/__init__.py b/src/adaptix/_internal/conversion/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/adaptix/_internal/conversion/broaching/__init__.py b/src/adaptix/_internal/conversion/broaching/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/adaptix/_internal/conversion/broaching/code_generator.py b/src/adaptix/_internal/conversion/broaching/code_generator.py
new file mode 100644
index 00000000..250b3586
--- /dev/null
+++ b/src/adaptix/_internal/conversion/broaching/code_generator.py
@@ -0,0 +1,188 @@
+import ast
+import itertools
+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 ...code_tools.ast_templater import ast_substitute
+from ...code_tools.cascade_namespace import BuiltinCascadeNamespace, CascadeNamespace
+from ...code_tools.code_builder import CodeBuilder
+from ...code_tools.utils import get_literal_expr
+from ...compat import compat_ast_unparse
+from ...model_tools.definitions import DescriptorAccessor, ItemAccessor
+from ...special_cases_optimization import as_is_stub
+from .definitions import (
+    AccessorElement,
+    ConstantElement,
+    FunctionElement,
+    KeywordArg,
+    ParameterElement,
+    PositionalArg,
+    UnpackIterable,
+    UnpackMapping,
+)
+
+BroachingPlan = Union[
+    ParameterElement,
+    ConstantElement,
+    FunctionElement['BroachingPlan'],
+    AccessorElement['BroachingPlan'],
+]
+
+
+class GenState:
+    def __init__(self, namespace: CascadeNamespace):
+        self._namespace = namespace
+        self._prefix_counter: DefaultDict[str, int] = defaultdict(lambda: 0)
+
+    def register_next_id(self, prefix: str, obj: object) -> str:
+        number = self._prefix_counter[prefix]
+        self._prefix_counter[prefix] += 1
+        name = f"{prefix}_{number}"
+        return self.register_mangled(name, obj)
+
+    def register_mangled(self, base: str, obj: object) -> str:
+        if self._namespace.try_add_constant(base, obj):
+            return base
+
+        for i in itertools.count(1):
+            name = f'{base}_{i}'
+            if self._namespace.try_add_constant(name, obj):
+                return name
+        raise RuntimeError
+
+
+class BroachingCodeGenerator(ABC):
+    @abstractmethod
+    def produce_code(
+        self,
+        signature: Signature,
+        closure_name: str,
+        stub_function: Optional[Callable],
+    ) -> Tuple[str, Mapping[str, object]]:
+        ...
+
+
+class BuiltinBroachingCodeGenerator(BroachingCodeGenerator):
+    def __init__(self, plan: BroachingPlan):
+        self._plan = plan
+
+    def _create_state(self, namespace: CascadeNamespace) -> GenState:
+        return GenState(
+            namespace=namespace,
+        )
+
+    def produce_code(
+        self,
+        signature: Signature,
+        closure_name: str,
+        stub_function: Optional[Callable],
+    ) -> 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)
+        no_types_signature = signature.replace(
+            parameters=[param.replace(annotation=Signature.empty) for param in signature.parameters.values()],
+            return_annotation=Signature.empty,
+        )
+        with builder(f'def {closure_name}{no_types_signature}:'):
+            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
+
+    def _gen_plan_element_dispatch(self, state: GenState, element: BroachingPlan) -> AST:
+        if isinstance(element, ParameterElement):
+            return self._gen_parameter_element(state, element)
+        if isinstance(element, ConstantElement):
+            return self._gen_constant_element(state, element)
+        if isinstance(element, FunctionElement):
+            return self._gen_function_element(state, element)
+        if isinstance(element, AccessorElement):
+            return self._gen_accessor_element(state, element)
+        raise TypeError
+
+    def _gen_parameter_element(self, state: GenState, element: ParameterElement) -> AST:
+        return ast.Name(id=element.name, ctx=ast.Load())
+
+    def _gen_constant_element(self, state: GenState, element: ConstantElement) -> AST:
+        expr = get_literal_expr(element.value)
+        if expr is not None:
+            return ast.parse(expr)
+
+        name = state.register_next_id('constant', element.value)
+        return ast.Name(id=name, ctx=ast.Load())
+
+    def _gen_function_element(self, state: GenState, element: FunctionElement[BroachingPlan]) -> AST:
+        if (
+            element.func == as_is_stub
+            and len(element.args) == 1
+            and isinstance(element.args[0], PositionalArg)
+        ):
+            return self._gen_plan_element_dispatch(state, element.args[0].element)
+
+        if getattr(element.func, '__name__', None) is not None:
+            name = state.register_mangled(element.func.__name__, element.func)
+        else:
+            name = state.register_next_id('func', element.func)
+
+        args = []
+        keywords = []
+        for arg in element.args:
+            if isinstance(arg, PositionalArg):
+                sub_ast = self._gen_plan_element_dispatch(state, arg.element)
+                args.append(sub_ast)
+            elif isinstance(arg, KeywordArg):
+                sub_ast = self._gen_plan_element_dispatch(state, arg.element)
+                keywords.append(ast.keyword(arg=arg.key, value=sub_ast))
+            elif isinstance(arg, UnpackMapping):
+                sub_ast = self._gen_plan_element_dispatch(state, arg.element)
+                keywords.append(ast.keyword(value=sub_ast))
+            elif isinstance(arg, UnpackIterable):
+                sub_ast = self._gen_plan_element_dispatch(state, arg.element)
+                args.append(ast.Starred(value=sub_ast, ctx=ast.Load()))
+            else:
+                raise TypeError
+
+        return ast.Call(
+            func=ast.Name(name, ast.Load()),
+            args=args,
+            keywords=keywords,
+        )
+
+    def _gen_accessor_element(self, state: GenState, element: AccessorElement[BroachingPlan]) -> AST:
+        target_expr = self._gen_plan_element_dispatch(state, element.target)
+        if isinstance(element.accessor, DescriptorAccessor):
+            if element.accessor.attr_name.isidentifier():
+                return ast_substitute(
+                    f'__target_expr__.{element.accessor.attr_name}',
+                    target_expr=target_expr,
+                )
+            return ast_substitute(
+                f"getattr(__target_expr__, {element.accessor.attr_name!r})",
+                target_expr=target_expr,
+            )
+
+        if isinstance(element.accessor, ItemAccessor):
+            return ast_substitute(
+                f"__target_expr__[{element.accessor.key!r}]",
+                target_expr=target_expr,
+            )
+
+        name = state.register_next_id('accessor', element.accessor.getter)
+        return ast_substitute(
+            f"{name}(__target_expr__)",
+            target_expr=target_expr,
+        )
diff --git a/src/adaptix/_internal/conversion/broaching/definitions.py b/src/adaptix/_internal/conversion/broaching/definitions.py
new file mode 100644
index 00000000..71a5c657
--- /dev/null
+++ b/src/adaptix/_internal/conversion/broaching/definitions.py
@@ -0,0 +1,64 @@
+from abc import ABC
+from dataclasses import dataclass
+from typing import Any, Callable, Generic, TypeVar, Union
+
+from adaptix._internal.common import VarTuple
+from adaptix._internal.model_tools.definitions import Accessor
+
+
+class BasePlanElement(ABC):
+    pass
+
+
+PlanT = TypeVar('PlanT', bound=BasePlanElement)
+
+
+@dataclass(frozen=True)
+class ParameterElement(BasePlanElement):
+    name: str
+
+
+@dataclass(frozen=True)
+class ConstantElement(BasePlanElement):
+    value: Any
+
+
+@dataclass(frozen=True)
+class PositionalArg(Generic[PlanT]):
+    element: PlanT
+
+
+@dataclass(frozen=True)
+class KeywordArg(Generic[PlanT]):
+    key: str
+    element: PlanT
+
+
+@dataclass(frozen=True)
+class UnpackIterable(Generic[PlanT]):
+    element: PlanT
+
+
+@dataclass(frozen=True)
+class UnpackMapping(Generic[PlanT]):
+    element: PlanT
+
+
+FuncCallArg = Union[
+    PositionalArg[PlanT],
+    KeywordArg[PlanT],
+    UnpackIterable[PlanT],
+    UnpackMapping[PlanT],
+]
+
+
+@dataclass(frozen=True)
+class FunctionElement(BasePlanElement, Generic[PlanT]):
+    func: Callable[..., Any]
+    args: VarTuple[FuncCallArg[PlanT]]
+
+
+@dataclass(frozen=True)
+class AccessorElement(BasePlanElement, Generic[PlanT]):
+    target: PlanT
+    accessor: Accessor
diff --git a/src/adaptix/_internal/conversion/coercer_provider.py b/src/adaptix/_internal/conversion/coercer_provider.py
new file mode 100644
index 00000000..6255ea02
--- /dev/null
+++ b/src/adaptix/_internal/conversion/coercer_provider.py
@@ -0,0 +1,74 @@
+from abc import ABC, abstractmethod
+from typing import Any
+
+from ..common import Coercer
+from ..provider.essential import CannotProvide, Mediator
+from ..provider.loc_stack_filtering import LocStackChecker
+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 is_generic, is_subclass_soft, strip_tags
+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):
+    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(src_tp)
+        norm_dst = try_normalize_type(dst_tp)
+        if strip_tags(norm_src) == strip_tags(norm_dst):
+            return as_is_stub
+        raise CannotProvide
+
+
+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
+        raise CannotProvide
+
+
+class SubclassCoercerProvider(CoercerProvider):
+    def _provide_coercer(self, mediator: Mediator, request: CoercerRequest) -> Coercer:
+        src_tp = request.src[-1].type
+        dst_tp = request.dst[-1].type
+
+        if is_generic(src_tp) or is_generic(dst_tp):
+            raise CannotProvide
+
+        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)
+        if is_subclass_soft(stripped_src.origin, stripped_dst.origin):
+            return as_is_stub
+        raise CannotProvide
+
+
+class MatchingCoercerProvider(CoercerProvider):
+    def __init__(self, src_lsc: LocStackChecker, dst_lsc: LocStackChecker, coercer: Coercer):
+        self._src_lsc = src_lsc
+        self._dst_lsc = dst_lsc
+        self._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())
+        ):
+            return self._coercer
+        raise CannotProvide
diff --git a/src/adaptix/_internal/conversion/converter_provider.py b/src/adaptix/_internal/conversion/converter_provider.py
new file mode 100644
index 00000000..f4517215
--- /dev/null
+++ b/src/adaptix/_internal/conversion/converter_provider.py
@@ -0,0 +1,375 @@
+from abc import ABC, abstractmethod
+from functools import reduce
+from inspect import Parameter, Signature
+from typing import Any, Iterable, List, Mapping, Optional, Sequence, Tuple, cast, final
+
+from ..code_tools.compiler import BasicClosureCompiler, ClosureCompiler
+from ..common import Converter, TypeHint
+from ..conversion.broaching.code_generator import BroachingCodeGenerator, BroachingPlan, BuiltinBroachingCodeGenerator
+from ..conversion.broaching.definitions import (
+    AccessorElement,
+    FuncCallArg,
+    FunctionElement,
+    KeywordArg,
+    ParameterElement,
+    PositionalArg,
+)
+from ..conversion.request_cls import (
+    CoercerRequest,
+    ConverterRequest,
+    LinkingDest,
+    LinkingRequest,
+    LinkingResult,
+    LinkingSource,
+    UnlinkedOptionalPolicyRequest,
+)
+from ..model_tools.definitions import BaseField, DefaultValue, InputField, InputShape, NoDefault, OutputShape, ParamKind
+from ..morphing.model.basic_gen import NameSanitizer, 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:
+        ...
+
+
+class BuiltinConverterProvider(ConverterProvider):
+    def __init__(self, *, name_sanitizer: NameSanitizer = NameSanitizer()):
+        self._name_sanitizer = name_sanitizer
+
+    def _provide_converter(self, mediator: Mediator, request: ConverterRequest) -> Converter:
+        signature = request.signature
+        if len(signature.parameters.values()) == 0:
+            raise CannotProvide(
+                message='At least one parameter is required',
+                is_demonstrative=True,
+            )
+        if any(
+            param.kind in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD)
+            for param in signature.parameters.values()
+        ):
+            raise CannotProvide(
+                message='Parameters specified by *args and **kwargs are not supported',
+                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)
+
+    def _make_converter(
+        self,
+        mediator: Mediator,
+        request: ConverterRequest,
+        broaching_plan: BroachingPlan,
+        signature: Signature,
+    ):
+        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=request.signature,
+            closure_name=closure_name,
+            stub_function=request.stub_function,
+        )
+        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),
+            namespace=dumper_namespace,
+            closure_code=dumper_code,
+            closure_name=closure_name,
+            file_name=self._get_file_name(request),
+        )
+
+    def _get_closure_name(self, request: ConverterRequest) -> str:
+        if request.function_name is not None:
+            return request.function_name
+        stub_function_name = getattr(request.stub_function, '__name__', None)
+        if stub_function_name is not None:
+            return stub_function_name
+        src = next(iter(request.signature.parameters.values()))
+        dst = self._get_type_from_annotation(request.signature.return_annotation)
+        return self._name_sanitizer.sanitize(f'convert_{src}_to_{dst}')
+
+    def _get_file_name(self, request: ConverterRequest) -> str:
+        if request.function_name is not None:
+            return request.function_name
+        stub_function_name = getattr(request.stub_function, '__name__', None)
+        if stub_function_name is not None:
+            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}'
+
+    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,
+            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)
+
+    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,
+        mediator: Mediator,
+        linking_src: LinkingSource,
+        linking_dst: LinkingDest,
+    ) -> BroachingPlan:
+        coercer = mediator.provide(
+            CoercerRequest(
+                src=linking_src,
+                dst=linking_dst,
+            )
+        )
+        return FunctionElement(
+            func=coercer,
+            args=(
+                PositionalArg(
+                    self._linking_source_to_plan(linking_src)
+                ),
+            ),
+        )
+
+    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: LinkingResult):
+            linking_dst = owner_linking_dst.append_with(input_field)
+            try:
+                return self._get_coercer_sub_plan(
+                    mediator=mediator,
+                    linking_src=linking.source,
+                    linking_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
+
+        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),
+        )
diff --git a/src/adaptix/_internal/conversion/facade/__init__.py b/src/adaptix/_internal/conversion/facade/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/adaptix/_internal/conversion/facade/checker.py b/src/adaptix/_internal/conversion/facade/checker.py
new file mode 100644
index 00000000..bb4d789a
--- /dev/null
+++ b/src/adaptix/_internal/conversion/facade/checker.py
@@ -0,0 +1,23 @@
+import ast
+import inspect
+from textwrap import dedent
+
+
+def ensure_function_is_stub(func):
+    try:
+        raw_source = inspect.getsource(func)
+    except OSError:
+        return
+
+    source = dedent(raw_source)
+    ast_module = ast.parse(source)
+    func_body = ast_module.body[0].body
+    if len(func_body) == 1:
+        body_element = func_body[0]
+        if isinstance(body_element, ast.Pass):
+            return
+        if isinstance(body_element, ast.Expr) and isinstance(body_element.value, ast.Constant):
+            value = body_element.value.value
+            if value == Ellipsis or isinstance(value, str):
+                return
+    raise ValueError('Body of function must be empty')
diff --git a/src/adaptix/_internal/conversion/facade/func.py b/src/adaptix/_internal/conversion/facade/func.py
new file mode 100644
index 00000000..8c6e83a4
--- /dev/null
+++ b/src/adaptix/_internal/conversion/facade/func.py
@@ -0,0 +1,78 @@
+from typing import Any, Callable, Iterable, Optional, Type, TypeVar, overload
+
+from ...common import TypeHint
+from ...provider.essential import Provider
+from .retort import ConversionRetort
+
+_global_retort = ConversionRetort()
+
+SrcT = TypeVar('SrcT')
+DstT = TypeVar('DstT')
+CallableT = TypeVar('CallableT', bound=Callable)
+
+
+def convert(src_obj: Any, dst: Type[DstT], *, recipe: Iterable[Provider] = ()) -> DstT:
+    """Function transforming a source object to destination.
+
+    :param src_obj: A type of converter input data.
+    :param dst: A type of converter output data.
+    :param recipe: An extra recipe adding to retort.
+    :return: Instance of destination
+    """
+    return _global_retort.convert(src_obj, dst, recipe=recipe)
+
+
+@overload
+def get_converter(
+    src: Type[SrcT],
+    dst: Type[DstT],
+    *,
+    recipe: Iterable[Provider] = (),
+    name: Optional[str] = None,
+) -> Callable[[SrcT], DstT]:
+    ...
+
+
+@overload
+def get_converter(
+    src: TypeHint,
+    dst: TypeHint,
+    *,
+    recipe: Iterable[Provider] = (),
+    name: Optional[str] = None,
+) -> Callable[[Any], Any]:
+    ...
+
+
+def get_converter(src: TypeHint, dst: TypeHint, *, recipe: Iterable[Provider] = (), name: Optional[str] = None):
+    """Factory producing basic converter.
+
+    :param src: A type of converter input data.
+    :param dst: A type of converter output data.
+    :param recipe: An extra recipe adding to retort.
+    :param name: Name of generated function, if value is None, name will be derived.
+    :return: Desired converter function
+    """
+    return _global_retort.get_converter(src, dst, recipe=recipe, name=name)
+
+
+@overload
+def impl_converter(func_stub: CallableT, /) -> CallableT:
+    ...
+
+
+@overload
+def impl_converter(*, recipe: Iterable[Provider] = ()) -> Callable[[CallableT], CallableT]:
+    ...
+
+
+def impl_converter(stub_function: Optional[Callable] = None, *, recipe: Iterable[Provider] = ()):
+    """Decorator producing converter with signature of stub function.
+
+    :param stub_function: A function that signature is used to generate converter.
+    :param recipe: An extra recipe adding to retort.
+    :return: Desired converter function
+    """
+    if stub_function is None:
+        return _global_retort.impl_converter(recipe=recipe)
+    return _global_retort.impl_converter(stub_function)
diff --git a/src/adaptix/_internal/conversion/facade/provider.py b/src/adaptix/_internal/conversion/facade/provider.py
new file mode 100644
index 00000000..0188b4f1
--- /dev/null
+++ b/src/adaptix/_internal/conversion/facade/provider.py
@@ -0,0 +1,57 @@
+from ...common import Coercer
+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 ..coercer_provider import MatchingCoercerProvider
+from ..linking_provider import MatchingLinkingProvider
+from ..policy_provider import UnlinkedOptionalPolicyProvider
+
+
+def link(src: Pred, dst: Pred) -> Provider:
+    """Basic provider to define custom linking between fields.
+
+    :param src: Predicate specifying source point of linking. See :ref:`predicate-system` for details.
+    :param dst: Predicate specifying destination point of linking. See :ref:`predicate-system` for details.
+    :return: Desired provider
+    """
+    return MatchingLinkingProvider(
+        src_lsc=create_loc_stack_checker(src),
+        dst_lsc=create_loc_stack_checker(dst),
+    )
+
+
+def coercer(src: Pred, dst: Pred, func: Coercer) -> Provider:
+    """Basic provider to define custom coercer.
+
+    :param src: Predicate specifying source point of linking. See :ref:`predicate-system` for details.
+    :param dst: Predicate specifying destination point of linking. See :ref:`predicate-system` for details.
+    :param func: The function is used to transform input data to a destination type.
+    :return: Desired provider
+    """
+    return MatchingCoercerProvider(
+        src_lsc=create_loc_stack_checker(src),
+        dst_lsc=create_loc_stack_checker(dst),
+        coercer=func,
+    )
+
+
+def allow_unlinked_optional(*preds: Pred) -> Provider:
+    """Sets policy to permit optional fields that does not linked to any source field.
+
+    :param preds: Predicate specifying target of policy.
+        Each predicate is merged via ``|`` operator.
+        See :ref:`predicate-system` for details.
+    :return: Desired provider.
+    """
+    return bound_by_any(preds, UnlinkedOptionalPolicyProvider(is_allowed=True))
+
+
+def forbid_unlinked_optional(*preds: Pred) -> Provider:
+    """Sets policy to prohibit optional fields that does not linked to any source field.
+
+    :param preds: Predicate specifying target of policy.
+        Each predicate is merged via ``|`` operator.
+        See :ref:`predicate-system` for details.
+    :return: Desired provider.
+    """
+    return bound_by_any(preds, UnlinkedOptionalPolicyProvider(is_allowed=False))
diff --git a/src/adaptix/_internal/conversion/facade/retort.py b/src/adaptix/_internal/conversion/facade/retort.py
new file mode 100644
index 00000000..8de3a164
--- /dev/null
+++ b/src/adaptix/_internal/conversion/facade/retort.py
@@ -0,0 +1,176 @@
+# pylint: disable=protected-access, not-callable
+import inspect
+from functools import partial
+from inspect import Parameter, Signature
+from typing import Any, Callable, Dict, Iterable, Optional, Tuple, Type, TypeVar, overload
+
+from adaptix import TypeHint
+
+from ...common import Converter
+from ...provider.essential import Provider
+from ...provider.loc_stack_filtering import P
+from ...provider.shape_provider import BUILTIN_SHAPE_PROVIDER
+from ...retort.operating_retort import OperatingRetort
+from ...type_tools import is_generic_class
+from ..coercer_provider import DstAnyCoercerProvider, SameTypeCoercerProvider, SubclassCoercerProvider
+from ..converter_provider import BuiltinConverterProvider
+from ..linking_provider import SameNameLinkingProvider
+from ..request_cls import ConverterRequest
+from .checker import ensure_function_is_stub
+from .provider import forbid_unlinked_optional
+
+
+class FilledConversionRetort(OperatingRetort):
+    recipe = [
+        BUILTIN_SHAPE_PROVIDER,
+
+        BuiltinConverterProvider(),
+
+        SameNameLinkingProvider(is_default=True),
+
+        SameTypeCoercerProvider(),
+        DstAnyCoercerProvider(),
+        SubclassCoercerProvider(),
+
+        forbid_unlinked_optional(P.ANY),
+    ]
+
+
+AR = TypeVar('AR', bound='AdornedConversionRetort')
+SrcT = TypeVar('SrcT')
+DstT = TypeVar('DstT')
+CallableT = TypeVar('CallableT', bound=Callable)
+
+
+class AdornedConversionRetort(OperatingRetort):
+    def _calculate_derived(self) -> None:
+        super()._calculate_derived()
+        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
+            )
+
+        return clone
+
+    def _produce_converter(
+        self,
+        signature: Signature,
+        stub_function: Optional[Callable],
+        function_name: Optional[str],
+    ) -> Callable[..., Any]:
+        return self._facade_provide(
+            ConverterRequest(
+                signature=signature,
+                function_name=function_name,
+                stub_function=stub_function,
+            ),
+            error_message=f'Cannot produce converter for {signature!r}',
+        )
+
+    def _make_simple_converter(self, src: TypeHint, dst: TypeHint, name: Optional[str]) -> Converter:
+        return self._produce_converter(
+            signature=Signature(
+                parameters=[Parameter('src', kind=Parameter.POSITIONAL_ONLY, annotation=src)],
+                return_annotation=dst,
+            ),
+            stub_function=None,
+            function_name=name,
+        )
+
+    @overload
+    def get_converter(
+        self,
+        src: Type[SrcT],
+        dst: Type[DstT],
+        *,
+        recipe: Iterable[Provider] = (),
+    ) -> Callable[[SrcT], DstT]:
+        ...
+
+    @overload
+    def get_converter(
+        self,
+        src: TypeHint,
+        dst: TypeHint,
+        *,
+        name: Optional[str] = None,
+        recipe: Iterable[Provider] = (),
+    ) -> Callable[[Any], Any]:
+        ...
+
+    def get_converter(
+        self,
+        src: TypeHint,
+        dst: TypeHint,
+        *,
+        name: Optional[str] = None,
+        recipe: Iterable[Provider] = (),
+    ):
+        """Method producing basic converter.
+
+        :param src: A type of converter input data.
+        :param dst: A type of converter output data.
+        :param recipe: An extra recipe adding to retort.
+        :param name: Name of generated function, if value is None, name will be derived.
+        :return: Desired converter function
+        """
+        retort = self.extend(recipe=recipe) if recipe else self
+
+        try:
+            return retort._simple_converter_cache[(src, dst, name)]
+        except KeyError:
+            pass
+        converter = retort._make_simple_converter(src, dst, name)
+        retort._simple_converter_cache[(src, dst, name)] = converter
+        return converter
+
+    @overload
+    def impl_converter(self, func_stub: CallableT, /) -> CallableT:
+        ...
+
+    @overload
+    def impl_converter(self, *, recipe: Iterable[Provider] = ()) -> Callable[[CallableT], CallableT]:
+        ...
+
+    def impl_converter(self, stub_function: Optional[Callable] = None, *, recipe: Iterable[Provider] = ()):
+        """Decorator producing converter with signature of stub function.
+
+        :param stub_function: A function that signature is used to generate converter.
+        :param recipe: An extra recipe adding to retort.
+        :return: Desired converter function
+        """
+        if stub_function is None:
+            return partial(self.impl_converter, recipe=recipe)
+
+        ensure_function_is_stub(stub_function)
+        retort = self.extend(recipe=recipe) if recipe else self
+        return retort._produce_converter(
+            signature=inspect.signature(stub_function),
+            stub_function=stub_function,
+            function_name=None,
+        )
+
+    def convert(self, src_obj: Any, dst: Type[DstT], *, recipe: Iterable[Provider] = ()) -> DstT:
+        """Method transforming a source object to destination.
+
+        :param src_obj: A type of converter input data.
+        :param dst: A type of converter output data.
+        :param recipe: An extra recipe adding to retort.
+        :return: Instance of destination
+        """
+        src = type(src_obj)
+        if is_generic_class(src):
+            raise ValueError(
+                f'Can not infer the actual type of generic class instance ({src!r}),'
+                ' you have to use `get_converter` explicitly passing the type of object'
+            )
+
+        return self.get_converter(src, dst, recipe=recipe)(src_obj)
+
+
+class ConversionRetort(FilledConversionRetort, AdornedConversionRetort):
+    pass
diff --git a/src/adaptix/_internal/conversion/linking_provider.py b/src/adaptix/_internal/conversion/linking_provider.py
new file mode 100644
index 00000000..f884d5a8
--- /dev/null
+++ b/src/adaptix/_internal/conversion/linking_provider.py
@@ -0,0 +1,49 @@
+from abc import ABC, abstractmethod
+from typing import Iterable
+
+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 LinkingRequest, LinkingResult, LinkingSource, SourceCandidates
+
+
+class LinkingProvider(StaticProvider, ABC):
+    @static_provision_action
+    @abstractmethod
+    def _provide_linking(self, mediator: Mediator, request: LinkingRequest) -> LinkingResult:
+        ...
+
+
+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:
+                return LinkingResult(source=source, is_default=self._is_default)
+        raise CannotProvide
+
+
+class MatchingLinkingProvider(LinkingProvider):
+    def __init__(self, src_lsc: LocStackChecker, dst_lsc: LocStackChecker):
+        self._src_lsc = src_lsc
+        self._dst_lsc = dst_lsc
+
+    def _provide_linking(self, mediator: Mediator, request: LinkingRequest) -> LinkingResult:
+        if not self._dst_lsc.check_loc_stack(mediator, request.destination.to_loc_stack()):
+            raise CannotProvide
+
+        for source in iterate_source_candidates(request.sources):
+            if self._src_lsc.check_loc_stack(mediator, source.to_loc_stack()):
+                return LinkingResult(source=source)
+        raise CannotProvide
diff --git a/src/adaptix/_internal/conversion/policy_provider.py b/src/adaptix/_internal/conversion/policy_provider.py
new file mode 100644
index 00000000..37cc6d37
--- /dev/null
+++ b/src/adaptix/_internal/conversion/policy_provider.py
@@ -0,0 +1,16 @@
+from ..provider.essential import Mediator
+from ..provider.static_provider import StaticProvider, static_provision_action
+from .request_cls import UnlinkedOptionalPolicy, UnlinkedOptionalPolicyRequest
+
+
+class UnlinkedOptionalPolicyProvider(StaticProvider):
+    def __init__(self, is_allowed: bool):
+        self._is_allowed = is_allowed
+
+    @static_provision_action
+    def _unlinked_optional_policy(
+        self,
+        mediator: Mediator,
+        request: UnlinkedOptionalPolicyRequest,
+    ) -> UnlinkedOptionalPolicy:
+        return UnlinkedOptionalPolicy(is_allowed=self._is_allowed)
diff --git a/src/adaptix/_internal/conversion/request_cls.py b/src/adaptix/_internal/conversion/request_cls.py
new file mode 100644
index 00000000..2232d97c
--- /dev/null
+++ b/src/adaptix/_internal/conversion/request_cls.py
@@ -0,0 +1,74 @@
+from dataclasses import dataclass
+from inspect import Signature
+from itertools import chain, islice
+from typing import Callable, Iterator, Optional, Union
+
+from ..common import Coercer, VarTuple
+from ..datastructures import ImmutableStack
+from ..model_tools.definitions import BaseField, InputField, OutputField
+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.request_cls import LocatedRequest, LocStack
+
+LinkingSourceItem = Union[OutputField, BaseField]
+
+
+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]
+
+    @property
+    def tail(self) -> Iterator[OutputField]:
+        return islice(self, 1, None)  # type: ignore[arg-type]
+
+
+class LinkingDest(ImmutableStack[InputField]):
+    def to_loc_stack(self) -> LocStack:
+        return LocStack.from_iter(map(input_field_to_loc_map, self))
+
+
+@dataclass(frozen=True)
+class LinkingResult:
+    source: LinkingSource
+    is_default: bool = False
+
+
+SourceCandidates = VarTuple[Union[LinkingSource, VarTuple[LinkingSource]]]
+
+
+@dataclass(frozen=True)
+class LinkingRequest(Request[LinkingResult]):
+    sources: SourceCandidates
+    destination: LinkingDest
+
+
+@dataclass(frozen=True)
+class CoercerRequest(Request[Coercer]):
+    src: LinkingSource
+    dst: LinkingDest
+
+
+@dataclass(frozen=True)
+class UnlinkedOptionalPolicy:
+    is_allowed: bool
+
+
+@dataclass(frozen=True)
+class UnlinkedOptionalPolicyRequest(LocatedRequest):
+    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 0f86392d..5c7f7506 100644
--- a/src/adaptix/_internal/datastructures.py
+++ b/src/adaptix/_internal/datastructures.py
@@ -11,14 +11,18 @@
     Mapping,
     Optional,
     Protocol,
+    Sequence,
     Tuple,
     Type,
     TypeVar,
     Union,
     ValuesView,
+    overload,
     runtime_checkable,
 )
 
+from .common import VarTuple
+
 K = TypeVar('K', bound=Hashable)
 V = TypeVar('V')
 _VT_co = TypeVar("_VT_co", covariant=True)
@@ -210,3 +214,61 @@ def discard(self: CM, *classes: Type[H]) -> CM:
             value for key, value in self._mapping.items()
             if key not in classes
         )
+
+
+T = TypeVar('T')
+StackT = TypeVar('StackT', bound='ImmutableStack')
+
+
+class ImmutableStack(Sequence[T], Generic[T]):
+    __slots__ = ('_tuple', )
+
+    def __init__(self, *args: T):
+        self._tuple = args
+
+    @classmethod
+    def from_tuple(cls: Type[StackT], tpl: VarTuple[T]) -> 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))
+
+    @property
+    def last(self) -> T:
+        return self[-1]
+
+    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)
+
+    def __hash__(self):
+        return hash(self._tuple)
+
+    def __eq__(self, other):
+        if isinstance(other, type(self)):
+            return self._tuple == other._tuple
+        return NotImplemented
+
+    def __iter__(self) -> Iterator[T]:
+        return iter(self._tuple)
+
+    def append_with(self: StackT, item: T) -> StackT:
+        return self.from_tuple(self._tuple + (item, ))
diff --git a/src/adaptix/_internal/feature_requirement.py b/src/adaptix/_internal/feature_requirement.py
index 44d4d144..bdb1a1d5 100644
--- a/src/adaptix/_internal/feature_requirement.py
+++ b/src/adaptix/_internal/feature_requirement.py
@@ -171,5 +171,8 @@ def fail_reason(self) -> str:
 HAS_SUPPORTED_ATTRS_PKG = DistributionVersionRequirement('attrs', '21.3.0')
 HAS_ATTRS_PKG = DistributionRequirement('attrs')
 
+HAS_SUPPORTED_SQLALCHEMY_PKG = DistributionVersionRequirement('sqlalchemy', '2.0.0')
+HAS_SQLALCHEMY_PKG = DistributionRequirement('sqlalchemy')
+
 IS_CPYTHON = PythonImplementationRequirement('cpython')
 IS_PYPY = PythonImplementationRequirement('pypy')
diff --git a/src/adaptix/_internal/model_tools/definitions.py b/src/adaptix/_internal/model_tools/definitions.py
index 5bef628d..ee73fb81 100644
--- a/src/adaptix/_internal/model_tools/definitions.py
+++ b/src/adaptix/_internal/model_tools/definitions.py
@@ -6,6 +6,7 @@
 from typing import Any, Callable, FrozenSet, Generic, Hashable, Mapping, Optional, TypeVar, Union
 
 from ..common import Catchable, TypeHint, VarTuple
+from ..feature_requirement import DistributionRequirement, DistributionVersionRequirement
 from ..struct_trail import Attr, TrailElement
 from ..utils import SingletonMeta, pairs
 
@@ -126,7 +127,7 @@ def __hash__(self):
 
     def __repr__(self):
         return (
-            "{type(self).__qualname__}"
+            f"{type(self).__qualname__}"
             f"(key={self.key!r}, access_error={self.access_error}, path_element={self.trail_element!r})"
         )
 
@@ -331,9 +332,15 @@ class IntrospectionImpossible(Exception):
 
 
 class NoTargetPackage(IntrospectionImpossible):
-    pass
+    def __init__(self, requirement: DistributionRequirement):
+        self.requirement = requirement
 
 
 class PackageIsTooOld(IntrospectionImpossible):
-    def __init__(self, required_version: str):
-        self.required_version = required_version
+    def __init__(self, requirement: DistributionVersionRequirement):
+        self.requirement = requirement
+
+
+class ClarifiedIntrospectionImpossible(IntrospectionImpossible):
+    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 e5193f64..7b66f1b7 100644
--- a/src/adaptix/_internal/model_tools/introspection/attrs.py
+++ b/src/adaptix/_internal/model_tools/introspection/attrs.py
@@ -30,7 +30,7 @@
     attrs = None  # type: ignore[assignment]
 
 
-def _get_attrs_default(attrs_field) -> Default:
+def _get_default(attrs_field) -> Default:
     default: Any = attrs_field.default
 
     if isinstance(default, attrs.Factory):  # type: ignore
@@ -44,7 +44,7 @@ def _get_attrs_default(attrs_field) -> Default:
     return DefaultValue(default)
 
 
-def _get_attrs_field_type(attrs_field, type_hints):
+def _get_field_type(attrs_field, type_hints):
     try:
         return type_hints[attrs_field.name]
     except KeyError:
@@ -54,7 +54,7 @@ def _get_attrs_field_type(attrs_field, type_hints):
 NoneType = type(None)
 
 
-def _process_attr_input_field(
+def _process_input_field(
     field: InputField,
     param_name_to_base_field: Dict[str, BaseField],
     has_custom_init: bool,
@@ -83,7 +83,7 @@ def _process_attr_input_field(
     )
 
 
-def _get_attrs_param_name(attrs_field):
+def _get_param_name(attrs_field):
     if hasattr(attrs_field, 'alias'):
         return attrs_field.alias
     return (
@@ -93,15 +93,15 @@ def _get_attrs_param_name(attrs_field):
     )
 
 
-def _get_attrs_input_shape(tp, attrs_fields, type_hints) -> InputShape:
+def _get_input_shape(tp, attrs_fields, type_hints) -> InputShape:
     param_name_to_field_from_attrs = {
-        _get_attrs_param_name(attrs_fld): InputField(
+        _get_param_name(attrs_fld): InputField(
             id=attrs_fld.name,
-            type=_get_attrs_field_type(attrs_fld, type_hints),
-            default=_get_attrs_default(attrs_fld),
+            type=_get_field_type(attrs_fld, type_hints),
+            default=_get_default(attrs_fld),
             metadata=attrs_fld.metadata,
             original=attrs_fld,
-            is_required=_get_attrs_default(attrs_fld) == NoDefault(),
+            is_required=_get_default(attrs_fld) == NoDefault(),
         )
         for attrs_fld in attrs_fields
         if attrs_fld.init
@@ -155,12 +155,12 @@ def _get_attrs_input_shape(tp, attrs_fields, type_hints) -> InputShape:
     )
 
 
-def _get_attrs_output_shape(attrs_fields, type_hints) -> OutputShape:
+def _get_output_shape(attrs_fields, type_hints) -> OutputShape:
     output_fields = tuple(
         OutputField(
             id=attrs_fld.name,
-            type=_get_attrs_field_type(attrs_fld, type_hints),
-            default=_get_attrs_default(attrs_fld),
+            type=_get_field_type(attrs_fld, type_hints),
+            default=_get_default(attrs_fld),
             metadata=attrs_fld.metadata,
             original=attrs_fld,
             accessor=create_attr_accessor(attrs_fld.name, is_required=True),
@@ -178,8 +178,8 @@ def _get_attrs_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
-        raise PackageIsTooOld(HAS_SUPPORTED_ATTRS_PKG.min_version)
+            raise NoTargetPackage(HAS_ATTRS_PKG)
+        raise PackageIsTooOld(HAS_SUPPORTED_ATTRS_PKG)
 
     try:
         is_attrs = attrs.has(tp)
@@ -195,6 +195,6 @@ def get_attrs_shape(tp) -> FullShape:
 
     type_hints = get_all_type_hints(tp)
     return Shape(
-        input=_get_attrs_input_shape(tp, attrs_fields, type_hints),
-        output=_get_attrs_output_shape(attrs_fields, type_hints),
+        input=_get_input_shape(tp, attrs_fields, type_hints),
+        output=_get_output_shape(attrs_fields, type_hints),
     )
diff --git a/src/adaptix/_internal/model_tools/introspection/dataclass.py b/src/adaptix/_internal/model_tools/introspection/dataclass.py
index b80d0286..d7f6f33b 100644
--- a/src/adaptix/_internal/model_tools/introspection/dataclass.py
+++ b/src/adaptix/_internal/model_tools/introspection/dataclass.py
@@ -30,7 +30,7 @@ def all_dc_fields(cls) -> Dict[str, DCField]:
     return cls.__dataclass_fields__
 
 
-def _get_dc_default(field: DCField) -> Default:
+def _get_default(field: DCField) -> Default:
     if field.default is not DC_MISSING:
         return DefaultValue(field.default)
     if field.default_factory is not DC_MISSING:
@@ -39,7 +39,7 @@ def _get_dc_default(field: DCField) -> Default:
 
 
 def _create_inp_field_from_dc_fields(dc_field: DCField, type_hints):
-    default = _get_dc_default(dc_field)
+    default = _get_default(dc_field)
     return InputField(
         type=type_hints[dc_field.name],
         id=dc_field.name,
@@ -51,10 +51,10 @@ def _create_inp_field_from_dc_fields(dc_field: DCField, type_hints):
 
 
 if HAS_PY_310:
-    def _get_dc_param_kind(dc_field: DCField) -> ParamKind:
+    def _get_param_kind(dc_field: DCField) -> ParamKind:
         return ParamKind.KW_ONLY if dc_field.kw_only else ParamKind.POS_OR_KW
 else:
-    def _get_dc_param_kind(dc_field: DCField) -> ParamKind:
+    def _get_param_kind(dc_field: DCField) -> ParamKind:
         return ParamKind.POS_OR_KW
 
 
@@ -91,7 +91,7 @@ def get_dataclass_shape(tp) -> FullShape:
                 Param(
                     field_id=field_id,
                     name=field_id,
-                    kind=_get_dc_param_kind(name_to_dc_field[field_id]),
+                    kind=_get_param_kind(name_to_dc_field[field_id]),
                 )
                 for field_id in init_params
             ),
@@ -106,7 +106,7 @@ def get_dataclass_shape(tp) -> FullShape:
                 OutputField(
                     type=type_hints[dc_field.name],
                     id=dc_field.name,
-                    default=_get_dc_default(name_to_dc_field[dc_field.name]),
+                    default=_get_default(name_to_dc_field[dc_field.name]),
                     accessor=create_attr_accessor(attr_name=dc_field.name, is_required=True),
                     metadata=dc_field.metadata,
                     original=dc_field,
diff --git a/src/adaptix/_internal/model_tools/introspection/sqlalchemy.py b/src/adaptix/_internal/model_tools/introspection/sqlalchemy.py
new file mode 100644
index 00000000..2ef6ac4c
--- /dev/null
+++ b/src/adaptix/_internal/model_tools/introspection/sqlalchemy.py
@@ -0,0 +1,256 @@
+import inspect
+from typing import Any, Generic, List, Mapping, Optional, TypeVar, get_args, get_origin
+
+from ...common import TypeHint
+
+try:
+    import sqlalchemy
+    from sqlalchemy import Column, ColumnCollection
+    from sqlalchemy.exc import NoInspectionAvailable
+    from sqlalchemy.orm import Mapped, Mapper, RelationshipProperty
+    from sqlalchemy.sql.schema import (
+        CallableColumnDefault,
+        ColumnElement,
+        DefaultGenerator,
+        ScalarElementColumnDefault,
+        Table,
+    )
+    from sqlalchemy.util import ReadOnlyProperties
+except ImportError:
+    pass
+
+from ...feature_requirement import HAS_SQLALCHEMY_PKG, HAS_SUPPORTED_SQLALCHEMY_PKG
+from ...type_tools import get_all_type_hints
+from ..definitions import (
+    ClarifiedIntrospectionImpossible,
+    DefaultFactory,
+    DefaultValue,
+    FullShape,
+    InputField,
+    InputShape,
+    IntrospectionImpossible,
+    NoDefault,
+    NoTargetPackage,
+    OutputField,
+    OutputShape,
+    PackageIsTooOld,
+    Param,
+    ParamKind,
+    Shape,
+    create_attr_accessor,
+)
+
+T = TypeVar('T')
+
+
+class IdWrapper(Generic[T]):
+    def __init__(self, value):
+        self.value = value
+
+    def __eq__(self, other: Any):
+        if isinstance(other, IdWrapper):
+            return self.value is other.value
+        return NotImplemented
+
+    def __repr__(self):
+        return f'{type(self).__name__}({self.value})'
+
+
+def _is_context_sensitive(default: 'CallableColumnDefault'):
+    try:
+        wrapped_callable = default.arg.__wrapped__  # type: ignore[attr-defined]
+    except AttributeError:
+        return True
+
+    parameters = inspect.signature(wrapped_callable).parameters
+    return len(parameters) > 0
+
+
+def _unwrap_mapped_annotation(type_hint: TypeHint) -> TypeHint:
+    if get_origin(type_hint) == Mapped:
+        return get_args(type_hint)[0]
+    return type_hint
+
+
+def _get_type_for_column(column: 'ColumnElement', type_hints: Mapping[str, TypeHint]):
+    try:
+        return _unwrap_mapped_annotation(type_hints[column.name])
+    except KeyError:
+        if column.nullable:
+            return Optional[column.type.python_type]
+        return column.type.python_type
+
+
+def _get_type_for_relationship(relationship: 'RelationshipProperty', type_hints: Mapping[str, TypeHint]):
+    try:
+        return _unwrap_mapped_annotation(type_hints[relationship.key])
+    except KeyError:
+        if relationship.uselist:
+            return List[relationship.entity.class_]  # type: ignore[name-defined]
+        return Optional[relationship.entity.class_]
+
+
+def _get_default(column_default: 'Optional[DefaultGenerator]'):
+    if isinstance(column_default, CallableColumnDefault):
+        if _is_context_sensitive(column_default):
+            return NoDefault()
+        return DefaultFactory(factory=column_default.arg.__wrapped__)  # type: ignore[attr-defined]
+    if isinstance(column_default, ScalarElementColumnDefault):
+        return DefaultValue(value=column_default.arg)
+    return NoDefault()
+
+
+def _is_input_required_for_column(column: 'ColumnElement', autoincrement_column: 'Optional[Column[int]]'):
+    return not (
+        # columns constrained by FK are not required since they can be specified by instances
+        column.default is not None
+        or column.nullable
+        or column.server_default is not None
+        or column.foreign_keys
+        or column is autoincrement_column
+    )
+
+
+def _get_autoincrement_column(table: 'Table'):
+    try:
+        return table.autoincrement_column
+    except AttributeError:
+        # for old sqlalchemy
+        return table._autoincrement_column  # pylint: disable=protected-access
+
+
+def _get_input_shape(
+    tp: TypeHint,
+    table: 'Table',
+    columns: 'ColumnCollection',
+    relationships: 'ReadOnlyProperties[RelationshipProperty[Any]]',
+    type_hints: Mapping[str, TypeHint],
+) -> InputShape:
+    # FromClause has no autoincrement_column
+    autoincrement_column = _get_autoincrement_column(table)
+    fields = []
+    params = []
+    for column in columns:
+        fields.append(
+            InputField(
+                id=column.key,
+                type=_get_type_for_column(column, type_hints),
+                default=_get_default(column.default),
+                is_required=_is_input_required_for_column(column, autoincrement_column),
+                metadata=column.info,
+                original=IdWrapper(column),
+            )
+        )
+        params.append(
+            Param(
+                field_id=column.key,
+                name=column.key,
+                kind=ParamKind.KW_ONLY,
+            )
+        )
+
+    for relationship in relationships:
+        if relationship.collection_class is not None:
+            continue  # it is not supported
+        if relationship.uselist is None:
+            continue  # it cannot be None there
+
+        fields.append(
+            InputField(
+                id=relationship.key,
+                type=_get_type_for_relationship(relationship, type_hints),
+                default=NoDefault(),
+                is_required=False,
+                metadata={},
+                original=relationship,
+            )
+        )
+        params.append(
+            Param(
+                field_id=relationship.key,
+                name=relationship.key,
+                kind=ParamKind.KW_ONLY
+            )
+        )
+
+    return InputShape(
+        constructor=tp,
+        fields=tuple(fields),
+        overriden_types=frozenset(),
+        kwargs=None,
+        params=tuple(params)
+    )
+
+
+def _get_output_shape(
+    columns: 'ColumnCollection',
+    relationships: 'ReadOnlyProperties[RelationshipProperty[Any]]',
+    type_hints: Mapping[str, TypeHint],
+) -> OutputShape:
+    output_fields = [
+        OutputField(
+            id=column.name,
+            type=_get_type_for_column(column, type_hints),
+            default=_get_default(column.default),
+            metadata=column.info,
+            original=IdWrapper(column),
+            accessor=create_attr_accessor(column.name, is_required=True)
+        )
+        for column in columns
+    ]
+    for relationship in relationships:
+        if relationship.collection_class is not None:
+            continue  # it is not supported
+        if relationship.uselist is None:
+            continue  # it cannot be None there
+
+        output_fields.append(
+            OutputField(
+                id=relationship.key,
+                type=_get_type_for_relationship(relationship, type_hints),
+                default=NoDefault(),
+                metadata={},
+                original=relationship,
+                accessor=create_attr_accessor(relationship.key, is_required=True)
+            )
+        )
+    return OutputShape(
+        fields=tuple(output_fields),
+        overriden_types=frozenset()
+    )
+
+
+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)
+
+    try:
+        mapper = sqlalchemy.inspect(tp)
+    except NoInspectionAvailable:
+        raise IntrospectionImpossible
+
+    if not isinstance(mapper, Mapper):
+        raise IntrospectionImpossible
+
+    if not isinstance(mapper.local_table, Table):
+        raise ClarifiedIntrospectionImpossible(
+            'Only sqlalchemy mapping to Table is supported'
+        )
+
+    type_hints = get_all_type_hints(tp)
+    return Shape(
+        input=_get_input_shape(
+            tp=tp,
+            table=mapper.local_table,
+            columns=mapper.columns,
+            relationships=mapper.relationships,
+            type_hints=type_hints
+        ),
+        output=_get_output_shape(
+            columns=mapper.columns,
+            relationships=mapper.relationships,
+            type_hints=type_hints,
+        )
+    )
diff --git a/src/adaptix/_internal/model_tools/introspection/typed_dict.py b/src/adaptix/_internal/model_tools/introspection/typed_dict.py
index eb3d4b24..cbc0150d 100644
--- a/src/adaptix/_internal/model_tools/introspection/typed_dict.py
+++ b/src/adaptix/_internal/model_tools/introspection/typed_dict.py
@@ -1,8 +1,10 @@
+import typing
 import warnings
 from types import MappingProxyType
+from typing import AbstractSet, Sequence, Set, Tuple
 
-from ...feature_requirement import HAS_PY_39
-from ...type_tools import get_all_type_hints, is_typed_dict_class
+from ...feature_requirement import HAS_PY_39, HAS_TYPED_DICT_REQUIRED
+from ...type_tools import BaseNormType, get_all_type_hints, is_typed_dict_class, normalize_type
 from ..definitions import (
     FullShape,
     InputField,
@@ -36,12 +38,38 @@ def _get_td_hints(tp):
     return elements
 
 
+def _extract_item_type(tp) -> BaseNormType:
+    if tp.origin is typing.Annotated:
+        return tp.args[0]
+    return tp
+
+
+def _fetch_required_keys(
+    fields: Sequence[Tuple[str, BaseNormType]],
+    frozen_required_keys: AbstractSet[str],
+) -> Set:
+    required_keys = set(frozen_required_keys)
+
+    for field_name, field_tp in fields:
+        require_type = _extract_item_type(field_tp)
+        if require_type.origin is typing.Required and field_name not in required_keys:
+            required_keys.add(field_name)
+        elif require_type.origin is typing.NotRequired and field_name in required_keys:
+            required_keys.remove(field_name)
+
+    return required_keys
+
+
+def _make_requirement_determinant_from_keys(required_fields: set):
+    return lambda name: name in required_fields
+
+
 if HAS_PY_39:
-    def _td_make_requirement_determinant(tp):
+    def _make_requirement_determinant_from_type(tp):
         required_fields = tp.__required_keys__
         return lambda name: name in required_fields
 else:
-    def _td_make_requirement_determinant(tp):
+    def _make_requirement_determinant_from_type(tp):
         warnings.warn(TypedDictAt38Warning(), stacklevel=3)
         is_total = tp.__total__
         return lambda name: is_total
@@ -53,8 +81,19 @@ def get_typed_dict_shape(tp) -> FullShape:
     if not is_typed_dict_class(tp):
         raise IntrospectionImpossible
 
-    requirement_determinant = _td_make_requirement_determinant(tp)
     type_hints = _get_td_hints(tp)
+
+    if HAS_TYPED_DICT_REQUIRED:
+        norm_types = [normalize_type(tp) for _, tp in type_hints]
+
+        required_keys = _fetch_required_keys(
+            [(field_name, field_tp) for (field_name, _), field_tp in zip(type_hints, norm_types)],
+            tp.__required_keys__,
+        )
+        requirement_determinant = _make_requirement_determinant_from_keys(required_keys)
+    else:
+        requirement_determinant = _make_requirement_determinant_from_type(tp)
+
     return Shape(
         input=InputShape(
             constructor=tp,
diff --git a/src/adaptix/_internal/morphing/concrete_provider.py b/src/adaptix/_internal/morphing/concrete_provider.py
index a263ef0b..7a3c8f69 100644
--- a/src/adaptix/_internal/morphing/concrete_provider.py
+++ b/src/adaptix/_internal/morphing/concrete_provider.py
@@ -11,29 +11,25 @@
 from ..common import Dumper, Loader
 from ..feature_requirement import HAS_PY_311, HAS_SELF_TYPE
 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 FieldLoc, LocatedRequest, StrictCoercionRequest, TypeHintLoc
-from ..provider.request_filtering import P, create_request_checker
+from ..provider.request_cls import LocatedRequest, StrictCoercionRequest, TypeHintLoc, find_owner_with_field
 from ..provider.static_provider import static_provision_action
-from ..provider.utils import find_field_request
 from ..special_cases_optimization import as_is_stub
-from .load_error import DatetimeFormatMismatch, TypeLoadError, ValueLoadError
-from .provider_template import DumperProvider, LoaderProvider, ProviderWithAttachableRC
+from .load_error import FormatMismatchLoadError, TypeLoadError, ValueLoadError
+from .provider_template import DumperProvider, LoaderProvider, ProviderWithAttachableLSC
 from .request_cls import DumperRequest, LoaderRequest
 
 T = TypeVar('T')
 
 
 @dataclass
-class ForAnyDateTime(ProviderWithAttachableRC):
+class IsoFormatProvider(LoaderProvider, DumperProvider):
     cls: Type[Union[date, time]]
 
     def __post_init__(self):
-        self._request_checker = create_request_checker(self.cls)
+        self._loc_stack_checker = create_loc_stack_checker(self.cls)
 
-
-@dataclass
-class IsoFormatProvider(ForAnyDateTime, LoaderProvider, DumperProvider):
     def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         raw_loader = self.cls.fromisoformat
 
@@ -63,7 +59,7 @@ def datetime_format_loader(data):
             try:
                 return datetime.strptime(data, fmt)
             except ValueError:
-                raise DatetimeFormatMismatch(fmt, data)
+                raise FormatMismatchLoadError(fmt, data)
             except TypeError:
                 raise TypeLoadError(str, data)
 
@@ -147,9 +143,10 @@ class BytearrayBase64Provider(LoaderProvider, Base64DumperMixin):
     _BYTES_PROVIDER = BytesBase64Provider()
 
     def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        request.loc_map.get_or_raise(TypeHintLoc, lambda: CannotProvide)
+        request.last_map.get_or_raise(TypeHintLoc, lambda: CannotProvide)
         bytes_loader = self._BYTES_PROVIDER.apply_provider(
-            mediator, replace(request, loc_map=request.loc_map.add(TypeHintLoc(bytes)))
+            mediator,
+            replace(request, loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(bytes)))
         )
 
         def bytearray_base64_loader(data):
@@ -188,13 +185,13 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
 
 class ScalarLoaderProvider(LoaderProvider, Generic[T]):
     def __init__(self, pred: Type[T], strict_coercion_loader: Loader[T], lax_coercion_loader: Loader[T]):
-        self._request_checker = create_request_checker(pred)
+        self._loc_stack_checker = create_loc_stack_checker(pred)
         self._pred = pred
         self._strict_coercion_loader = strict_coercion_loader
         self._lax_coercion_loader = lax_coercion_loader
 
     def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_map=request.loc_map))
+        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
         return self._strict_coercion_loader if strict_coercion else self._lax_coercion_loader
 
 
@@ -357,13 +354,13 @@ def complex_lax_coercion_loader(data):
 
 
 @for_predicate(typing.Self if HAS_SELF_TYPE else ~P.ANY)
-class SelfTypeProvider(ProviderWithAttachableRC):
+class SelfTypeProvider(ProviderWithAttachableLSC):
     @static_provision_action
     def _provide_substitute(self, mediator: Mediator, request: LocatedRequest) -> Loader:
-        self._request_checker.check_request(mediator, request)
+        self._apply_loc_stack_checker(mediator, request)
 
         try:
-            field_request = find_field_request(mediator.request_stack)
+            owner_loc_map, _field_loc_map = find_owner_with_field(request.loc_stack)
         except ValueError:
             raise CannotProvide(
                 'Owner type is not found',
@@ -371,11 +368,10 @@ def _provide_substitute(self, mediator: Mediator, request: LocatedRequest) -> Lo
                 is_demonstrative=True
             ) from None
 
-        owner_type = field_request.loc_map[FieldLoc].owner_type
         return mediator.delegating_provide(
             replace(
                 request,
-                loc_map=request.loc_map.add(TypeHintLoc(owner_type))
+                loc_stack=request.loc_stack.add_to_last_map(owner_loc_map[TypeHintLoc])
             ),
         )
 
@@ -383,7 +379,7 @@ def _provide_substitute(self, mediator: Mediator, request: LocatedRequest) -> Lo
 @for_predicate(typing.LiteralString if HAS_PY_311 else ~P.ANY)
 class LiteralStringProvider(LoaderProvider, DumperProvider):
     def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
-        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_map=request.loc_map))
+        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
         return str_strict_coercion_loader if strict_coercion else str  # type: ignore[return-value]
 
     def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
diff --git a/src/adaptix/_internal/morphing/constant_length_tuple_provider.py b/src/adaptix/_internal/morphing/constant_length_tuple_provider.py
index bb02327b..253dcc85 100644
--- a/src/adaptix/_internal/morphing/constant_length_tuple_provider.py
+++ b/src/adaptix/_internal/morphing/constant_length_tuple_provider.py
@@ -21,9 +21,9 @@
 from .load_error import (
     AggregateLoadError,
     ExcludedTypeLoadError,
-    ExtraItemsError,
+    ExtraItemsLoadError,
     LoadError,
-    NoRequiredItemsError,
+    NoRequiredItemsLoadError,
     TypeLoadError,
 )
 from .provider_template import DumperProvider, LoaderProvider
@@ -48,16 +48,19 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         loaders = mediator.mandatory_provide_by_iterable(
             [
                 LoaderRequest(
-                    loc_map=LocMap(
-                        TypeHintLoc(type=tp.source),
-                        GenericParamLoc(generic_pos=i),
+                    loc_stack=request.loc_stack.append_with(
+                        LocMap(
+                            TypeHintLoc(type=tp.source),
+                            GenericParamLoc(generic_pos=i),
+                        )
                     )
                 )
                 for i, tp in enumerate(norm.args)
-            ]
+            ],
+            lambda: 'Cannot create loader for tuple. Loaders for some elements cannot be created',
         )
-        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_map=request.loc_map))
-        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_map=request.loc_map))
+        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
+        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
         return self._make_loader(tuple(loaders), strict_coercion, debug_trail)
 
     def _make_loader(self, loaders: Collection[Loader], strict_coercion: bool, debug_trail: DebugTrail):
@@ -111,9 +114,9 @@ def dt_all_loader(data):  # noqa: CCR001
 
             if data_len != loaders_len:
                 if data_len > loaders_len:
-                    raise ExtraItemsError(loaders_len, data)
+                    raise ExtraItemsLoadError(loaders_len, data)
                 if loaders_len > data_len:
-                    raise NoRequiredItemsError(loaders_len, data)
+                    raise NoRequiredItemsLoadError(loaders_len, data)
 
             idx = 0
             errors = []
@@ -153,9 +156,9 @@ def dt_first_loader(data):
 
             if data_len != loaders_len:
                 if data_len > loaders_len:
-                    raise ExtraItemsError(loaders_len, data)
+                    raise ExtraItemsLoadError(loaders_len, data)
                 if loaders_len > data_len:
-                    raise NoRequiredItemsError(loaders_len, data)
+                    raise NoRequiredItemsLoadError(loaders_len, data)
 
             idx = 0
             for loader, field in zip(loaders, data):
@@ -179,14 +182,13 @@ def dt_disable_non_sc_loader(data):
 
             if data_len != loaders_len:
                 if data_len > loaders_len:
-                    raise ExtraItemsError(loaders_len, data)
+                    raise ExtraItemsLoadError(loaders_len, data)
                 if loaders_len > data_len:
-                    raise NoRequiredItemsError(loaders_len, data)
+                    raise NoRequiredItemsLoadError(loaders_len, data)
 
             return tuple(
                 loader(field)
-                for loader, field
-                in zip(loaders, data)
+                for loader, field in zip(loaders, data)
             )
 
         return dt_disable_non_sc_loader
@@ -207,14 +209,13 @@ def dt_disable_sc_loader(data):
 
             if data_len != loaders_len:
                 if data_len > loaders_len:
-                    raise ExtraItemsError(loaders_len, data)
+                    raise ExtraItemsLoadError(loaders_len, data)
                 if loaders_len > data_len:
-                    raise NoRequiredItemsError(loaders_len, data)
+                    raise NoRequiredItemsLoadError(loaders_len, data)
 
             return tuple(
                 loader(field)
-                for loader, field
-                in zip(loaders, data)
+                for loader, field in zip(loaders, data)
             )
 
         return dt_disable_sc_loader
@@ -232,15 +233,18 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         dumpers = mediator.mandatory_provide_by_iterable(
             [
                 DumperRequest(
-                    loc_map=LocMap(
-                        TypeHintLoc(type=tp.source),
-                        GenericParamLoc(generic_pos=i),
+                    loc_stack=request.loc_stack.append_with(
+                        LocMap(
+                            TypeHintLoc(type=tp.source),
+                            GenericParamLoc(generic_pos=i),
+                        )
                     )
                 )
                 for i, tp in enumerate(norm.args)
-            ]
+            ],
+            lambda: 'Cannot create dumper for tuple. Dumpers for some elements cannot be created',
         )
-        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_map=request.loc_map))
+        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
         return self._make_dumper(tuple(dumpers), debug_trail)
 
     def _make_dumper(self, dumpers: Collection[Dumper], debug_trail: DebugTrail):
@@ -263,9 +267,9 @@ def dt_all_dumper(data):
 
             if data_len != dumpers_len:
                 if data_len > dumpers_len:
-                    raise ExtraItemsError(dumpers_len, data)
+                    raise ExtraItemsLoadError(dumpers_len, data)
                 if dumpers_len > data_len:
-                    raise NoRequiredItemsError(dumpers_len, data)
+                    raise NoRequiredItemsLoadError(dumpers_len, data)
 
             idx = 0
             errors = []
@@ -296,9 +300,9 @@ def dt_first_dumper(data):
 
             if data_len != dumpers_len:
                 if data_len > dumpers_len:
-                    raise ExtraItemsError(dumpers_len, data)
+                    raise ExtraItemsLoadError(dumpers_len, data)
                 if dumpers_len > data_len:
-                    raise NoRequiredItemsError(dumpers_len, data)
+                    raise NoRequiredItemsLoadError(dumpers_len, data)
 
             idx = 0
             for dumper, field in zip(dumpers, data):
@@ -328,14 +332,13 @@ def tuple_dumper(data):
 
             if data_len != dumpers_len:
                 if data_len > dumpers_len:
-                    raise ExtraItemsError(dumpers_len, data)
+                    raise ExtraItemsLoadError(dumpers_len, data)
                 if dumpers_len > data_len:
-                    raise NoRequiredItemsError(dumpers_len, data)
+                    raise NoRequiredItemsLoadError(dumpers_len, data)
 
             return tuple(
                 dumper(field)
-                for dumper, field
-                in zip(dumpers, data)
+                for dumper, field in zip(dumpers, data)
             )
 
         return tuple_dumper
diff --git a/src/adaptix/_internal/morphing/dict_provider.py b/src/adaptix/_internal/morphing/dict_provider.py
index a8a5c182..17305e97 100644
--- a/src/adaptix/_internal/morphing/dict_provider.py
+++ b/src/adaptix/_internal/morphing/dict_provider.py
@@ -1,5 +1,7 @@
 import collections.abc
-from typing import Dict, Mapping, Tuple
+from collections import defaultdict
+from dataclasses import replace
+from typing import Callable, DefaultDict, Dict, Mapping, Optional, Tuple
 
 from ..common import Dumper, Loader
 from ..compat import CompatExceptionGroup
@@ -35,24 +37,28 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
 
         key_loader = mediator.mandatory_provide(
             LoaderRequest(
-                loc_map=LocMap(
-                    TypeHintLoc(type=key.source),
-                    GenericParamLoc(generic_pos=0),
+                loc_stack=request.loc_stack.append_with(
+                    LocMap(
+                        TypeHintLoc(type=key.source),
+                        GenericParamLoc(generic_pos=0),
+                    )
                 )
             ),
             lambda x: 'Cannot create loader for dict. Loader for key cannot be created',
         )
         value_loader = mediator.mandatory_provide(
             LoaderRequest(
-                loc_map=LocMap(
-                    TypeHintLoc(type=value.source),
-                    GenericParamLoc(generic_pos=1),
+                loc_stack=request.loc_stack.append_with(
+                    LocMap(
+                        TypeHintLoc(type=value.source),
+                        GenericParamLoc(generic_pos=1),
+                    )
                 )
             ),
             lambda x: 'Cannot create loader for dict. Loader for value cannot be created',
         )
         debug_trail = mediator.mandatory_provide(
-            DebugTrailRequest(loc_map=request.loc_map)
+            DebugTrailRequest(loc_stack=request.loc_stack)
         )
         return self._make_loader(
             key_loader=key_loader,
@@ -160,24 +166,28 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
 
         key_dumper = mediator.mandatory_provide(
             DumperRequest(
-                loc_map=LocMap(
-                    TypeHintLoc(type=key.source),
-                    GenericParamLoc(generic_pos=0),
+                loc_stack=request.loc_stack.append_with(
+                    LocMap(
+                        TypeHintLoc(type=key.source),
+                        GenericParamLoc(generic_pos=0),
+                    )
                 )
             ),
             lambda x: 'Cannot create dumper for dict. Dumper for key cannot be created',
         )
         value_dumper = mediator.mandatory_provide(
             DumperRequest(
-                loc_map=LocMap(
-                    TypeHintLoc(type=value.source),
-                    GenericParamLoc(generic_pos=1),
+                loc_stack=request.loc_stack.append_with(
+                    LocMap(
+                        TypeHintLoc(type=value.source),
+                        GenericParamLoc(generic_pos=1),
+                    )
                 )
             ),
             lambda x: 'Cannot create dumper for dict. Dumper for value cannot be created',
         )
         debug_trail = mediator.mandatory_provide(
-            DebugTrailRequest(loc_map=request.loc_map)
+            DebugTrailRequest(loc_stack=request.loc_stack)
         )
         return self._make_dumper(
             key_dumper=key_dumper,
@@ -261,3 +271,38 @@ def dict_dumper_dt_all(data: Mapping):
             return result
 
         return dict_dumper_dt_all
+
+
+@for_predicate(DefaultDict)
+class DefaultDictProvider(LoaderProvider, DumperProvider):
+    _DICT_PROVIDER = DictProvider()
+
+    def __init__(self, default_factory: Optional[Callable] = None):
+        self.default_factory = default_factory
+
+    def _extract_key_value(self, request: LocatedRequest) -> Tuple[BaseNormType, BaseNormType]:
+        norm = try_normalize_type(get_type_from_request(request))
+        return norm.args
+
+    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        key, value = self._extract_key_value(request)
+        dict_type_hint = Dict[key.source, value.source]  # type: ignore
+        dict_loader = self._DICT_PROVIDER.apply_provider(
+            mediator,
+            replace(request, loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(dict_type_hint)))
+        )
+        default_factory = self.default_factory
+
+        def defaultdict_loader(data):
+            return defaultdict(default_factory, dict_loader(data))
+
+        return defaultdict_loader
+
+    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        key, value = self._extract_key_value(request)
+        dict_type_hint = Dict[key.source, value.source]  # type: ignore
+
+        return self._DICT_PROVIDER.apply_provider(
+            mediator,
+            replace(request, loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(dict_type_hint)))
+        )
diff --git a/src/adaptix/_internal/morphing/enum_provider.py b/src/adaptix/_internal/morphing/enum_provider.py
index 6e7d3ddf..1efc3ea4 100644
--- a/src/adaptix/_internal/morphing/enum_provider.py
+++ b/src/adaptix/_internal/morphing/enum_provider.py
@@ -1,62 +1,135 @@
-from abc import ABC
+import collections
+import math
+from abc import ABC, abstractmethod
 from enum import Enum, EnumMeta, Flag
-from typing import Any, Mapping, Optional, Type
+from functools import reduce
+from operator import or_
+from typing import Any, Iterable, Mapping, Optional, Sequence, Type, TypeVar, Union, final
 
 from ..common import Dumper, Loader, TypeHint
 from ..morphing.provider_template import DumperProvider, LoaderProvider
-from ..provider.essential import CannotProvide, Mediator, Request
-from ..provider.request_cls import LocatedRequest, LocMap, TypeHintLoc, get_type_from_request, try_normalize_type
-from ..provider.request_filtering import DirectMediator, RequestChecker
-from .load_error import BadVariantError, MsgError
+from ..name_style import NameStyle, convert_snake_style
+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 ..type_tools import is_subclass_soft, normalize_type
+from .load_error import (
+    BadVariantLoadError,
+    DuplicatedValuesLoadError,
+    ExcludedTypeLoadError,
+    MsgLoadError,
+    MultipleBadVariantLoadError,
+    OutOfRangeLoadError,
+    TypeLoadError,
+)
 from .request_cls import DumperRequest, LoaderRequest
 
+EnumT = TypeVar("EnumT", bound=Enum)
+FlagT = TypeVar("FlagT", bound=Flag)
+CollectionsMapping = collections.abc.Mapping
 
-class AnyEnumRC(RequestChecker):
-    def check_request(self, mediator: DirectMediator, request: Request) -> None:
-        if not isinstance(request, LocatedRequest):
-            raise CannotProvide
 
-        norm = try_normalize_type(get_type_from_request(request))
+class BaseEnumMappingGenerator(ABC):
+    @abstractmethod
+    def _generate_mapping(self, cases: Iterable[EnumT]) -> Mapping[EnumT, str]:
+        ...
 
-        if not isinstance(norm.origin, EnumMeta):
-            raise CannotProvide
+    @final
+    def generate_for_dumping(self, cases: Iterable[EnumT]) -> Mapping[EnumT, str]:
+        return self._generate_mapping(cases)
 
+    @final
+    def generate_for_loading(self, cases: Iterable[EnumT]) -> Mapping[str, EnumT]:
+        return {
+            mapping_result: case
+            for case, mapping_result in self._generate_mapping(cases).items()
+        }
 
+
+class ByNameEnumMappingGenerator(BaseEnumMappingGenerator):
+    def __init__(
+        self,
+        name_style: Optional[NameStyle] = None,
+        map: Optional[Mapping[Union[str, Enum], str]] = None  # noqa: A002
+    ):
+        self._name_style = name_style
+        self._map = map if map is not None else {}
+
+    def _generate_mapping(self, cases: Iterable[EnumT]) -> Mapping[EnumT, str]:
+        result = {}
+
+        for case in cases:
+            if case in self._map:
+                mapped = self._map[case]
+            elif case.name in self._map:
+                mapped = self._map[case.name]
+            elif self._name_style:
+                mapped = convert_snake_style(case.name, self._name_style)
+            else:
+                mapped = case.name
+            result[case] = mapped
+
+        return result
+
+
+class AnyEnumLSC(LastLocMapChecker):
+    def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
+        try:
+            norm = normalize_type(loc.type)
+        except ValueError:
+            return False
+        origin = norm.origin
+        return isinstance(origin, EnumMeta) and not is_subclass_soft(origin, Flag)
+
+
+class FlagEnumLSC(LastLocMapChecker):
+    def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
+        try:
+            norm = normalize_type(loc.type)
+        except ValueError:
+            return False
+        return is_subclass_soft(norm.origin, Flag)
+
+
+@for_predicate(AnyEnumLSC())
 class BaseEnumProvider(LoaderProvider, DumperProvider, ABC):
-    _request_checker = AnyEnumRC()
+    pass
 
 
-def _enum_name_dumper(data):
-    return data.name
+@for_predicate(FlagEnumLSC())
+class BaseFlagProvider(LoaderProvider, DumperProvider, ABC):
+    pass
 
 
 class EnumNameProvider(BaseEnumProvider):
     """This provider represents enum members to the outside world by their name"""
+    def __init__(self, mapping_generator: BaseEnumMappingGenerator):
+        self._mapping_generator = mapping_generator
 
     def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         enum = get_type_from_request(request)
-
-        if issubclass(enum, Flag):
-            raise CannotProvide(
-                "Flag subclasses is not supported yet",
-                is_terminal=True,
-                is_demonstrative=True
-            )
-
-        variants = [case.name for case in enum]
+        mapping = self._mapping_generator.generate_for_loading(enum.__members__.values())
+        variants = list(mapping.keys())
 
         def enum_loader(data):
             try:
-                return enum[data]
+                return mapping[data]
             except KeyError:
-                raise BadVariantError(variants, data) from None
+                raise BadVariantLoadError(variants, data) from None
             except TypeError:
-                raise BadVariantError(variants, data)
+                raise BadVariantLoadError(variants, data)
 
         return enum_loader
 
     def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
-        return _enum_name_dumper
+        enum = get_type_from_request(request)
+        mapping = self._mapping_generator.generate_for_dumping(enum.__members__.values())
+
+        def enum_dumper(data: Enum) -> str:
+            return mapping[data]
+
+        return enum_dumper
 
 
 class EnumValueProvider(BaseEnumProvider):
@@ -67,8 +140,10 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         enum = get_type_from_request(request)
         value_loader = mediator.mandatory_provide(
             LoaderRequest(
-                loc_map=LocMap(
-                    TypeHintLoc(type=self._value_type),
+                loc_stack=request.loc_stack.append_with(
+                    LocMap(
+                        TypeHintLoc(type=self._value_type),
+                    )
                 )
             ),
         )
@@ -78,15 +153,17 @@ def enum_loader(data):
             try:
                 return enum(loaded_value)
             except ValueError:
-                raise MsgError("Bad enum value", data)
+                raise MsgLoadError("Bad enum value", data)
 
         return enum_loader
 
     def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         value_dumper = mediator.mandatory_provide(
             DumperRequest(
-                loc_map=LocMap(
-                    TypeHintLoc(type=self._value_type)
+                loc_stack=request.loc_stack.append_with(
+                    LocMap(
+                        TypeHintLoc(type=self._value_type)
+                    )
                 )
             ),
         )
@@ -117,12 +194,12 @@ def _make_loader(self, enum):
             def enum_exact_loader(data):
                 # since MyEnum(MyEnum.MY_CASE) == MyEnum.MY_CASE
                 if type(data) is enum:  # pylint: disable=unidiomatic-typecheck
-                    raise BadVariantError(variants, data)
+                    raise BadVariantLoadError(variants, data)
 
                 try:
                     return enum(data)
                 except ValueError:
-                    raise BadVariantError(variants, data) from None
+                    raise BadVariantLoadError(variants, data) from None
 
             return enum_exact_loader
 
@@ -130,9 +207,9 @@ def enum_exact_loader_v2m(data):
             try:
                 return value_to_member[data]
             except KeyError:
-                raise BadVariantError(variants, data) from None
+                raise BadVariantLoadError(variants, data) from None
             except TypeError:
-                raise BadVariantError(variants, data)
+                raise BadVariantLoadError(variants, data)
 
         return enum_exact_loader_v2m
 
@@ -150,3 +227,137 @@ def _get_exact_value_to_member(self, enum: Type[Enum]) -> Optional[Mapping[Any,
 
     def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         return _enum_exact_value_dumper
+
+
+class FlagByExactValueProvider(BaseFlagProvider):
+    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
+        enum = get_type_from_request(request)
+        flag_mask = reduce(or_, enum.__members__.values()).value
+
+        if flag_mask < 0:
+            raise CannotProvide(
+                "Cannot create a loader for flag with negative values",
+                is_terminal=True,
+                is_demonstrative=True,
+            )
+
+        all_bits = 2 ** flag_mask.bit_length() - 1
+        if all_bits != flag_mask:
+            raise CannotProvide(
+                "Cannot create a loader for flag with skipped bits",
+                is_terminal=True,
+                is_demonstrative=True,
+            )
+
+        def flag_loader(data):
+            if type(data) is not int:   # pylint: disable=unidiomatic-typecheck
+                raise TypeLoadError(int, data)
+
+            if data < 0 or data > flag_mask:
+                raise OutOfRangeLoadError(0, flag_mask, data)
+
+            # data already has been validated for all edge cases
+            # so enum lookup cannot raise an error
+
+            return enum(data)
+
+        return flag_loader
+
+    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        return _enum_exact_value_dumper
+
+
+def _extract_non_compound_cases_from_flag(enum: Type[FlagT]) -> Sequence[FlagT]:
+    return [case for case in enum.__members__.values() if not math.log2(case.value) % 1]
+
+
+class FlagByListProvider(BaseFlagProvider):
+    def __init__(
+        self,
+        mapping_generator: BaseEnumMappingGenerator,
+        allow_single_value: bool = False,
+        allow_duplicates: bool = True,
+        allow_compound: bool = True,
+    ):
+        self._mapping_generator = mapping_generator
+        self._allow_single_value = allow_single_value
+        self._allow_duplicates = allow_duplicates
+        self._allow_compound = allow_compound
+
+    def _get_cases(self, enum: Type[FlagT]) -> Sequence[FlagT]:
+        if self._allow_compound:
+            return list(enum.__members__.values())
+        return _extract_non_compound_cases_from_flag(enum)
+
+    def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:  # noqa: CCR001
+        enum = get_type_from_request(request)
+
+        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
+        allow_single_value = self._allow_single_value
+        allow_duplicates = self._allow_duplicates
+
+        cases = self._get_cases(enum)
+        mapping = self._mapping_generator.generate_for_loading(cases)
+        variants = list(mapping.keys())
+        zero_case = enum(0)
+
+        # treat str and Iterable[str] as different types
+        expected_type = Union[str, Iterable[str]] if allow_single_value else Iterable[str]
+
+        def flag_loader(data) -> Flag:  # noqa: CCR001
+            data_type = type(data)
+
+            if isinstance(data, Iterable) and data_type is not str:
+                if strict_coercion and isinstance(data, CollectionsMapping):
+                    raise ExcludedTypeLoadError(expected_type, Mapping, data)
+                process_data = tuple(data)
+            else:
+                if not allow_single_value or data_type is not str:
+                    raise TypeLoadError(expected_type, data)
+                process_data = (data,)
+
+            if not allow_duplicates:
+                if len(process_data) != len(set(process_data)):
+                    raise DuplicatedValuesLoadError(data)
+
+            bad_variants = []
+            result = zero_case
+            for item in process_data:
+                if item not in variants:
+                    bad_variants.append(item)
+                    continue
+                result |= mapping[item]
+
+            if bad_variants:
+                raise MultipleBadVariantLoadError(
+                    allowed_values=variants,
+                    invalid_values=bad_variants,
+                    input_value=data,
+                )
+
+            return result
+
+        return flag_loader
+
+    def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
+        enum = get_type_from_request(request)
+
+        cases = self._get_cases(enum)
+        need_to_reverse = self._allow_compound and cases != _extract_non_compound_cases_from_flag(enum)
+        if need_to_reverse:
+            cases = tuple(reversed(cases))
+
+        mapping = self._mapping_generator.generate_for_dumping(cases)
+
+        zero_case = enum(0)
+
+        def flag_dumper(value: Flag) -> Sequence[str]:
+            result = []
+            cases_sum = zero_case
+            for case in cases:
+                if case in value and case not in cases_sum:
+                    cases_sum |= case
+                    result.append(mapping[case])
+            return list(reversed(result)) if need_to_reverse else result
+
+        return flag_dumper
diff --git a/src/adaptix/_internal/morphing/facade/provider.py b/src/adaptix/_internal/morphing/facade/provider.py
index 7bb29b3b..4c59d012 100644
--- a/src/adaptix/_internal/morphing/facade/provider.py
+++ b/src/adaptix/_internal/morphing/facade/provider.py
@@ -2,29 +2,38 @@
 
 from enum import Enum, EnumMeta
 from types import MappingProxyType
-from typing import Any, Callable, Iterable, List, Mapping, Optional, Sequence, TypeVar, Union
+from typing import Any, Callable, Iterable, List, Mapping, Optional, TypeVar, Union
 
 from ...common import Catchable, Dumper, Loader, TypeHint, VarTuple
 from ...model_tools.definitions import Default, DescriptorAccessor, NoDefault, OutputField
 from ...model_tools.introspection.callable import get_callable_shape
 from ...name_style import NameStyle
 from ...provider.essential import Provider
-from ...provider.overlay_schema import OverlayProvider
-from ...provider.provider_template import ValueProvider
-from ...provider.provider_wrapper import BoundingProvider, Chain, ChainingProvider
-from ...provider.request_filtering import (
-    AnyRequestChecker,
-    OrRequestChecker,
+from ...provider.facade.provider import bound, bound_by_any
+from ...provider.loc_stack_filtering import (
+    AnyLocStackChecker,
+    LocStackChecker,
+    LocStackPattern,
+    OrLocStackChecker,
     Pred,
-    RequestChecker,
-    RequestPattern,
-    create_request_checker,
+    create_loc_stack_checker,
 )
+from ...provider.overlay_schema import OverlayProvider
+from ...provider.provider_template import ValueProvider
+from ...provider.provider_wrapper import Chain, ChainingProvider
 from ...provider.shape_provider import PropertyExtender
 from ...special_cases_optimization import as_is_stub
 from ...utils import Omittable, Omitted
-from ..enum_provider import EnumExactValueProvider, EnumNameProvider, EnumValueProvider
-from ..load_error import LoadError, ValidationError
+from ..dict_provider import DefaultDictProvider
+from ..enum_provider import (
+    ByNameEnumMappingGenerator,
+    EnumExactValueProvider,
+    EnumNameProvider,
+    EnumValueProvider,
+    FlagByExactValueProvider,
+    FlagByListProvider,
+)
+from ..load_error import LoadError, ValidationLoadError
 from ..model.loader_provider import InlinedShapeModelLoaderProvider
 from ..name_layout.base import ExtraIn, ExtraOut
 from ..name_layout.component import ExtraMoveAndPoliciesOverlay, SievesOverlay, StructureOverlay
@@ -39,12 +48,6 @@
 T = TypeVar('T')
 
 
-def bound(pred: Pred, provider: Provider) -> Provider:
-    if pred == Omitted():
-        return provider
-    return BoundingProvider(create_request_checker(pred), provider)
-
-
 def make_chain(chain: Optional[Chain], provider: Provider) -> Provider:
     if chain is None:
         return provider
@@ -67,7 +70,7 @@ def loader(pred: Pred, func: Loader, chain: Optional[Chain] = None) -> Provider:
 
         If the parameter is ``Chain.LAST``, the specified function gets result of the previous loader.
 
-    :return: desired provider
+    :return: Desired provider
     """
     return bound(
         pred,
@@ -93,7 +96,7 @@ def dumper(pred: Pred, func: Dumper, chain: Optional[Chain] = None) -> Provider:
 
         If the parameter is ``Chain.LAST``, the specified function gets result of the previous dumper.
 
-    :return: desired provider
+    :return: Desired provider
     """
     return bound(
         pred,
@@ -108,7 +111,7 @@ def as_is_loader(pred: Pred) -> Provider:
     """Provider that creates loader which does nothing with input data.
 
     :param pred: Predicate specifying where loader should be used. See :ref:`predicate-system` for details.
-    :return: desired provider
+    :return: Desired provider
     """
     return loader(pred, as_is_stub)
 
@@ -117,7 +120,7 @@ def as_is_dumper(pred: Pred) -> Provider:
     """Provider that creates dumper which does nothing with input data.
 
     :param pred: Predicate specifying where dumper should be used. See :ref:`predicate-system` for details.
-    :return: desired provider
+    :return: Desired provider
     """
     return dumper(pred, as_is_stub)
 
@@ -148,26 +151,26 @@ def _name_mapping_convert_map(name_map: Omittable[NameMap]) -> VarTuple[Provider
         else:
             pred, value = element
             result.append(
-                FuncNameMappingProvider(create_request_checker(pred), value)
+                FuncNameMappingProvider(create_loc_stack_checker(pred), value)
                 if callable(value) else
-                ConstNameMappingProvider(create_request_checker(pred), value)
+                ConstNameMappingProvider(create_loc_stack_checker(pred), value)
             )
     return tuple(result)
 
 
-def _name_mapping_convert_preds(value: Omittable[Union[Iterable[Pred], Pred]]) -> Omittable[RequestChecker]:
+def _name_mapping_convert_preds(value: Omittable[Union[Iterable[Pred], Pred]]) -> Omittable[LocStackChecker]:
     if isinstance(value, Omitted):
         return value
     if isinstance(value, Iterable) and not isinstance(value, str):
-        return OrRequestChecker([create_request_checker(el) for el in value])
-    return create_request_checker(value)
+        return OrLocStackChecker([create_loc_stack_checker(el) for el in value])
+    return create_loc_stack_checker(value)
 
 
 def _name_mapping_convert_omit_default(
     value: Omittable[Union[Iterable[Pred], Pred, bool]]
-) -> Omittable[RequestChecker]:
+) -> Omittable[LocStackChecker]:
     if isinstance(value, bool):
-        return AnyRequestChecker() if value else ~AnyRequestChecker()
+        return AnyLocStackChecker() if value else ~AnyLocStackChecker()
     return _name_mapping_convert_preds(value)
 
 
@@ -294,32 +297,33 @@ def _ensure_attr_name(prop: NameOrProp) -> str:
     return fget.__name__
 
 
-EnumPred = Union[TypeHint, str, EnumMeta, RequestPattern]
-
-
-def _wrap_enum_provider(preds: Sequence[EnumPred], provider: Provider) -> Provider:
-    if len(preds) == 0:
-        return provider
+EnumPred = Union[TypeHint, str, EnumMeta, LocStackPattern]
 
-    if Enum in preds:
-        raise ValueError(f"Can not apply enum provider to {Enum}")
 
-    return BoundingProvider(
-        OrRequestChecker([create_request_checker(pred) for pred in preds]),
-        provider,
-    )
-
-
-def enum_by_name(*preds: EnumPred) -> Provider:
+def enum_by_name(
+    *preds: EnumPred,
+    name_style: Optional[NameStyle] = None,
+    map: Optional[Mapping[Union[str, Enum], str]] = None  # noqa: A002
+) -> Provider:
     """Provider that represents enum members to the outside world by their name.
 
     :param preds: Predicates specifying where the provider should be used.
         The provider will be applied if any predicates meet the conditions,
         if no predicates are passed, the provider will be used for all Enums.
         See :ref:`predicate-system` for details.
-    :return: desired provider
+    :param name_style: Name style for representing members to the outside world.
+        If it is set, the provider will automatically convert the names of enum members to the specified convention.
+    :param map: Mapping for representing members to the outside world.
+        If it is set, the provider will use it to rename members individually;
+        its keys can either be member names as strings or member instances.
+    :return: Desired provider
     """
-    return _wrap_enum_provider(preds, EnumNameProvider())
+    return bound_by_any(
+        preds,
+        EnumNameProvider(
+            ByNameEnumMappingGenerator(name_style=name_style, map=map)
+        )
+    )
 
 
 def enum_by_exact_value(*preds: EnumPred) -> Provider:
@@ -329,9 +333,9 @@ def enum_by_exact_value(*preds: EnumPred) -> Provider:
         The provider will be applied if any predicates meet the conditions,
         if no predicates are passed, the provider will be used for all Enums.
         See :ref:`predicate-system` for details.
-    :return: desired provider
+    :return: Desired provider
     """
-    return _wrap_enum_provider(preds, EnumExactValueProvider())
+    return bound_by_any(preds, EnumExactValueProvider())
 
 
 def enum_by_value(first_pred: EnumPred, /, *preds: EnumPred, tp: TypeHint) -> Provider:
@@ -344,9 +348,67 @@ def enum_by_value(first_pred: EnumPred, /, *preds: EnumPred, tp: TypeHint) -> Pr
     :param preds: Additional predicates. The provider will be applied if any predicates meet the conditions.
     :param tp: Type of enum members.
         This type must cover all enum members for the correct operation of loader and dumper
-    :return: desired provider
+    :return: Desired provider
     """
-    return _wrap_enum_provider([first_pred, *preds], EnumValueProvider(tp))
+    return bound_by_any([first_pred, *preds], EnumValueProvider(tp))
+
+
+def flag_by_exact_value(*preds: EnumPred) -> Provider:
+    """Provider that represents flag members to the outside world by their value without any processing.
+    It does not support flags with skipped bits and negative values (it is recommended to use ``enum.auto()``
+    to define flag values instead of manually specifying them).
+
+    :param preds: Predicates specifying where the provider should be used.
+        The provider will be applied if any predicates meet the conditions,
+        if no predicates are passed, the provider will be used for all Flags.
+        See :ref:`predicate-system` for details.
+    :return: Desired provider
+    """
+    return bound_by_any(preds, FlagByExactValueProvider())
+
+
+def flag_by_member_names(
+    *preds: EnumPred,
+    allow_single_value: bool = False,
+    allow_duplicates: bool = True,
+    allow_compound: bool = True,
+    name_style: Optional[NameStyle] = None,
+    map: Optional[Mapping[Union[str, Enum], str]] = None  # noqa: A002
+) -> Provider:
+    """Provider that represents flag members to the outside world by list of their names.
+
+    Loader takes a flag members name list and returns united flag member
+    (given members combined by operator ``|``, namely `bitwise or`).
+
+    Dumper takes a flag member and returns a list of names of flag members, included in the given flag member.
+
+    :param preds: Predicates specifying where the provider should be used.
+        The provider will be applied if any predicates meet the conditions,
+        if no predicates are passed, the provider will be used for all Flags.
+        See :ref:`predicate-system` for details.
+    :param allow_single_value: Allows calling the loader with a single value.
+        If this is allowed, singlular values are treated as one element list.
+    :param allow_duplicates: Allows calling the loader with a list containing non-unique elements.
+        Unless this is allowed, loader will raise :exc:`.DuplicatedValuesLoadError` in that case.
+    :param allow_compound: Allows the loader to accept names of compound members
+        (e.g. ``WHITE = RED | GREEN | BLUE``) and the dumper to return names of compound members.
+        If this is allowed, dumper will use compound members names to serialize value.
+    :param name_style: Name style for representing members to the outside world.
+        If it is set, the provider will automatically convert the names of all flag members to the specified convention.
+    :param map: Mapping for representing members to the outside world.
+        If it is set, the provider will use it to rename members individually;
+        its keys can either be member names as strings or member instances.
+    :return: Desired provider
+    """
+    return bound_by_any(
+        preds,
+        FlagByListProvider(
+            ByNameEnumMappingGenerator(name_style=name_style, map=map),
+            allow_single_value=allow_single_value,
+            allow_duplicates=allow_duplicates,
+            allow_compound=allow_compound,
+        ),
+    )
 
 
 def validator(
@@ -357,7 +419,7 @@ def validator(
 ) -> Provider:
     # pylint: disable=C3001
     exception_factory = (
-        (lambda x: ValidationError(error, x))
+        (lambda x: ValidationLoadError(error, x))
         if error is None or isinstance(error, str) else
         error
     )
@@ -368,3 +430,13 @@ def validating_loader(data):
         raise exception_factory(data)
 
     return loader(pred, validating_loader, chain)
+
+
+def default_dict(pred: Pred, default_factory: Callable) -> Provider:
+    """DefaultDict provider with overriden default_factory parameter
+
+    :param pred: Predicate specifying where the provider should be used.
+        See :ref:`predicate-system` for details.
+    :param default_factory: default_factory parameter of the defaultdict instance to be created by the loader
+    """
+    return bound(pred, DefaultDictProvider(default_factory))
diff --git a/src/adaptix/_internal/morphing/facade/retort.py b/src/adaptix/_internal/morphing/facade/retort.py
index beb6a91d..14e4688d 100644
--- a/src/adaptix/_internal/morphing/facade/retort.py
+++ b/src/adaptix/_internal/morphing/facade/retort.py
@@ -11,16 +11,10 @@
 from ...common import Dumper, Loader, TypeHint, VarTuple
 from ...definitions import DebugTrail
 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, StrictCoercionRequest, TypeHintLoc
-from ...provider.request_filtering import P
-from ...provider.shape_provider import (
-    ATTRS_SHAPE_PROVIDER,
-    CLASS_INIT_SHAPE_PROVIDER,
-    DATACLASS_SHAPE_PROVIDER,
-    NAMED_TUPLE_SHAPE_PROVIDER,
-    TYPED_DICT_SHAPE_PROVIDER,
-)
+from ...provider.request_cls import DebugTrailRequest, LocMap, 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
 from ...type_tools.basic_utils import is_generic_class
@@ -42,8 +36,7 @@
     SelfTypeProvider,
 )
 from ..constant_length_tuple_provider import ConstantLengthTupleProvider
-from ..dict_provider import DictProvider
-from ..enum_provider import EnumExactValueProvider
+from ..dict_provider import DefaultDictProvider, DictProvider
 from ..generic_provider import (
     LiteralProvider,
     NewTypeUnwrappingProvider,
@@ -60,7 +53,7 @@
 from ..name_layout.provider import BuiltinNameLayoutProvider
 from ..provider_template import ABCProxy
 from ..request_cls import DumperRequest, LoaderRequest
-from .provider import as_is_dumper, as_is_loader, dumper, loader, name_mapping
+from .provider import as_is_dumper, as_is_loader, dumper, enum_by_exact_value, flag_by_exact_value, loader, name_mapping
 
 
 class FilledRetort(OperatingRetort, ABC):
@@ -79,7 +72,8 @@ class FilledRetort(OperatingRetort, ABC):
         IsoFormatProvider(time),
         SecondsTimedeltaProvider(),
 
-        EnumExactValueProvider(),  # it has higher priority than scalar types for Enum with mixins
+        flag_by_exact_value(),
+        enum_by_exact_value(),  # it has higher priority than scalar types for Enum with mixins
 
         INT_LOADER_PROVIDER,
         as_is_dumper(int),
@@ -135,6 +129,7 @@ class FilledRetort(OperatingRetort, ABC):
         ConstantLengthTupleProvider(),
         IterableProvider(),
         DictProvider(),
+        DefaultDictProvider(),
         RegexPatternProvider(),
         SelfTypeProvider(),
         LiteralStringProvider(),
@@ -164,12 +159,7 @@ class FilledRetort(OperatingRetort, ABC):
         ModelLoaderProvider(),
         ModelDumperProvider(),
 
-        NAMED_TUPLE_SHAPE_PROVIDER,
-        TYPED_DICT_SHAPE_PROVIDER,
-        DATACLASS_SHAPE_PROVIDER,
-        ATTRS_SHAPE_PROVIDER,
-
-        CLASS_INIT_SHAPE_PROVIDER,
+        BUILTIN_SHAPE_PROVIDER,
 
         NewTypeUnwrappingProvider(),
         TypeHintTagsUnwrappingProvider(),
@@ -243,7 +233,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_map=LocMap(TypeHintLoc(type=tp))),
+            LoaderRequest(loc_stack=LocStack(LocMap(TypeHintLoc(type=tp)))),
             error_message=f'Cannot produce loader for type {tp!r}',
         )
         if self._debug_trail == DebugTrail.FIRST:
@@ -269,7 +259,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_map=LocMap(TypeHintLoc(type=tp))),
+            DumperRequest(loc_stack=LocStack(LocMap(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 a4dac014..18bf683c 100644
--- a/src/adaptix/_internal/morphing/generic_provider.py
+++ b/src/adaptix/_internal/morphing/generic_provider.py
@@ -3,7 +3,7 @@
 from enum import Enum
 from os import PathLike
 from pathlib import Path
-from typing import Any, Collection, Iterable, Literal, Union
+from typing import Any, Collection, Dict, Iterable, Literal, Sequence, Set, Type, Union
 
 from ..common import Dumper, Loader
 from ..compat import CompatExceptionGroup
@@ -17,6 +17,7 @@
     GenericParamLoc,
     LocatedRequest,
     LocMap,
+    LocStack,
     StrictCoercionRequest,
     TypeHintLoc,
     get_type_from_request,
@@ -26,7 +27,7 @@
 from ..special_cases_optimization import as_is_stub
 from ..type_tools import BaseNormType, NormTypeAlias, is_new_type, is_subclass_soft, strip_tags
 from ..type_tools.norm_utils import strip_annotated
-from .load_error import BadVariantError, LoadError, TypeLoadError, UnionLoadError
+from .load_error import BadVariantLoadError, LoadError, TypeLoadError, UnionLoadError
 from .provider_template import DumperProvider, LoaderProvider
 from .request_cls import DumperRequest, LoaderRequest
 
@@ -34,7 +35,7 @@
 class NewTypeUnwrappingProvider(StaticProvider):
     @static_provision_action
     def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Loader:
-        loc = request.loc_map.get_or_raise(TypeHintLoc, CannotProvide)
+        loc = request.last_map.get_or_raise(TypeHintLoc, CannotProvide)
 
         if not is_new_type(loc.type):
             raise CannotProvide
@@ -42,7 +43,7 @@ def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Lo
         return mediator.delegating_provide(
             replace(
                 request,
-                loc_map=request.loc_map.add(TypeHintLoc(type=loc.type.__supertype__))
+                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(type=loc.type.__supertype__))
             ),
         )
 
@@ -50,7 +51,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.loc_map.get_or_raise(TypeHintLoc, CannotProvide)
+        loc = request.last_map.get_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
@@ -59,7 +60,7 @@ def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Lo
         return mediator.delegating_provide(
             replace(
                 request,
-                loc_map=request.loc_map.add(TypeHintLoc(type=unwrapped.source))
+                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(type=unwrapped.source))
             ),
         )
 
@@ -67,7 +68,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.loc_map.get_or_raise(TypeHintLoc, CannotProvide)
+        loc = request.last_map.get_or_raise(TypeHintLoc, CannotProvide)
         norm = try_normalize_type(loc.type)
         if not isinstance(norm, NormTypeAlias):
             raise CannotProvide
@@ -80,7 +81,7 @@ def _provide_unwrapping(self, mediator: Mediator, request: LocatedRequest) -> Lo
         return mediator.delegating_provide(
             replace(
                 request,
-                loc_map=request.loc_map.add(TypeHintLoc(type=unwrapped))
+                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(type=unwrapped))
             ),
         )
 
@@ -99,19 +100,102 @@ def _get_allowed_values_collection(self, args: Collection) -> Collection:
             return set(args)
         return tuple(args)
 
+    def _get_allowed_values_repr(self, args: Collection, mediator: Mediator, loc_stack: LocStack) -> Collection:
+        enum_cases = [arg for arg in args if isinstance(arg, Enum)]
+        if not enum_cases:
+            return set(args)
+
+        literal_dumper = self._provide_dumper(mediator, DumperRequest(loc_stack))
+        return {literal_dumper(arg) if isinstance(arg, Enum) else arg for arg in args}
+
+    def _get_enum_types(self, cases: Collection) -> Collection:
+        seen: Set[Type[Enum]] = set()
+        enum_types = []
+        for case in cases:
+            case_type = type(case)
+            if case_type not in seen:
+                enum_types.append(case_type)
+                seen.add(case_type)
+        return enum_types
+
+    def _fetch_enum_loaders(
+        self, mediator: Mediator, request: LoaderRequest, enum_classes: Iterable[Type[Enum]]
+    ) -> Iterable[Loader[Enum]]:
+        requests = [
+            LoaderRequest(
+                loc_stack=request.loc_stack.append_with(
+                    LocMap(
+                        TypeHintLoc(type=enum_cls),
+                    )
+                )
+            ) for enum_cls in enum_classes
+        ]
+        return mediator.mandatory_provide_by_iterable(
+            requests,
+            lambda: 'Cannot create loaders for enum. Loader for literal cannot be created',
+        )
+
+    def _fetch_enum_dumpers(
+        self, mediator: Mediator, request: DumperRequest, enum_classes: Iterable[Type[Enum]]
+    ) -> Dict[Type[Enum], Dumper[Enum]]:
+        requests = [
+            DumperRequest(
+                loc_stack=request.loc_stack.append_with(
+                    LocMap(
+                        TypeHintLoc(type=enum_cls),
+                    )
+                )
+            ) for enum_cls in enum_classes
+        ]
+        dumpers = mediator.mandatory_provide_by_iterable(
+            requests,
+            lambda: 'Cannot create loaders for enum. Loader for literal cannot be created',
+        )
+        return dict(zip(enum_classes, dumpers))
+
+    def _get_literal_loader_with_enum(  # noqa: CCR001
+        self, basic_loader: Loader, enum_loaders: Sequence[Loader[Enum]], allowed_values: Collection
+    ) -> Loader:
+        if not enum_loaders:
+            return basic_loader
+
+        if len(enum_loaders) == 1:
+            enum_loader = enum_loaders[0]
+
+            def wrapped_loader_with_single_enum(data):
+                try:
+                    enum_value = enum_loader(data)
+                except LoadError:
+                    pass
+                else:
+                    if enum_value in allowed_values:
+                        return enum_value
+                return basic_loader(data)
+
+            return wrapped_loader_with_single_enum
+
+        def wrapped_loader_with_enums(data):
+            for loader in enum_loaders:
+                try:
+                    enum_value = loader(data)
+                except LoadError:
+                    pass
+                else:
+                    if enum_value in allowed_values:
+                        return enum_value
+            return basic_loader(data)
+
+        return wrapped_loader_with_enums
+
     def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         norm = try_normalize_type(get_type_from_request(request))
-        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_map=request.loc_map))
+        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
 
         cleaned_args = [strip_annotated(arg) for arg in norm.args]
 
         enum_cases = [arg for arg in cleaned_args if isinstance(arg, Enum)]
-        if enum_cases:
-            raise CannotProvide(
-                f"Enum inside Literal isn't supported yet, found {enum_cases}",
-                is_terminal=True,
-                is_demonstrative=True
-            )
+        enum_loaders = list(self._fetch_enum_loaders(mediator, request, self._get_enum_types(enum_cases)))
+        allowed_values_repr = self._get_allowed_values_repr(cleaned_args, mediator, request.loc_stack)
 
         if strict_coercion and any(
             isinstance(arg, bool) or _is_exact_zero_or_one(arg)
@@ -120,53 +204,69 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
             allowed_values_with_types = self._get_allowed_values_collection(
                 [(type(el), el) for el in cleaned_args]
             )
-            allowed_values_repr = set(cleaned_args)
 
             # since True == 1 and False == 0
             def literal_loader_sc(data):
                 if (type(data), data) in allowed_values_with_types:
                     return data
-                raise BadVariantError(allowed_values_repr, data)
+                raise BadVariantLoadError(allowed_values_repr, data)
 
-            return literal_loader_sc
+            return self._get_literal_loader_with_enum(
+                literal_loader_sc, enum_loaders, allowed_values_with_types
+            )
 
         allowed_values = self._get_allowed_values_collection(cleaned_args)
-        allowed_values_repr = set(cleaned_args)
 
         def literal_loader(data):
             if data in allowed_values:
                 return data
-            raise BadVariantError(allowed_values_repr, data)
+            raise BadVariantLoadError(allowed_values_repr, data)
 
-        return literal_loader
+        return self._get_literal_loader_with_enum(literal_loader, enum_loaders, allowed_values)
 
     def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         norm = try_normalize_type(get_type_from_request(request))
         cleaned_args = [strip_annotated(arg) for arg in norm.args]
         enum_cases = [arg for arg in cleaned_args if isinstance(arg, Enum)]
-        if enum_cases:
-            raise CannotProvide(
-                f"Enum inside Literal isn't supported yet, found {enum_cases}",
-                is_terminal=True,
-                is_demonstrative=True
-            )
 
-        return as_is_stub
+        if not enum_cases:
+            return as_is_stub
+
+        enum_dumpers = self._fetch_enum_dumpers(mediator, request, self._get_enum_types(enum_cases))
+
+        if len(enum_dumpers) == 1:
+            enum_dumper = list(enum_dumpers.values())[0]
+
+            def literal_dumper_with_single_enum(data):
+                if isinstance(data, Enum):
+                    return enum_dumper(data)
+                return data
+
+            return literal_dumper_with_single_enum
+
+        def literal_dumper_with_enums(data):
+            if isinstance(data, Enum):
+                return enum_dumpers[type(data)](data)
+            return data
+
+        return literal_dumper_with_enums
 
 
 @for_predicate(Union)
 class UnionProvider(LoaderProvider, DumperProvider):
     def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         norm = try_normalize_type(get_type_from_request(request))
-        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_map=request.loc_map))
+        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
 
         if self._is_single_optional(norm):
             not_none = next(case for case in norm.args if case.origin is not None)
             not_none_loader = mediator.mandatory_provide(
                 LoaderRequest(
-                    loc_map=LocMap(
-                        TypeHintLoc(type=not_none.source),
-                        GenericParamLoc(generic_pos=0),
+                    loc_stack=request.loc_stack.append_with(
+                        LocMap(
+                            TypeHintLoc(type=not_none.source),
+                            GenericParamLoc(generic_pos=0),
+                        )
                     )
                 ),
                 lambda x: 'Cannot create loader for union. Loaders for some union cases cannot be created',
@@ -180,9 +280,11 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         loaders = mediator.mandatory_provide_by_iterable(
             [
                 LoaderRequest(
-                    loc_map=LocMap(
-                        TypeHintLoc(type=tp.source),
-                        GenericParamLoc(generic_pos=i),
+                    loc_stack=request.loc_stack.append_with(
+                        LocMap(
+                            TypeHintLoc(type=tp.source),
+                            GenericParamLoc(generic_pos=i),
+                        )
                     )
                 )
                 for i, tp in enumerate(norm.args)
@@ -278,9 +380,11 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
             not_none = next(case for case in norm.args if case.origin is not None)
             not_none_dumper = mediator.mandatory_provide(
                 DumperRequest(
-                    loc_map=LocMap(
-                        TypeHintLoc(type=not_none.source),
-                        GenericParamLoc(generic_pos=0),
+                    loc_stack=request.loc_stack.append_with(
+                        LocMap(
+                            TypeHintLoc(type=not_none.source),
+                            GenericParamLoc(generic_pos=0),
+                        )
                     )
                 ),
                 lambda x: 'Cannot create dumper for union. Dumpers for some union cases cannot be created',
@@ -300,9 +404,11 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         dumpers = mediator.mandatory_provide_by_iterable(
             [
                 DumperRequest(
-                    loc_map=LocMap(
-                        TypeHintLoc(type=tp.source),
-                        GenericParamLoc(generic_pos=i),
+                    loc_stack=request.loc_stack.append_with(
+                        LocMap(
+                            TypeHintLoc(type=tp.source),
+                            GenericParamLoc(generic_pos=i),
+                        )
                     )
                 )
                 for i, tp in enumerate(norm.args)
@@ -343,7 +449,7 @@ class PathLikeProvider(LoaderProvider, DumperProvider):
     def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         return mediator.mandatory_provide(
             LoaderRequest(
-                loc_map=LocMap(TypeHintLoc(type=self._impl))
+                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(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 442f6242..9eaed81e 100644
--- a/src/adaptix/_internal/morphing/iterable_provider.py
+++ b/src/adaptix/_internal/morphing/iterable_provider.py
@@ -73,15 +73,17 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         iter_factory = self._get_iter_factory(norm.origin)
         arg_loader = mediator.mandatory_provide(
             LoaderRequest(
-                loc_map=LocMap(
-                    TypeHintLoc(type=arg),
-                    GenericParamLoc(generic_pos=0),
+                loc_stack=request.loc_stack.append_with(
+                    LocMap(
+                        TypeHintLoc(type=arg),
+                        GenericParamLoc(generic_pos=0),
+                    )
                 )
             ),
             lambda x: 'Cannot create loader for iterable. Loader for element cannot be created',
         )
-        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_map=request.loc_map))
-        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_map=request.loc_map))
+        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
+        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
         return self._make_loader(
             origin=norm.origin,
             iter_factory=iter_factory,
@@ -212,14 +214,16 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         iter_factory = self._get_iter_factory(norm.origin)
         arg_dumper = mediator.mandatory_provide(
             DumperRequest(
-                loc_map=LocMap(
-                    TypeHintLoc(type=arg),
-                    GenericParamLoc(generic_pos=0),
+                loc_stack=request.loc_stack.append_with(
+                    LocMap(
+                        TypeHintLoc(type=arg),
+                        GenericParamLoc(generic_pos=0),
+                    )
                 )
             ),
             lambda x: 'Cannot create dumper for iterable. Dumper for element cannot be created',
         )
-        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_map=request.loc_map))
+        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
         return self._make_dumper(
             origin=norm.origin,
             iter_factory=iter_factory,
diff --git a/src/adaptix/_internal/morphing/load_error.py b/src/adaptix/_internal/morphing/load_error.py
index f24632bc..6f7a7821 100644
--- a/src/adaptix/_internal/morphing/load_error.py
+++ b/src/adaptix/_internal/morphing/load_error.py
@@ -1,7 +1,7 @@
 import dataclasses
 from dataclasses import dataclass
 from functools import partial
-from typing import Any, Iterable, Optional
+from typing import Any, Iterable, Optional, Union
 
 from ..common import TypeHint, VarTuple
 from ..compat import CompatExceptionGroup
@@ -48,6 +48,10 @@ class LoadExceptionGroup(CompatExceptionGroup[LoadError], LoadError):
     message: str
     exceptions: VarTuple[LoadError]
 
+    # stub `__init__` is required for right introspection
+    def __init__(self, message: str, exceptions: VarTuple[LoadError]):
+        pass
+
 
 @custom_exception(str_by_fields=False)
 @dataclass(eq=False, init=False)
@@ -63,35 +67,35 @@ class UnionLoadError(LoadExceptionGroup):
 
 @custom_exception
 @dataclass(eq=False)
-class MsgError(LoadError):
+class MsgLoadError(LoadError):
     msg: Optional[str]
     input_value: Any
 
 
 @custom_exception
 @dataclass(eq=False)
-class ExtraFieldsError(LoadError):
+class ExtraFieldsLoadError(LoadError):
     fields: Iterable[str]
     input_value: Any
 
 
 @custom_exception
 @dataclass(eq=False)
-class ExtraItemsError(LoadError):
+class ExtraItemsLoadError(LoadError):
     expected_len: int
     input_value: Any
 
 
 @custom_exception
 @dataclass(eq=False)
-class NoRequiredFieldsError(LoadError):
+class NoRequiredFieldsLoadError(LoadError):
     fields: Iterable[str]
     input_value: Any
 
 
 @custom_exception
 @dataclass(eq=False)
-class NoRequiredItemsError(LoadError):
+class NoRequiredItemsLoadError(LoadError):
     expected_len: int
     input_value: Any
 
@@ -113,25 +117,47 @@ class ExcludedTypeLoadError(TypeLoadError):
 
 @custom_exception(str_by_fields=False)
 @dataclass(eq=False)
-class ValueLoadError(MsgError):
+class ValueLoadError(MsgLoadError):
     pass
 
 
 @custom_exception(str_by_fields=False)
 @dataclass(eq=False)
-class ValidationError(MsgError):
+class ValidationLoadError(MsgLoadError):
     pass
 
 
 @custom_exception
 @dataclass(eq=False)
-class BadVariantError(LoadError):
+class BadVariantLoadError(LoadError):
     allowed_values: Iterable[Any]
     input_value: Any
 
 
 @custom_exception
 @dataclass(eq=False)
-class DatetimeFormatMismatch(LoadError):
+class MultipleBadVariantLoadError(LoadError):
+    allowed_values: Iterable[Any]
+    invalid_values: Iterable[Any]
+    input_value: Any
+
+
+@custom_exception
+@dataclass(eq=False)
+class FormatMismatchLoadError(LoadError):
     format: str
     input_value: Any
+
+
+@custom_exception
+@dataclass(eq=False)
+class DuplicatedValuesLoadError(LoadError):
+    input_value: Any
+
+
+@custom_exception
+@dataclass(eq=False)
+class OutOfRangeLoadError(LoadError):
+    min_value: Optional[Union[int, float]]
+    max_value: Optional[Union[int, float]]
+    input_value: Any
diff --git a/src/adaptix/_internal/morphing/model/basic_gen.py b/src/adaptix/_internal/morphing/model/basic_gen.py
index 38e8e493..67f6caa1 100644
--- a/src/adaptix/_internal/morphing/model/basic_gen.py
+++ b/src/adaptix/_internal/morphing/model/basic_gen.py
@@ -1,8 +1,10 @@
 import itertools
 import re
 import string
-from dataclasses import dataclass, replace
+from abc import ABC, abstractmethod
+from dataclasses import dataclass
 from typing import (
+    AbstractSet,
     Any,
     Callable,
     Collection,
@@ -11,7 +13,6 @@
     Iterable,
     List,
     Mapping,
-    Sequence,
     Set,
     Tuple,
     TypeVar,
@@ -21,9 +22,9 @@
 from ...code_tools.code_builder import CodeBuilder
 from ...code_tools.compiler import ClosureCompiler
 from ...code_tools.utils import get_literal_expr
-from ...model_tools.definitions import InputField, InputShape, OutputField, OutputShape
-from ...provider.essential import Mediator, Request
-from ...provider.request_cls import LocatedRequest, TypeHintLoc
+from ...model_tools.definitions import InputField, OutputField
+from ...provider.essential import CannotProvide, Mediator
+from ...provider.request_cls import LocatedRequest, LocStack, TypeHintLoc
 from ...provider.static_provider import StaticProvider, static_provision_action
 from .crown_definitions import (
     BaseCrown,
@@ -59,35 +60,38 @@ def stub_code_gen_hook(data: CodeGenHookData):
 
 
 @dataclass(frozen=True)
-class CodeGenHookRequest(Request[CodeGenHook]):
+class CodeGenHookRequest(LocatedRequest[CodeGenHook]):
     pass
 
 
+def fetch_code_gen_hook(mediator: Mediator, loc_stack: LocStack) -> CodeGenHook:
+    try:
+        return mediator.delegating_provide(CodeGenHookRequest(loc_stack=loc_stack))
+    except CannotProvide:
+        return stub_code_gen_hook
+
+
 class CodeGenAccumulator(StaticProvider):
     """Accumulates all generated code. It may be useful for debugging"""
 
     def __init__(self) -> None:
-        self.list: List[Tuple[Sequence[Request], CodeGenHookData]] = []
+        self.list: List[Tuple[CodeGenHookRequest, CodeGenHookData]] = []
 
     @static_provision_action
     def _provide_code_gen_hook(self, mediator: Mediator, request: CodeGenHookRequest) -> CodeGenHook:
-        request_stack = mediator.request_stack
-
         def hook(data: CodeGenHookData):
-            self.list.append((request_stack, data))
+            self.list.append((request, data))
 
         return hook
 
     @property
     def code_pairs(self):
         return [
-            (request_stack[-2].loc_map[TypeHintLoc].type, hook_data.source)
-            for request_stack, hook_data in self.list
+            (request.loc_stack[-2].loc_map[TypeHintLoc].type, hook_data.source)
+            for request, hook_data in self.list
             if (
-                len(request_stack) >= 2
-                and isinstance(request_stack[-2], LocatedRequest)
-                and request_stack[-2].loc_map.has(TypeHintLoc)
-                and request_stack[-2].loc_map[TypeHintLoc].type
+                len(request.loc_stack) >= 2
+                and request.loc_stack[-2].loc_map.has(TypeHintLoc)
             )
         ]
 
@@ -133,17 +137,17 @@ def _collect_used_direct_fields(crown: BaseCrown) -> Set[str]:
     return used_set
 
 
-def get_skipped_fields(shape: BaseShape, name_layout: BaseNameLayout) -> Collection[str]:
+def get_skipped_fields(shape: BaseShape, name_layout: BaseNameLayout) -> AbstractSet[str]:
     used_direct_fields = _collect_used_direct_fields(name_layout.crown)
     if isinstance(name_layout.extra_move, ExtraTargets):
         extra_targets = name_layout.extra_move.fields
     else:
         extra_targets = ()
 
-    return [
+    return {
         field.id for field in shape.fields
         if field.id not in used_direct_fields and field.id not in extra_targets
-    ]
+    }
 
 
 def _inner_get_extra_targets_at_crown(extra_targets: Container[str], crown: BaseCrown) -> Collection[str]:
@@ -206,47 +210,6 @@ def get_wild_extra_targets(shape: BaseShape, extra_move: Union[InpExtraMove, Out
     ]
 
 
-def strip_input_shape_fields(shape: InputShape, skipped_fields: Collection[str]) -> InputShape:
-    skipped_required_fields = [
-        field.id
-        for field in shape.fields
-        if field.is_required and field.id in skipped_fields
-    ]
-    if skipped_required_fields:
-        raise ValueError(
-            f"Required fields {skipped_required_fields} are skipped"
-        )
-    return replace(
-        shape,
-        fields=tuple(
-            field for field in shape.fields
-            if field.id not in skipped_fields
-        ),
-        params=tuple(
-            param for param in shape.params
-            if param.field_id not in skipped_fields
-        ),
-        overriden_types=frozenset(
-            field.id for field in shape.fields
-            if field.id not in skipped_fields
-        ),
-    )
-
-
-def strip_output_shape_fields(shape: OutputShape, skipped_fields: Collection[str]) -> OutputShape:
-    return replace(
-        shape,
-        fields=tuple(
-            field for field in shape.fields
-            if field.id not in skipped_fields
-        ),
-        overriden_types=frozenset(
-            field.id for field in shape.fields
-            if field.id not in skipped_fields
-        )
-    )
-
-
 class NameSanitizer:
     _BAD_CHARS = re.compile(r'\W')
     _TRANSLATE_MAP = str.maketrans({'.': '_', '[': '_'})
@@ -262,11 +225,10 @@ def sanitize(self, name: str) -> str:
 def compile_closure_with_globals_capturing(
     compiler: ClosureCompiler,
     code_gen_hook: CodeGenHook,
-    namespace: Dict[str, object],
-    body_builders: Iterable[CodeBuilder],
+    namespace: Mapping[str, object],
     *,
     closure_name: str,
-    closure_params: str,
+    closure_code: str,
     file_name: str,
 ):
     builder = CodeBuilder()
@@ -282,11 +244,7 @@ def compile_closure_with_globals_capturing(
             builder += f"{name} = {value_literal}"
 
     builder.empty_line()
-
-    with builder(f"def {closure_name}({closure_params}):"):
-        for body_builder in body_builders:
-            builder.extend(body_builder)
-
+    builder += closure_code
     builder += f"return {closure_name}"
 
     code_gen_hook(
@@ -318,3 +276,15 @@ def has_collect_policy(crown: InpCrown) -> bool:
     if isinstance(crown, (InpFieldCrown, InpNoneCrown)):
         return False
     raise TypeError
+
+
+class ModelLoaderGen(ABC):
+    @abstractmethod
+    def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
+        ...
+
+
+class ModelDumperGen(ABC):
+    @abstractmethod
+    def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
+        ...
diff --git a/src/adaptix/_internal/morphing/model/dumper_gen.py b/src/adaptix/_internal/morphing/model/dumper_gen.py
index 86f450c3..927292a7 100644
--- a/src/adaptix/_internal/morphing/model/dumper_gen.py
+++ b/src/adaptix/_internal/morphing/model/dumper_gen.py
@@ -1,10 +1,9 @@
 import contextlib
 from string import Template
-from typing import Dict, Mapping, NamedTuple
+from typing import Dict, Mapping, NamedTuple, Tuple
 
-from ...code_generator import CodeGenerator
+from ...code_tools.cascade_namespace import BuiltinCascadeNamespace, CascadeNamespace
 from ...code_tools.code_builder import CodeBuilder
-from ...code_tools.context_namespace import ContextNamespace
 from ...code_tools.utils import get_literal_expr, get_literal_from_factory, is_singleton
 from ...common import Dumper
 from ...compat import CompatExceptionGroup
@@ -20,6 +19,7 @@
 )
 from ...special_cases_optimization import as_is_stub, get_default_clause
 from ...struct_trail import append_trail, extend_trail, render_trail_as_note
+from .basic_gen import ModelDumperGen, get_skipped_fields
 from .crown_definitions import (
     CrownPath,
     CrownPathElem,
@@ -36,9 +36,9 @@
 
 
 class GenState:
-    def __init__(self, builder: CodeBuilder, ctx_namespace: ContextNamespace):
+    def __init__(self, builder: CodeBuilder, namespace: CascadeNamespace):
         self.builder = builder
-        self.ctx_namespace = ctx_namespace
+        self.namespace = namespace
 
         self.field_id_to_path: Dict[str, CrownPath] = {}
         self.path_to_suffix: Dict[CrownPath, str] = {}
@@ -93,7 +93,7 @@ class ElementExpr(NamedTuple):
     can_inline: bool
 
 
-class ModelDumperGen(CodeGenerator):
+class BuiltinModelDumperGen(ModelDumperGen):
     def __init__(
         self,
         shape: OutputShape,
@@ -114,35 +114,39 @@ def __init__(
         self._id_to_field: Dict[str, OutputField] = {field.id: field for field in self._shape.fields}
         self._model_identity = model_identity
 
-    def produce_code(self, ctx_namespace: ContextNamespace) -> CodeBuilder:
-        builder = CodeBuilder()
+    def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
+        body_builder = CodeBuilder()
 
-        ctx_namespace.add('CompatExceptionGroup', CompatExceptionGroup)
-        ctx_namespace.add("append_trail", append_trail)
-        ctx_namespace.add("extend_trail", extend_trail)
+        namespace = BuiltinCascadeNamespace()
+        namespace.add_constant('CompatExceptionGroup', CompatExceptionGroup)
+        namespace.add_constant("append_trail", append_trail)
+        namespace.add_constant("extend_trail", extend_trail)
         for field_id, dumper in self._fields_dumpers.items():
-            ctx_namespace.add(self._v_dumper(self._id_to_field[field_id]), dumper)
+            namespace.add_constant(self._v_dumper(self._id_to_field[field_id]), dumper)
 
         if self._debug_trail == DebugTrail.ALL:
-            builder('errors = []')
-            builder.empty_line()
+            body_builder('errors = []')
+            body_builder.empty_line()
 
         if any(field.is_optional for field in self._shape.fields):
-            builder("opt_fields = {}")
-            builder.empty_line()
+            body_builder("opt_fields = {}")
+            body_builder.empty_line()
 
+        skipped_fields = get_skipped_fields(self._shape, self._name_layout)
         for field in self._shape.fields:
+            if field.id in skipped_fields:
+                continue
             if not self._is_extra_target(field):
                 self._gen_field_extraction(
-                    builder, ctx_namespace, field,
+                    body_builder, namespace, field,
                     on_access_error="pass",
                     on_access_ok_req=f"{self._v_field(field)} = $expr",
                     on_access_ok_opt=f"opt_fields[{field.id!r}] = $expr",
                 )
 
-        self._gen_extra_extraction(builder, ctx_namespace)
+        self._gen_extra_extraction(body_builder, namespace)
 
-        state = self._create_state(builder, ctx_namespace)
+        state = self._create_state(body_builder, namespace)
 
         if not self._gen_root_crown_dispatch(state, self._name_layout.crown):
             raise TypeError
@@ -155,7 +159,11 @@ def produce_code(self, ctx_namespace: ContextNamespace) -> CodeBuilder:
             )
 
         self._gen_header(state)
-        return builder
+
+        builder = CodeBuilder()
+        with builder(f'def {closure_name}(data):'):
+            builder.extend(body_builder)
+        return builder.string(), namespace.constants
 
     def _is_extra_target(self, field: OutputField) -> bool:
         return field.id in self._extra_targets
@@ -178,41 +186,39 @@ def _v_trail_element(self, field: OutputField) -> str:
     def _v_access_error(self, field: OutputField) -> str:
         return f"access_error_{field.id}"
 
-    def _gen_access_expr(self, ctx_namespace: ContextNamespace, field: OutputField) -> str:
+    def _gen_access_expr(self, namespace: CascadeNamespace, field: OutputField) -> str:
         accessor = field.accessor
         if isinstance(accessor, DescriptorAccessor):
             if accessor.attr_name.isidentifier():
                 return f"data.{accessor.attr_name}"
             return f"getattr(data, {accessor.attr_name!r})"
         if isinstance(accessor, ItemAccessor):
-            literal_expr = get_literal_expr(accessor.key)
-            if literal_expr is not None:
-                return f"data[{literal_expr}]"
+            return f"data[{accessor.key!r}]"
 
         accessor_getter = self._v_accessor_getter(field)
-        ctx_namespace.add(accessor_getter, field.accessor.getter)
+        namespace.add_constant(accessor_getter, field.accessor.getter)
         return f"{accessor_getter}(data)"
 
-    def _get_trail_element_expr(self, ctx_namespace: ContextNamespace, field: OutputField) -> str:
+    def _get_trail_element_expr(self, namespace: CascadeNamespace, field: OutputField) -> str:
         trail_element = field.accessor.trail_element
         literal_expr = get_literal_expr(trail_element)
         if literal_expr is not None:
             return literal_expr
 
         v_trail_element = self._v_trail_element(field)
-        ctx_namespace.add(v_trail_element, trail_element)
+        namespace.add_constant(v_trail_element, trail_element)
         return v_trail_element
 
     def _gen_required_field_extraction(
         self,
         builder: CodeBuilder,
-        ctx_namespace: ContextNamespace,
+        namespace: CascadeNamespace,
         field: OutputField,
         *,
         on_access_ok: str,
     ):
-        raw_access_expr = self._gen_access_expr(ctx_namespace, field)
-        v_element_expr = self._get_trail_element_expr(ctx_namespace, field)
+        raw_access_expr = self._gen_access_expr(namespace, field)
+        v_element_expr = self._get_trail_element_expr(namespace, field)
 
         if self._fields_dumpers[field.id] == as_is_stub:
             on_access_ok_stmt = Template(on_access_ok).substitute(expr=raw_access_expr)
@@ -243,14 +249,14 @@ def _gen_required_field_extraction(
     def _gen_optional_field_extraction(
         self,
         builder: CodeBuilder,
-        ctx_namespace: ContextNamespace,
+        namespace: CascadeNamespace,
         field: OutputField,
         *,
         on_access_error: str,
         on_access_ok: str,
     ):
-        raw_access_expr = self._gen_access_expr(ctx_namespace, field)
-        path_element_expr = self._get_trail_element_expr(ctx_namespace, field)
+        raw_access_expr = self._gen_access_expr(namespace, field)
+        path_element_expr = self._get_trail_element_expr(namespace, field)
 
         v_raw_field = self._v_raw_field(field)
         if self._fields_dumpers[field.id] == as_is_stub:
@@ -267,7 +273,7 @@ def _gen_optional_field_extraction(
         access_error_expr = get_literal_expr(access_error)
         if access_error_expr is None:
             access_error_expr = self._v_access_error(field)
-            ctx_namespace.add(access_error_expr, access_error)
+            namespace.add_constant(access_error_expr, access_error)
 
         if self._debug_trail == DebugTrail.ALL:
             builder += f"""
@@ -309,7 +315,7 @@ def _gen_optional_field_extraction(
     def _gen_field_extraction(
         self,
         builder: CodeBuilder,
-        ctx_namespace: ContextNamespace,
+        namespace: CascadeNamespace,
         field: OutputField,
         *,
         on_access_ok_req: str,
@@ -318,22 +324,22 @@ def _gen_field_extraction(
     ):
         if field.is_required:
             self._gen_required_field_extraction(
-                builder, ctx_namespace, field,
+                builder, namespace, field,
                 on_access_ok=on_access_ok_req,
             )
         else:
             self._gen_optional_field_extraction(
-                builder, ctx_namespace, field,
+                builder, namespace, field,
                 on_access_ok=on_access_ok_opt,
                 on_access_error=on_access_error,
             )
 
-    def _gen_raising_extraction_errors(self, builder: CodeBuilder, ctx_namespace):
+    def _gen_raising_extraction_errors(self, builder: CodeBuilder, namespace):
         if self._debug_trail != DebugTrail.ALL:
             return
 
-        ctx_namespace.add('model_identity', self._model_identity)
-        ctx_namespace.add('render_trail_as_note', render_trail_as_note)
+        namespace.add_constant('model_identity', self._model_identity)
+        namespace.add_constant('render_trail_as_note', render_trail_as_note)
         builder(
             """
             if errors:
@@ -347,43 +353,43 @@ def _gen_raising_extraction_errors(self, builder: CodeBuilder, ctx_namespace):
     def _gen_extra_extraction(
         self,
         builder: CodeBuilder,
-        ctx_namespace: ContextNamespace,
+        namespace: CascadeNamespace,
     ):
         if isinstance(self._name_layout.extra_move, ExtraTargets):
-            self._gen_extra_target_extraction(builder, ctx_namespace)
+            self._gen_extra_target_extraction(builder, namespace)
         elif isinstance(self._name_layout.extra_move, ExtraExtract):
-            self._gen_extra_extract_extraction(builder, ctx_namespace, self._name_layout.extra_move)
+            self._gen_extra_extract_extraction(builder, namespace, self._name_layout.extra_move)
         elif self._name_layout.extra_move is None:
-            self._gen_raising_extraction_errors(builder, ctx_namespace)
+            self._gen_raising_extraction_errors(builder, namespace)
         else:
             raise ValueError
 
     def _gen_extra_target_extraction(
         self,
         builder: CodeBuilder,
-        ctx_namespace: ContextNamespace,
+        namespace: CascadeNamespace,
     ):
         if len(self._extra_targets) == 1:
             field = self._id_to_field[self._extra_targets[0]]
 
             self._gen_field_extraction(
-                builder, ctx_namespace, field,
+                builder, namespace, field,
                 on_access_error="extra = {}",
                 on_access_ok_req="extra = $expr",
                 on_access_ok_opt="extra = $expr",
             )
-            self._gen_raising_extraction_errors(builder, ctx_namespace)
+            self._gen_raising_extraction_errors(builder, namespace)
 
         elif all(field.is_required for field in self._id_to_field.values()):
             for field_id in self._extra_targets:
                 field = self._id_to_field[field_id]
 
                 self._gen_required_field_extraction(
-                    builder, ctx_namespace, field,
+                    builder, namespace, field,
                     on_access_ok=f"{self._v_field(field)} = $expr",
                 )
 
-            self._gen_raising_extraction_errors(builder, ctx_namespace)
+            self._gen_raising_extraction_errors(builder, namespace)
             builder += 'extra = {'
             builder <<= ", ".join(
                 "**" + self._v_field(self._id_to_field[field_id])
@@ -396,13 +402,13 @@ def _gen_extra_target_extraction(
                 field = self._id_to_field[field_id]
 
                 self._gen_field_extraction(
-                    builder, ctx_namespace, field,
+                    builder, namespace, field,
                     on_access_ok_req="extra_stack.append($expr)",
                     on_access_ok_opt="extra_stack.append($expr)",
                     on_access_error="pass",
                 )
 
-            self._gen_raising_extraction_errors(builder, ctx_namespace)
+            self._gen_raising_extraction_errors(builder, namespace)
             builder += """
                 extra = {
                     key: value for extra_element in extra_stack for key, value in extra_element.items()
@@ -412,10 +418,10 @@ def _gen_extra_target_extraction(
     def _gen_extra_extract_extraction(
         self,
         builder: CodeBuilder,
-        ctx_namespace: ContextNamespace,
+        namespace: CascadeNamespace,
         extra_move: ExtraExtract,
     ):
-        ctx_namespace.add('extractor', extra_move.func)
+        namespace.add_constant('extractor', extra_move.func)
 
         if self._debug_trail == DebugTrail.ALL:
             builder += """
@@ -427,7 +433,7 @@ def _gen_extra_extract_extraction(
         else:
             builder += "extra = extractor(data)"
 
-        self._gen_raising_extraction_errors(builder, ctx_namespace)
+        self._gen_raising_extraction_errors(builder, namespace)
         builder.empty_line()
 
     def _gen_header(self, state: GenState):
@@ -448,8 +454,8 @@ def _gen_header(self, state: GenState):
 
         state.builder.extend_above(builder)
 
-    def _create_state(self, builder: CodeBuilder, ctx_namespace: ContextNamespace) -> GenState:
-        return GenState(builder, ctx_namespace)
+    def _create_state(self, builder: CodeBuilder, namespace: CascadeNamespace) -> GenState:
+        return GenState(builder, namespace)
 
     def _gen_root_crown_dispatch(self, state: GenState, crown: OutCrown):
         if isinstance(crown, OutDictCrown):
@@ -479,7 +485,7 @@ def _get_element_expr_for_none_crown(self, state: GenState, crown: OutNoneCrown)
             if literal_expr is not None:
                 return ElementExpr(literal_expr, can_inline=True)
 
-            state.ctx_namespace.add(state.v_placeholder, crown.placeholder.factory)
+            state.namespace.add_constant(state.v_placeholder, crown.placeholder.factory)
             return ElementExpr(state.v_placeholder + '()', can_inline=False)
 
         if isinstance(crown.placeholder, DefaultValue):
@@ -487,7 +493,7 @@ def _get_element_expr_for_none_crown(self, state: GenState, crown: OutNoneCrown)
             if literal_expr is not None:
                 return ElementExpr(literal_expr, can_inline=True)
 
-            state.ctx_namespace.add(state.v_placeholder, crown.placeholder.value)
+            state.namespace.add_constant(state.v_placeholder, crown.placeholder.value)
             return ElementExpr(state.v_placeholder, can_inline=True)
 
         raise TypeError
@@ -597,7 +603,7 @@ def _get_sieve_condition(self, state: GenState, sieve: Sieve, key: str, input_ex
         default_clause = get_default_clause(sieve)
         if default_clause is None:
             v_sieve = state.v_sieve(key)
-            state.ctx_namespace.add(v_sieve, sieve)
+            state.namespace.add_constant(v_sieve, sieve)
             return f'{v_sieve}({input_expr})'
 
         if isinstance(default_clause, DefaultValue):
@@ -609,7 +615,7 @@ def _get_sieve_condition(self, state: GenState, sieve: Sieve, key: str, input_ex
                     f"{input_expr} != {literal_expr}"
                 )
             v_default = state.v_default(key)
-            state.ctx_namespace.add(v_default, default_clause.value)
+            state.namespace.add_constant(v_default, default_clause.value)
             return f"{input_expr} != {v_default}"
 
         if isinstance(default_clause, DefaultFactory):
@@ -617,12 +623,12 @@ def _get_sieve_condition(self, state: GenState, sieve: Sieve, key: str, input_ex
             if literal_expr is not None:
                 return f"{input_expr} != {literal_expr}"
             v_default = state.v_default(key)
-            state.ctx_namespace.add(v_default, default_clause.factory)
+            state.namespace.add_constant(v_default, default_clause.factory)
             return f"{input_expr} != {v_default}()"
 
         if isinstance(default_clause, DefaultFactoryWithSelf):
             v_default = state.v_default(key)
-            state.ctx_namespace.add(v_default, default_clause.factory)
+            state.namespace.add_constant(v_default, default_clause.factory)
             return f"{input_expr} != {v_default}(data)"
 
         raise TypeError
diff --git a/src/adaptix/_internal/morphing/model/dumper_provider.py b/src/adaptix/_internal/morphing/model/dumper_provider.py
index edf30acc..fa4300db 100644
--- a/src/adaptix/_internal/morphing/model/dumper_provider.py
+++ b/src/adaptix/_internal/morphing/model/dumper_provider.py
@@ -1,30 +1,27 @@
 from typing import Mapping
 
-from ...code_generator import CodeGenerator
-from ...code_tools.compiler import BasicClosureCompiler
-from ...code_tools.context_namespace import BuiltinContextNamespace
+from adaptix._internal.provider.fields import output_field_to_loc_map
+
+from ...code_tools.compiler import BasicClosureCompiler, ClosureCompiler
 from ...common import Dumper
 from ...definitions import DebugTrail
 from ...model_tools.definitions import OutputShape
-from ...provider.essential import CannotProvide, Mediator
+from ...provider.essential import Mediator
 from ...provider.request_cls import DebugTrailRequest, TypeHintLoc
 from ...provider.shape_provider import OutputShapeRequest, provide_generic_resolved_shape
 from ..provider_template import DumperProvider
 from ..request_cls import DumperRequest
 from .basic_gen import (
-    CodeGenHookRequest,
+    ModelDumperGen,
     NameSanitizer,
     compile_closure_with_globals_capturing,
+    fetch_code_gen_hook,
     get_extra_targets_at_crown,
     get_optional_fields_at_list_crown,
-    get_skipped_fields,
     get_wild_extra_targets,
-    strip_output_shape_fields,
-    stub_code_gen_hook,
 )
 from .crown_definitions import OutputNameLayout, OutputNameLayoutRequest
-from .dumper_gen import ModelDumperGen
-from .fields import output_field_to_loc_map
+from .dumper_gen import BuiltinModelDumperGen
 
 
 class ModelDumperProvider(DumperProvider):
@@ -33,31 +30,24 @@ def __init__(self, *, name_sanitizer: NameSanitizer = NameSanitizer()):
 
     def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
         dumper_gen = self._fetch_model_dumper_gen(mediator, request)
-        ctx_namespace = BuiltinContextNamespace()
-        dumper_code_builder = dumper_gen.produce_code(ctx_namespace)
-
-        try:
-            code_gen_hook = mediator.delegating_provide(CodeGenHookRequest())
-        except CannotProvide:
-            code_gen_hook = stub_code_gen_hook
-
+        closure_name = self._get_closure_name(request)
+        dumper_code, dumper_namespace = dumper_gen.produce_code(closure_name=closure_name)
         return compile_closure_with_globals_capturing(
             compiler=self._get_compiler(),
-            code_gen_hook=code_gen_hook,
-            namespace=ctx_namespace.dict,
-            body_builders=[dumper_code_builder],
-            closure_name=self._get_closure_name(request),
-            closure_params='data',
+            code_gen_hook=fetch_code_gen_hook(mediator, request.loc_stack),
+            namespace=dumper_namespace,
+            closure_code=dumper_code,
+            closure_name=closure_name,
             file_name=self._get_file_name(request),
         )
 
-    def _fetch_model_dumper_gen(self, mediator: Mediator, request: DumperRequest) -> CodeGenerator:
+    def _fetch_model_dumper_gen(self, mediator: Mediator, request: DumperRequest) -> ModelDumperGen:
         shape = self._fetch_shape(mediator, request)
         name_layout = self._fetch_name_layout(mediator, request, shape)
-        shape = self._process_shape(shape, name_layout)
+        self._validate_params(shape, name_layout)
 
         fields_dumpers = self._fetch_field_dumpers(mediator, request, shape)
-        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_map=request.loc_map))
+        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
         return self._create_model_dumper_gen(
             debug_trail=debug_trail,
             shape=shape,
@@ -74,8 +64,8 @@ def _fetch_model_identity(
         name_layout: OutputNameLayout,
     ) -> str:
         return (
-            repr(request.loc_map[TypeHintLoc].type)
-            if request.loc_map.has(TypeHintLoc) else
+            repr(request.last_map[TypeHintLoc].type)
+            if request.last_map.has(TypeHintLoc) else
             '<unknown model>'
         )
 
@@ -86,8 +76,8 @@ def _create_model_dumper_gen(
         name_layout: OutputNameLayout,
         fields_dumpers: Mapping[str, Dumper],
         model_identity: str,
-    ) -> CodeGenerator:
-        return ModelDumperGen(
+    ) -> ModelDumperGen:
+        return BuiltinModelDumperGen(
             shape=shape,
             name_layout=name_layout,
             debug_trail=debug_trail,
@@ -96,8 +86,8 @@ def _create_model_dumper_gen(
         )
 
     def _request_to_view_string(self, request: DumperRequest) -> str:
-        if request.loc_map.has(TypeHintLoc):
-            tp = request.loc_map[TypeHintLoc].type
+        if request.last_map.has(TypeHintLoc):
+            tp = request.last_map[TypeHintLoc].type
             if isinstance(tp, type):
                 return tp.__name__
             return str(tp)
@@ -116,16 +106,16 @@ def _get_closure_name(self, request: DumperRequest) -> str:
             'model_dumper', self._name_sanitizer.sanitize(self._request_to_view_string(request)),
         )
 
-    def _get_compiler(self):
+    def _get_compiler(self) -> ClosureCompiler:
         return BasicClosureCompiler()
 
     def _fetch_shape(self, mediator: Mediator, request: DumperRequest) -> OutputShape:
-        return provide_generic_resolved_shape(mediator, OutputShapeRequest(loc_map=request.loc_map))
+        return provide_generic_resolved_shape(mediator, OutputShapeRequest(loc_stack=request.loc_stack))
 
     def _fetch_name_layout(self, mediator: Mediator, request: DumperRequest, shape: OutputShape) -> OutputNameLayout:
         return mediator.delegating_provide(
             OutputNameLayoutRequest(
-                loc_map=request.loc_map,
+                loc_stack=request.loc_stack,
                 shape=shape,
             )
         )
@@ -136,17 +126,16 @@ def _fetch_field_dumpers(
         request: DumperRequest,
         shape: OutputShape,
     ) -> Mapping[str, Dumper]:
-        owner_type = request.loc_map[TypeHintLoc].type
         dumpers = mediator.mandatory_provide_by_iterable(
             [
-                DumperRequest(loc_map=output_field_to_loc_map(owner_type, field))
+                DumperRequest(loc_stack=request.loc_stack.append_with(output_field_to_loc_map(field)))
                 for field in shape.fields
             ],
             lambda: "Cannot create dumper for model. Dumpers for some fields cannot be created",
         )
         return {field.id: dumper for field, dumper in zip(shape.fields, dumpers)}
 
-    def _process_shape(self, shape: OutputShape, name_layout: OutputNameLayout) -> OutputShape:
+    def _validate_params(self, shape: OutputShape, name_layout: OutputNameLayout) -> None:
         optional_fields_at_list_crown = get_optional_fields_at_list_crown(
             {field.id: field for field in shape.fields},
             name_layout.crown,
@@ -167,5 +156,3 @@ def _process_shape(self, shape: OutputShape, name_layout: OutputNameLayout) -> O
             raise ValueError(
                 f"Extra targets {extra_targets_at_crown} are found at crown"
             )
-
-        return strip_output_shape_fields(shape, get_skipped_fields(shape, name_layout))
diff --git a/src/adaptix/_internal/morphing/model/fields.py b/src/adaptix/_internal/morphing/model/fields.py
deleted file mode 100644
index 7446f577..00000000
--- a/src/adaptix/_internal/morphing/model/fields.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from ...common import TypeHint
-from ...model_tools.definitions import BaseField, InputField, OutputField
-from ...provider.request_cls import FieldLoc, InputFieldLoc, LocMap, OutputFieldLoc, TypeHintLoc
-
-
-def input_field_to_loc_map(owner_type: TypeHint, field: InputField) -> LocMap:
-    return LocMap(
-        TypeHintLoc(
-            type=field.type,
-        ),
-        FieldLoc(
-            owner_type=owner_type,
-            field_id=field.id,
-            default=field.default,
-            metadata=field.metadata,
-        ),
-        InputFieldLoc(
-            is_required=field.is_required,
-        )
-    )
-
-
-def output_field_to_loc_map(owner_type: TypeHint, field: OutputField) -> LocMap:
-    return LocMap(
-        TypeHintLoc(
-            type=field.type,
-        ),
-        FieldLoc(
-            owner_type=owner_type,
-            field_id=field.id,
-            default=field.default,
-            metadata=field.metadata,
-        ),
-        OutputFieldLoc(
-            accessor=field.accessor,
-        )
-    )
-
-
-def field_to_loc_map(owner_type: TypeHint, field: BaseField) -> LocMap:
-    if isinstance(field, InputField):
-        return input_field_to_loc_map(owner_type, field)
-    if isinstance(field, OutputField):
-        return output_field_to_loc_map(owner_type, field)
-    raise TypeError
diff --git a/src/adaptix/_internal/morphing/model/loader_gen.py b/src/adaptix/_internal/morphing/model/loader_gen.py
index bab8c1cf..5d85ef01 100644
--- a/src/adaptix/_internal/morphing/model/loader_gen.py
+++ b/src/adaptix/_internal/morphing/model/loader_gen.py
@@ -1,11 +1,10 @@
 import collections.abc
 import contextlib
 from dataclasses import dataclass
-from typing import Dict, List, Mapping, Optional, Set
+from typing import AbstractSet, Dict, List, Mapping, Optional, Set, Tuple
 
-from ...code_generator import CodeGenerator
+from ...code_tools.cascade_namespace import BuiltinCascadeNamespace, CascadeNamespace
 from ...code_tools.code_builder import CodeBuilder
-from ...code_tools.context_namespace import ContextNamespace
 from ...code_tools.utils import get_literal_expr, get_literal_from_factory
 from ...common import Loader
 from ...compat import CompatExceptionGroup
@@ -16,13 +15,14 @@
 from ..load_error import (
     AggregateLoadError,
     ExcludedTypeLoadError,
-    ExtraFieldsError,
-    ExtraItemsError,
+    ExtraFieldsLoadError,
+    ExtraItemsLoadError,
     LoadError,
-    NoRequiredFieldsError,
-    NoRequiredItemsError,
+    NoRequiredFieldsLoadError,
+    NoRequiredItemsLoadError,
     TypeLoadError,
 )
+from .basic_gen import ModelLoaderGen
 from .crown_definitions import (
     BranchInpCrown,
     CrownPath,
@@ -102,13 +102,13 @@ class GenState(Namer):
     def __init__(
         self,
         builder: CodeBuilder,
-        ctx_namespace: ContextNamespace,
+        namespace: CascadeNamespace,
         name_to_field: Dict[str, InputField],
         debug_trail: DebugTrail,
         root_crown: InpCrown,
     ):
         self.builder = builder
-        self.ctx_namespace = ctx_namespace
+        self.namespace = namespace
         self._name_to_field = name_to_field
 
         self.field_id_to_path: Dict[str, CrownPath] = {}
@@ -168,8 +168,8 @@ class ModelLoaderProps:
     use_default_for_omitted: bool = True
 
 
-class ModelLoaderGen(CodeGenerator):
-    """ModelLoaderGen generates code that extracts raw values from input data,
+class BuiltinModelLoaderGen(ModelLoaderGen):
+    """BuiltinModelLoaderGen generates code that extracts raw values from input data,
     calls loaders and stores results to variables.
     """
 
@@ -180,6 +180,7 @@ def __init__(
         debug_trail: DebugTrail,
         strict_coercion: bool,
         field_loaders: Mapping[str, Loader],
+        skipped_fields: AbstractSet[str],
         model_identity: str,
         props: ModelLoaderProps,
     ):
@@ -194,6 +195,7 @@ def __init__(
             param.field_id: param for param in self._shape.params
         }
         self._field_loaders = field_loaders
+        self._skipped_fields = skipped_fields
         self._model_identity = model_identity
         self._props = props
 
@@ -208,17 +210,17 @@ def _is_extra_target(self, field: InputField) -> bool:
             field.id in self._name_layout.extra_move.fields
         )
 
-    def _create_state(self, ctx_namespace: ContextNamespace) -> GenState:
+    def _create_state(self, namespace: CascadeNamespace) -> GenState:
         return GenState(
             builder=CodeBuilder(),
-            ctx_namespace=ctx_namespace,
+            namespace=namespace,
             name_to_field=self._id_to_field,
             debug_trail=self._debug_trail,
             root_crown=self._name_layout.crown,
         )
 
     @property
-    def has_packed_fields(self):
+    def _has_packed_fields(self):
         return any(self._is_packed_field(fld) for fld in self._shape.fields)
 
     def _is_packed_field(self, field: InputField) -> bool:
@@ -226,32 +228,33 @@ def _is_packed_field(self, field: InputField) -> bool:
             return False
         return field.is_optional and not self._is_extra_target(field)
 
-    def produce_code(self, ctx_namespace: ContextNamespace) -> CodeBuilder:
-        state = self._create_state(ctx_namespace)
+    def produce_code(self, closure_name: str) -> Tuple[str, Mapping[str, object]]:
+        namespace = BuiltinCascadeNamespace()
+        state = self._create_state(namespace)
 
         for field_id, loader in self._field_loaders.items():
-            state.ctx_namespace.add(state.v_field_loader(field_id), loader)
+            state.namespace.add_constant(state.v_field_loader(field_id), loader)
 
         for named_value in (
             append_trail, extend_trail, render_trail_as_note,
-            ExtraFieldsError, ExtraItemsError,
-            NoRequiredFieldsError, NoRequiredItemsError,
+            ExtraFieldsLoadError, ExtraItemsLoadError,
+            NoRequiredFieldsLoadError, NoRequiredItemsLoadError,
             TypeLoadError, ExcludedTypeLoadError,
             LoadError, AggregateLoadError,
         ):
-            state.ctx_namespace.add(named_value.__name__, named_value)  # type: ignore[attr-defined]
+            state.namespace.add_constant(named_value.__name__, named_value)  # type: ignore[attr-defined]
 
-        state.ctx_namespace.add('CompatExceptionGroup', CompatExceptionGroup)
-        state.ctx_namespace.add('CollectionsMapping', collections.abc.Mapping)
-        state.ctx_namespace.add('CollectionsSequence', collections.abc.Sequence)
-        state.ctx_namespace.add('sentinel', object())
+        state.namespace.add_constant('CompatExceptionGroup', CompatExceptionGroup)
+        state.namespace.add_constant('CollectionsMapping', collections.abc.Mapping)
+        state.namespace.add_constant('CollectionsSequence', collections.abc.Sequence)
+        state.namespace.add_constant('sentinel', object())
 
         if self._debug_trail == DebugTrail.ALL:
             state.builder += "errors = []"
             state.builder += "has_unexpected_error = False"
-            state.ctx_namespace.add('model_identity', self._model_identity)
+            state.namespace.add_constant('model_identity', self._model_identity)
 
-        if self.has_packed_fields:
+        if self._has_packed_fields:
             state.builder += "packed_fields = {}"
 
         if not self._gen_root_crown_dispatch(state, self._name_layout.crown):
@@ -278,7 +281,11 @@ def produce_code(self, ctx_namespace: ContextNamespace) -> CodeBuilder:
 
         self._gen_constructor_call(state)
         self._gen_header(state)
-        return state.builder
+
+        builder = CodeBuilder()
+        with builder(f'def {closure_name}(data):'):
+            builder.extend(state.builder)
+        return builder.string(), namespace.constants
 
     def _gen_header(self, state: GenState):
         header_builder = CodeBuilder()
@@ -298,25 +305,33 @@ def _gen_header(self, state: GenState):
 
         state.builder.extend_above(header_builder)
 
-    def _gen_constructor_call(self, state: GenState) -> None:
-        state.ctx_namespace.add('constructor', self._shape.constructor)
+    def _gen_constructor_call(self, state: GenState) -> None:  # noqa: CCR001
+        state.namespace.add_constant('constructor', self._shape.constructor)
 
         constructor_builder = CodeBuilder()
+        has_skipped_params = False
         with constructor_builder("constructor("):
             for param in self._shape.params:
                 field = self._shape.fields_dict[param.field_id]
 
-                if not self._is_packed_field(field):
-                    value = state.v_field(field)
-                else:
+                if field.id in self._skipped_fields:
+                    has_skipped_params = True
+                    continue
+                if self._is_packed_field(field):
                     continue
 
-                if param.kind == ParamKind.KW_ONLY:
+                value = state.v_field(field)
+                if param.kind == ParamKind.KW_ONLY or has_skipped_params:
                     constructor_builder(f"{param.name}={value},")
+                elif param.kind == ParamKind.POS_ONLY and has_skipped_params:
+                    raise ValueError(
+                        'Can not generate consistent constructor call,'
+                        ' positional-only parameter is skipped'
+                    )
                 else:
                     constructor_builder(f"{value},")
 
-            if self.has_packed_fields:
+            if self._has_packed_fields:
                 constructor_builder("**packed_fields,")
 
             if self._name_layout.extra_move == ExtraKwargs():
@@ -325,7 +340,7 @@ def _gen_constructor_call(self, state: GenState) -> None:
         constructor_builder += ")"
 
         if isinstance(self._name_layout.extra_move, ExtraSaturate):
-            state.ctx_namespace.add('saturator', self._name_layout.extra_move.func)
+            state.namespace.add_constant('saturator', self._name_layout.extra_move.func)
             state.builder += "result = "
             state.builder.extend_including(constructor_builder)
             state.builder += f"saturator(result, {state.v_extra})"
@@ -393,7 +408,7 @@ def _gen_assigment_from_parent_data(
             bad_type_error = '(TypeError, IndexError)'
             bad_type_load_error = f'TypeLoadError(CollectionsMapping, {state.parent.v_data})'
             not_found_error = (
-                "NoRequiredFieldsError("
+                "NoRequiredFieldsLoadError("
                 f"{state.parent.v_required_keys} - set({state.parent.v_data}), {state.parent.v_data}"
                 ")"
             )
@@ -401,7 +416,7 @@ def _gen_assigment_from_parent_data(
             lookup_error = 'IndexError'
             bad_type_error = '(TypeError, KeyError)'
             bad_type_load_error = f'TypeLoadError(CollectionsSequence, {state.parent.v_data})'
-            not_found_error = f"NoRequiredItemsError({len(state.parent_crown.map)}, {state.parent.v_data})"
+            not_found_error = f"NoRequiredItemsLoadError({len(state.parent_crown.map)}, {state.parent.v_data})"
 
         with state.builder(
             f"""
@@ -479,8 +494,8 @@ def _get_dict_crown_required_keys(self, crown: InpDictCrown) -> Set[str]:
         }
 
     def _gen_dict_crown(self, state: GenState, crown: InpDictCrown):
-        state.ctx_namespace.add(state.v_known_keys, set(crown.map.keys()))
-        state.ctx_namespace.add(state.v_required_keys, self._get_dict_crown_required_keys(crown))
+        state.namespace.add_constant(state.v_known_keys, set(crown.map.keys()))
+        state.namespace.add_constant(state.v_required_keys, self._get_dict_crown_required_keys(crown))
 
         if state.path:
             self._gen_assigment_from_parent_data(state, assign_to=state.v_data)
@@ -505,7 +520,7 @@ def _gen_dict_crown(self, state: GenState, crown: InpDictCrown):
                 state.builder += f"""
                     {state.v_extra}_set = set({state.v_data}) - {state.v_known_keys}
                     if {state.v_extra}_set:
-                        {state.emit_error(f"ExtraFieldsError({state.v_extra}_set, {state.v_data})")}
+                        {state.emit_error(f"ExtraFieldsLoadError({state.v_extra}_set, {state.v_data})")}
                 """
                 state.builder.empty_line()
             elif crown.extra_policy == ExtraCollect():
@@ -552,14 +567,14 @@ def _gen_list_crown(self, state: GenState, crown: InpListCrown):
                 state.builder += f"""
                     if len({state.v_data}) != {expected_len}:
                         if len({state.v_data}) < {expected_len}:
-                            {state.emit_error(f"NoRequiredItemsError({expected_len}, {state.v_data})")}
+                            {state.emit_error(f"NoRequiredItemsLoadError({expected_len}, {state.v_data})")}
                         else:
-                            {state.emit_error(f"ExtraItemsError({expected_len}, {state.v_data})")}
+                            {state.emit_error(f"ExtraItemsLoadError({expected_len}, {state.v_data})")}
                 """
             else:
                 state.builder += f"""
                     if len({state.v_data}) < {expected_len}:
-                        {state.emit_error(f"NoRequiredItemsError({expected_len}, {state.v_data})")}
+                        {state.emit_error(f"NoRequiredItemsLoadError({expected_len}, {state.v_data})")}
                 """
 
         if self._can_collect_extra:
@@ -570,13 +585,13 @@ def _get_default_clause_expr(self, state: GenState, field: InputField) -> str:
             literal_expr = get_literal_expr(field.default.value)
             if literal_expr is not None:
                 return literal_expr
-            state.ctx_namespace.add(f'dfl_{field.id}', field.default.value)
+            state.namespace.add_constant(f'dfl_{field.id}', field.default.value)
             return f'dfl_{field.id}'
         if isinstance(field.default, DefaultFactory):
             literal_expr = get_literal_from_factory(field.default.factory)
             if literal_expr is not None:
                 return literal_expr
-            state.ctx_namespace.add(f'dfl_{field.id}', field.default.factory)
+            state.namespace.add_constant(f'dfl_{field.id}', field.default.factory)
             return f'dfl_{field.id}()'
         raise ValueError
 
diff --git a/src/adaptix/_internal/morphing/model/loader_provider.py b/src/adaptix/_internal/morphing/model/loader_provider.py
index ef464d47..2b65fda9 100644
--- a/src/adaptix/_internal/morphing/model/loader_provider.py
+++ b/src/adaptix/_internal/morphing/model/loader_provider.py
@@ -1,31 +1,29 @@
-from typing import Mapping
+from typing import AbstractSet, Mapping
 
-from ...code_generator import CodeGenerator
-from ...code_tools.compiler import BasicClosureCompiler
-from ...code_tools.context_namespace import BuiltinContextNamespace
+from adaptix._internal.provider.fields import input_field_to_loc_map
+
+from ...code_tools.compiler import BasicClosureCompiler, ClosureCompiler
 from ...common import Loader
 from ...definitions import DebugTrail
 from ...model_tools.definitions import InputShape
-from ...provider.essential import CannotProvide, Mediator
+from ...provider.essential import Mediator
 from ...provider.request_cls import DebugTrailRequest, StrictCoercionRequest, TypeHintLoc
 from ...provider.shape_provider import InputShapeRequest, provide_generic_resolved_shape
-from ..model.loader_gen import ModelLoaderGen, ModelLoaderProps
+from ..model.loader_gen import BuiltinModelLoaderGen, ModelLoaderProps
 from ..provider_template import LoaderProvider
 from ..request_cls import LoaderRequest
 from .basic_gen import (
-    CodeGenHookRequest,
+    ModelLoaderGen,
     NameSanitizer,
     compile_closure_with_globals_capturing,
+    fetch_code_gen_hook,
     get_extra_targets_at_crown,
     get_optional_fields_at_list_crown,
     get_skipped_fields,
     get_wild_extra_targets,
     has_collect_policy,
-    strip_input_shape_fields,
-    stub_code_gen_hook,
 )
 from .crown_definitions import InputNameLayout, InputNameLayoutRequest
-from .fields import input_field_to_loc_map
 
 
 class ModelLoaderProvider(LoaderProvider):
@@ -40,39 +38,33 @@ def __init__(
 
     def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         loader_gen = self._fetch_model_loader_gen(mediator, request)
-        ctx_namespace = BuiltinContextNamespace()
-        loader_code_builder = loader_gen.produce_code(ctx_namespace)
-
-        try:
-            code_gen_hook = mediator.delegating_provide(CodeGenHookRequest())
-        except CannotProvide:
-            code_gen_hook = stub_code_gen_hook
-
+        closure_name = self._get_closure_name(request)
+        loader_code, loader_namespace = loader_gen.produce_code(closure_name=closure_name)
         return compile_closure_with_globals_capturing(
             compiler=self._get_compiler(),
-            code_gen_hook=code_gen_hook,
-            namespace=ctx_namespace.dict,
-            body_builders=[loader_code_builder],
-            closure_name=self._get_closure_name(request),
-            closure_params='data',
+            code_gen_hook=fetch_code_gen_hook(mediator, request.loc_stack),
+            namespace=loader_namespace,
+            closure_code=loader_code,
+            closure_name=closure_name,
             file_name=self._get_file_name(request),
         )
 
-    def _fetch_model_loader_gen(self, mediator: Mediator, request: LoaderRequest) -> CodeGenerator:
+    def _fetch_model_loader_gen(self, mediator: Mediator, request: LoaderRequest) -> ModelLoaderGen:
         shape = self._fetch_shape(mediator, request)
         name_layout = self._fetch_name_layout(mediator, request, shape)
-        shape = self._process_shape(shape, name_layout)
-        self._validate_params(shape, name_layout)
+        skipped_fields = get_skipped_fields(shape, name_layout)
+        self._validate_params(shape, name_layout, skipped_fields)
 
         field_loaders = self._fetch_field_loaders(mediator, request, shape)
-        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_map=request.loc_map))
-        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_map=request.loc_map))
+        strict_coercion = mediator.mandatory_provide(StrictCoercionRequest(loc_stack=request.loc_stack))
+        debug_trail = mediator.mandatory_provide(DebugTrailRequest(loc_stack=request.loc_stack))
         return self._create_model_loader_gen(
             debug_trail=debug_trail,
             strict_coercion=strict_coercion,
             shape=shape,
             name_layout=name_layout,
             field_loaders=field_loaders,
+            skipped_fields=skipped_fields,
             model_identity=self._fetch_model_identity(mediator, request, shape, name_layout),
         )
 
@@ -84,8 +76,8 @@ def _fetch_model_identity(
         name_layout: InputNameLayout,
     ) -> str:
         return (
-            repr(request.loc_map[TypeHintLoc].type)
-            if request.loc_map.has(TypeHintLoc) else
+            repr(request.last_map[TypeHintLoc].type)
+            if request.last_map.has(TypeHintLoc) else
             repr(shape.constructor)
         )
 
@@ -96,21 +88,23 @@ def _create_model_loader_gen(
         shape: InputShape,
         name_layout: InputNameLayout,
         field_loaders: Mapping[str, Loader],
+        skipped_fields: AbstractSet[str],
         model_identity: str,
-    ) -> CodeGenerator:
-        return ModelLoaderGen(
+    ) -> ModelLoaderGen:
+        return BuiltinModelLoaderGen(
             shape=shape,
             name_layout=name_layout,
             debug_trail=debug_trail,
             strict_coercion=strict_coercion,
             field_loaders=field_loaders,
+            skipped_fields=skipped_fields,
             model_identity=model_identity,
             props=self._props,
         )
 
     def _request_to_view_string(self, request: LoaderRequest) -> str:
-        if request.loc_map.has(TypeHintLoc):
-            tp = request.loc_map[TypeHintLoc].type
+        if request.last_map.has(TypeHintLoc):
+            tp = request.last_map[TypeHintLoc].type
             if isinstance(tp, type):
                 return tp.__name__
             return str(tp)
@@ -129,16 +123,16 @@ def _get_closure_name(self, request: LoaderRequest) -> str:
             'model_loader', self._name_sanitizer.sanitize(self._request_to_view_string(request)),
         )
 
-    def _get_compiler(self):
+    def _get_compiler(self) -> ClosureCompiler:
         return BasicClosureCompiler()
 
     def _fetch_shape(self, mediator: Mediator, request: LoaderRequest) -> InputShape:
-        return provide_generic_resolved_shape(mediator, InputShapeRequest(loc_map=request.loc_map))
+        return provide_generic_resolved_shape(mediator, InputShapeRequest(loc_stack=request.loc_stack))
 
     def _fetch_name_layout(self, mediator: Mediator, request: LoaderRequest, shape: InputShape) -> InputNameLayout:
         return mediator.mandatory_provide(
             InputNameLayoutRequest(
-                loc_map=request.loc_map,
+                loc_stack=request.loc_stack,
                 shape=shape,
             ),
             lambda x: 'Cannot create loader for model. Cannot fetch InputNameLayout',
@@ -150,25 +144,31 @@ def _fetch_field_loaders(
         request: LoaderRequest,
         shape: InputShape,
     ) -> Mapping[str, Loader]:
-        owner_type = request.loc_map[TypeHintLoc].type
         loaders = mediator.mandatory_provide_by_iterable(
             [
-                LoaderRequest(loc_map=input_field_to_loc_map(owner_type, field))
+                LoaderRequest(loc_stack=request.loc_stack.append_with(input_field_to_loc_map(field)))
                 for field in shape.fields
             ],
             lambda: "Cannot create loader for model. Loaders for some fields cannot be created",
         )
         return {field.id: loader for field, loader in zip(shape.fields, loaders)}
 
-    def _process_shape(self, shape: InputShape, name_layout: InputNameLayout) -> InputShape:
-        wild_extra_targets = get_wild_extra_targets(shape, name_layout.extra_move)
-        if wild_extra_targets:
+    def _validate_params(
+        self,
+        shape: InputShape,
+        name_layout: InputNameLayout,
+        skipped_fields: AbstractSet[str],
+    ) -> None:
+        skipped_required_fields = [
+            field.id
+            for field in shape.fields
+            if field.is_required and field.id in skipped_fields
+        ]
+        if skipped_required_fields:
             raise ValueError(
-                f"ExtraTargets {wild_extra_targets} are attached to non-existing fields"
+                f"Required fields {skipped_required_fields} are skipped"
             )
-        return strip_input_shape_fields(shape, get_skipped_fields(shape, name_layout))
 
-    def _validate_params(self, processed_shape: InputShape, name_layout: InputNameLayout) -> None:
         if name_layout.extra_move is None and has_collect_policy(name_layout.crown):
             raise ValueError(
                 "Cannot create loader that collect extra data"
@@ -182,7 +182,7 @@ def _validate_params(self, processed_shape: InputShape, name_layout: InputNameLa
             )
 
         optional_fields_at_list_crown = get_optional_fields_at_list_crown(
-            {field.id: field for field in processed_shape.fields},
+            {field.id: field for field in shape.fields},
             name_layout.crown,
         )
         if optional_fields_at_list_crown:
@@ -190,6 +190,12 @@ def _validate_params(self, processed_shape: InputShape, name_layout: InputNameLa
                 f"Optional fields {optional_fields_at_list_crown} are found at list crown"
             )
 
+        wild_extra_targets = get_wild_extra_targets(shape, name_layout.extra_move)
+        if wild_extra_targets:
+            raise ValueError(
+                f"ExtraTargets {wild_extra_targets} are attached to non-existing fields"
+            )
+
 
 class InlinedShapeModelLoaderProvider(ModelLoaderProvider):
     def __init__(
diff --git a/src/adaptix/_internal/morphing/model/request_filtering.py b/src/adaptix/_internal/morphing/model/request_filtering.py
index 97036d1b..f6a897ed 100644
--- a/src/adaptix/_internal/morphing/model/request_filtering.py
+++ b/src/adaptix/_internal/morphing/model/request_filtering.py
@@ -1,15 +1,20 @@
-from ...provider.essential import CannotProvide, Request
-from ...provider.request_cls import LocatedRequest
-from ...provider.request_filtering import DirectMediator, RequestChecker
+from ...provider.essential import CannotProvide
+from ...provider.loc_stack_filtering import DirectMediator, LocStackChecker
+from ...provider.request_cls import LocStack
 from ...provider.shape_provider import InputShapeRequest, OutputShapeRequest
 
 
-class AnyModelRC(RequestChecker):
-    def check_request(self, mediator: DirectMediator, request: Request) -> None:
-        if not isinstance(request, LocatedRequest):
-            raise CannotProvide
+class AnyModelLSC(LocStackChecker):
+    def check_loc_stack(self, mediator: DirectMediator, loc_stack: LocStack) -> bool:
+        try:
+            mediator.provide(InputShapeRequest(loc_stack=loc_stack))
+        except CannotProvide:
+            pass
+        else:
+            return True
 
         try:
-            mediator.provide(InputShapeRequest(loc_map=request.loc_map))
+            mediator.provide(OutputShapeRequest(loc_stack=loc_stack))
         except CannotProvide:
-            mediator.provide(OutputShapeRequest(loc_map=request.loc_map))
+            return False
+        return True
diff --git a/src/adaptix/_internal/morphing/name_layout/component.py b/src/adaptix/_internal/morphing/name_layout/component.py
index 7af89ab0..91d5b3f1 100644
--- a/src/adaptix/_internal/morphing/name_layout/component.py
+++ b/src/adaptix/_internal/morphing/name_layout/component.py
@@ -15,9 +15,10 @@
 )
 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.loc_stack_filtering import LocStackChecker
 from ...provider.overlay_schema import Overlay, Schema, provide_schema
-from ...provider.request_cls import LocatedRequest, TypeHintLoc
-from ...provider.request_filtering import ExtraStackMediator, RequestChecker
+from ...provider.request_cls import LocatedRequest
 from ...retort.operating_retort import OperatingRetort
 from ...special_cases_optimization import with_default_clause
 from ...utils import Omittable, get_prefix_groups
@@ -45,7 +46,6 @@
     OutputNameLayoutRequest,
     Sieve,
 )
-from ..model.fields import field_to_loc_map
 from .base import (
     ExtraIn,
     ExtraMoveMaker,
@@ -57,13 +57,13 @@
     SievesMaker,
     StructureMaker,
 )
-from .name_mapping import NameMappingFilterRequest, NameMappingRequest
+from .name_mapping import NameMappingRequest
 
 
 @dataclass(frozen=True)
 class StructureSchema(Schema):
-    skip: RequestChecker
-    only: RequestChecker
+    skip: LocStackChecker
+    only: LocStackChecker
 
     map: VarTuple[Provider]
     trim_trailing_underscore: bool
@@ -73,8 +73,8 @@ class StructureSchema(Schema):
 
 @dataclass(frozen=True)
 class StructureOverlay(Overlay[StructureSchema]):
-    skip: Omittable[RequestChecker]
-    only: Omittable[RequestChecker]
+    skip: Omittable[LocStackChecker]
+    only: Omittable[LocStackChecker]
 
     map: Omittable[VarTuple[Provider]]
     trim_trailing_underscore: Omittable[bool]
@@ -92,22 +92,14 @@ def _merge_map(self, old: VarTuple[Provider], new: VarTuple[Provider]) -> VarTup
 FieldAndPath = Tuple[F, Optional[KeyPath]]
 
 
-def apply_rc(
+def apply_lsc(
     mediator: Mediator,
     request: BaseNameLayoutRequest,
-    request_checker: RequestChecker,
+    loc_stack_checker: LocStackChecker,
     field: BaseField,
 ) -> bool:
-    owner_type = request.loc_map[TypeHintLoc].type
-    filter_request = NameMappingFilterRequest(loc_map=field_to_loc_map(owner_type, field))
-    try:
-        request_checker.check_request(
-            ExtraStackMediator(mediator, [filter_request]),
-            filter_request,
-        )
-    except CannotProvide:
-        return False
-    return True
+    loc_stack = request.loc_stack.append_with(field_to_loc_map(field))
+    return loc_stack_checker.check_loc_stack(mediator, loc_stack)
 
 
 class BuiltinStructureMaker(StructureMaker):
@@ -146,7 +138,7 @@ def _map_fields(
                         shape=request.shape,
                         field=field,
                         generated_key=generated_key,
-                        loc_map=field_to_loc_map(request.loc_map[TypeHintLoc].type, field),
+                        loc_stack=request.loc_stack.append_with(field_to_loc_map(field)),
                     )
                 )
             except CannotProvide:
@@ -155,8 +147,8 @@ def _map_fields(
             if path is None:
                 yield field, None
             elif (
-                not apply_rc(mediator, request, schema.skip, field)
-                and apply_rc(mediator, request, schema.only, field)
+                not apply_lsc(mediator, request, schema.skip, field)
+                and apply_lsc(mediator, request, schema.only, field)
             ):
                 yield field, path
             else:
@@ -290,7 +282,7 @@ def make_inp_structure(
         request: InputNameLayoutRequest,
         extra_move: InpExtraMove,
     ) -> PathsTo[LeafInpCrown]:
-        schema = provide_schema(StructureOverlay, mediator, request.loc_map)
+        schema = provide_schema(StructureOverlay, mediator, request.loc_stack)
         fields_to_paths: List[FieldAndPath[InputField]] = list(
             self._map_fields(mediator, request, schema, extra_move)
         )
@@ -315,7 +307,7 @@ def make_out_structure(
         request: OutputNameLayoutRequest,
         extra_move: OutExtraMove,
     ) -> PathsTo[LeafOutCrown]:
-        schema = provide_schema(StructureOverlay, mediator, request.loc_map)
+        schema = provide_schema(StructureOverlay, mediator, request.loc_stack)
         fields_to_paths: List[FieldAndPath[OutputField]] = list(
             self._map_fields(mediator, request, schema, extra_move)
         )
@@ -324,20 +316,20 @@ def make_out_structure(
         return paths_to_leaves
 
     def empty_as_list_inp(self, mediator: Mediator, request: InputNameLayoutRequest) -> bool:
-        return provide_schema(StructureOverlay, mediator, request.loc_map).as_list
+        return provide_schema(StructureOverlay, mediator, request.loc_stack).as_list
 
     def empty_as_list_out(self, mediator: Mediator, request: OutputNameLayoutRequest) -> bool:
-        return provide_schema(StructureOverlay, mediator, request.loc_map).as_list
+        return provide_schema(StructureOverlay, mediator, request.loc_stack).as_list
 
 
 @dataclass(frozen=True)
 class SievesSchema(Schema):
-    omit_default: RequestChecker
+    omit_default: LocStackChecker
 
 
 @dataclass(frozen=True)
 class SievesOverlay(Overlay[SievesSchema]):
-    omit_default: Omittable[RequestChecker]
+    omit_default: Omittable[LocStackChecker]
 
 
 class BuiltinSievesMaker(SievesMaker):
@@ -362,12 +354,12 @@ def make_sieves(
         request: OutputNameLayoutRequest,
         paths_to_leaves: PathsTo[LeafOutCrown],
     ) -> PathsTo[Sieve]:
-        schema = provide_schema(SievesOverlay, mediator, request.loc_map)
+        schema = provide_schema(SievesOverlay, mediator, request.loc_stack)
         result = {}
         for path, leaf in paths_to_leaves.items():
             if isinstance(leaf, OutFieldCrown):
                 field = request.shape.fields_dict[leaf.id]
-                if field.default != NoDefault() and apply_rc(mediator, request, schema.omit_default, field):
+                if field.default != NoDefault() and apply_lsc(mediator, request, schema.omit_default, field):
                     result[path] = self._create_sieve(field)
         return result
 
@@ -406,7 +398,7 @@ def make_inp_extra_move(
         mediator: Mediator,
         request: InputNameLayoutRequest,
     ) -> InpExtraMove:
-        schema = provide_schema(ExtraMoveAndPoliciesOverlay, mediator, request.loc_map)
+        schema = provide_schema(ExtraMoveAndPoliciesOverlay, mediator, request.loc_stack)
         if schema.extra_in in (ExtraForbid(), ExtraSkip()):
             return None
         if schema.extra_in == ExtraKwargs():
@@ -420,7 +412,7 @@ def make_out_extra_move(
         mediator: Mediator,
         request: OutputNameLayoutRequest,
     ) -> OutExtraMove:
-        schema = provide_schema(ExtraMoveAndPoliciesOverlay, mediator, request.loc_map)
+        schema = provide_schema(ExtraMoveAndPoliciesOverlay, mediator, request.loc_stack)
         if schema.extra_out == ExtraSkip():
             return None
         if callable(schema.extra_out):
@@ -440,7 +432,7 @@ def make_extra_policies(
         request: InputNameLayoutRequest,
         paths_to_leaves: PathsTo[LeafInpCrown],
     ) -> PathsTo[DictExtraPolicy]:
-        schema = provide_schema(ExtraMoveAndPoliciesOverlay, mediator, request.loc_map)
+        schema = provide_schema(ExtraMoveAndPoliciesOverlay, mediator, request.loc_stack)
         policy = self._get_extra_policy(schema)
         path_to_extra_policy: Dict[KeyPath, DictExtraPolicy] = {
             (): policy,
diff --git a/src/adaptix/_internal/morphing/name_layout/name_mapping.py b/src/adaptix/_internal/morphing/name_layout/name_mapping.py
index af97c748..348c0021 100644
--- a/src/adaptix/_internal/morphing/name_layout/name_mapping.py
+++ b/src/adaptix/_internal/morphing/name_layout/name_mapping.py
@@ -6,8 +6,9 @@
 from ...common import EllipsisType
 from ...model_tools.definitions import BaseField, BaseShape, is_valid_field_id
 from ...provider.essential import CannotProvide, Mediator, Provider
+from ...provider.loc_stack_filtering import LocStackChecker, Pred
+from ...provider.provider_wrapper import ProviderWithLSC
 from ...provider.request_cls import LocatedRequest
-from ...provider.request_filtering import Pred, ProviderWithRC, RequestChecker
 from ...provider.static_provider import StaticProvider, static_provision_action
 from .base import Key, KeyPath
 
@@ -66,35 +67,30 @@ def _provide_input_name_layout(self, mediator: Mediator, request: NameMappingReq
         return resolve_map_result(request.generated_key, map_result)
 
 
-class ConstNameMappingProvider(StaticProvider, ProviderWithRC):
-    def __init__(self, request_checker: RequestChecker, result: MapResult):
-        self._request_checker = request_checker
+class ConstNameMappingProvider(StaticProvider, ProviderWithLSC):
+    def __init__(self, loc_stack_checker: LocStackChecker, result: MapResult):
+        self._loc_stack_checker = loc_stack_checker
         self._result = result
 
-    def get_request_checker(self) -> Optional[RequestChecker]:
-        return self._request_checker
+    def get_loc_stack_checker(self) -> Optional[LocStackChecker]:
+        return self._loc_stack_checker
 
     @static_provision_action
     def _provide_input_name_layout(self, mediator: Mediator, request: NameMappingRequest) -> Optional[KeyPath]:
-        self._request_checker.check_request(mediator, request)
+        self._apply_loc_stack_checker(mediator, request)
         return resolve_map_result(request.generated_key, self._result)
 
 
-class FuncNameMappingProvider(StaticProvider, ProviderWithRC):
-    def __init__(self, request_checker: RequestChecker, func: Callable[[BaseShape, BaseField], MapResult]):
-        self._request_checker = request_checker
+class FuncNameMappingProvider(StaticProvider, ProviderWithLSC):
+    def __init__(self, loc_stack_checker: LocStackChecker, func: Callable[[BaseShape, BaseField], MapResult]):
+        self._loc_stack_checker = loc_stack_checker
         self._func = func
 
-    def get_request_checker(self) -> Optional[RequestChecker]:
-        return self._request_checker
+    def get_loc_stack_checker(self) -> Optional[LocStackChecker]:
+        return self._loc_stack_checker
 
     @static_provision_action
     def _provide_input_name_layout(self, mediator: Mediator, request: NameMappingRequest) -> Optional[KeyPath]:
-        self._request_checker.check_request(mediator, request)
+        self._apply_loc_stack_checker(mediator, request)
         result = self._func(request.shape, request.field)
         return resolve_map_result(request.generated_key, result)
-
-
-@dataclass(frozen=True)
-class NameMappingFilterRequest(LocatedRequest[None]):
-    pass
diff --git a/src/adaptix/_internal/morphing/provider_template.py b/src/adaptix/_internal/morphing/provider_template.py
index a9a698fa..8bcdf440 100644
--- a/src/adaptix/_internal/morphing/provider_template.py
+++ b/src/adaptix/_internal/morphing/provider_template.py
@@ -3,19 +3,19 @@
 
 from ..common import Dumper, Loader, TypeHint
 from ..provider.essential import CannotProvide, Mediator
-from ..provider.provider_template import ProviderWithAttachableRC
-from ..provider.request_cls import LocMap, TypeHintLoc
-from ..provider.request_filtering import ExactOriginRC
+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
 
 
-class LoaderProvider(ProviderWithAttachableRC, ABC):
+class LoaderProvider(ProviderWithAttachableLSC, ABC):
     @final
     @static_provision_action
     def _outer_provide_loader(self, mediator: Mediator, request: LoaderRequest):
-        self._request_checker.check_request(mediator, request)
+        self._apply_loc_stack_checker(mediator, request)
         return self._provide_loader(mediator, request)
 
     @abstractmethod
@@ -23,11 +23,11 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
         ...
 
 
-class DumperProvider(ProviderWithAttachableRC, ABC):
+class DumperProvider(ProviderWithAttachableLSC, ABC):
     @final
     @static_provision_action
     def _outer_provide_dumper(self, mediator: Mediator, request: DumperRequest):
-        self._request_checker.check_request(mediator, request)
+        self._apply_loc_stack_checker(mediator, request)
         return self._provide_dumper(mediator, request)
 
     @abstractmethod
@@ -39,7 +39,7 @@ class ABCProxy(LoaderProvider, DumperProvider):
     def __init__(self, abstract: TypeHint, impl: TypeHint, for_loader: bool = True, for_dumper: bool = True):
         self._abstract = normalize_type(abstract).origin
         self._impl = impl
-        self._request_checker = ExactOriginRC(self._abstract)
+        self._loc_stack_checker = ExactOriginLSC(self._abstract)
         self._for_loader = for_loader
         self._for_dumper = for_dumper
 
@@ -49,7 +49,7 @@ def _provide_loader(self, mediator: Mediator, request: LoaderRequest) -> Loader:
 
         return mediator.mandatory_provide(
             LoaderRequest(
-                loc_map=LocMap(TypeHintLoc(type=self._impl))
+                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(type=self._impl))
             ),
             lambda x: f'Cannot create loader for union. Loader for {self._impl} cannot be created',
         )
@@ -60,7 +60,7 @@ def _provide_dumper(self, mediator: Mediator, request: DumperRequest) -> Dumper:
 
         return mediator.mandatory_provide(
             DumperRequest(
-                loc_map=LocMap(TypeHintLoc(type=self._impl))
+                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(type=self._impl))
             ),
             lambda x: f'Cannot create dumper for union. Dumper for {self._impl} cannot be created',
         )
diff --git a/src/adaptix/_internal/name_style.py b/src/adaptix/_internal/name_style.py
index be02e3ed..245a3d2b 100644
--- a/src/adaptix/_internal/name_style.py
+++ b/src/adaptix/_internal/name_style.py
@@ -66,11 +66,8 @@ class StyleConversion:
 ONLY_WORD_CHARS = re.compile(r'\w+')
 
 
-def is_snake_case(name: str) -> bool:
-    return (
-        ONLY_WORD_CHARS.fullmatch(name) is not None
-        and name.lower() == name
-    )
+def is_snake_style(name: str) -> bool:
+    return ONLY_WORD_CHARS.fullmatch(name) is not None
 
 
 SNAKE_SPLITTER = re.compile(r'(_*)([^_]+)(.*?)(_*)$')
@@ -84,8 +81,8 @@ def rest_sub(conv: StyleConversion, match_: re.Match):
 
 
 def convert_snake_style(name: str, style: NameStyle) -> str:
-    if not is_snake_case(name):
-        raise ValueError("Cannot convert a name that not follows snake_case")
+    if not is_snake_style(name):
+        raise ValueError("Cannot convert a name that not follows snake style")
 
     match = SNAKE_SPLITTER.match(name)
     if match is None:
diff --git a/src/adaptix/_internal/provider/essential.py b/src/adaptix/_internal/provider/essential.py
index 78624439..55bffbb1 100644
--- a/src/adaptix/_internal/provider/essential.py
+++ b/src/adaptix/_internal/provider/essential.py
@@ -2,6 +2,7 @@
 from dataclasses import dataclass
 from typing import Any, Callable, Generic, Iterable, Optional, Sequence, TypeVar, final
 
+from ..common import VarTuple
 from ..compat import CompatExceptionGroup
 from ..feature_requirement import HAS_NATIVE_EXC_GROUP
 from ..utils import with_module
@@ -116,15 +117,20 @@ class Mediator(ABC, Generic[V]):
     """
 
     @abstractmethod
-    def provide(self, request: Request[T], *, extra_stack: Sequence[Request[Any]] = ()) -> T:
+    def provide(self, request: Request[T]) -> T:
         """Get response of sent request.
 
         :param request: A request instance
-        :param extra_stack: Additional stack that will be added to :attr:`.request_stack` before passed request
         :return: Result of the request processing
         :raise CannotProvide: A provider able to process the request does not be found
         """
 
+    @abstractmethod
+    def provide_from_next(self) -> V:
+        """Forward current request to providers
+        that placed after current provider at the recipe.
+        """
+
     @final
     def delegating_provide(
         self,
@@ -181,18 +187,29 @@ def mandatory_provide_by_iterable(
             )
         return results
 
-    @abstractmethod
-    def provide_from_next(self) -> V:
-        """Forward current request to providers
-        that placed after current provider at the recipe.
-        """
 
-    @property
-    @abstractmethod
-    def request_stack(self) -> Sequence[Request[Any]]:
-        """Call stack, but consisting of requests.
-        Last element of ``request_stack`` is current request.
-        """
+def mandatory_apply_by_iterable(
+    func: Callable[..., T],
+    args_iterable: Iterable[VarTuple[Any]],
+    error_describer: Optional[Callable[[], str]] = None,
+) -> Iterable[T]:
+    results = []
+    exceptions = []
+    for args in args_iterable:
+        try:
+            result = func(*args)
+        except CannotProvide as e:
+            exceptions.append(e)
+        else:
+            results.append(result)
+    if exceptions:
+        raise AggregateCannotProvide.make(
+            '' if error_describer is None else error_describer(),
+            exceptions,
+            is_demonstrative=True,
+            is_terminal=True,
+        )
+    return results
 
 
 class Provider(ABC):
diff --git a/src/adaptix/_internal/provider/facade/__init__.py b/src/adaptix/_internal/provider/facade/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/adaptix/_internal/provider/facade/provider.py b/src/adaptix/_internal/provider/facade/provider.py
new file mode 100644
index 00000000..a5bb4362
--- /dev/null
+++ b/src/adaptix/_internal/provider/facade/provider.py
@@ -0,0 +1,23 @@
+from typing import Sequence
+
+from ...utils import Omitted
+from ..essential import Provider
+from ..loc_stack_filtering import OrLocStackChecker, Pred, create_loc_stack_checker
+from ..provider_wrapper import BoundingProvider
+
+
+def bound_by_any(preds: Sequence[Pred], provider: Provider) -> Provider:
+    if len(preds) == 0:
+        return provider
+    if len(preds) == 1:
+        return BoundingProvider(create_loc_stack_checker(preds[0]), provider)
+    return BoundingProvider(
+        OrLocStackChecker([create_loc_stack_checker(pred) for pred in preds]),
+        provider,
+    )
+
+
+def bound(pred: Pred, provider: Provider) -> Provider:
+    if pred == Omitted():
+        return provider
+    return BoundingProvider(create_loc_stack_checker(pred), provider)
diff --git a/src/adaptix/_internal/provider/fields.py b/src/adaptix/_internal/provider/fields.py
new file mode 100644
index 00000000..653b30b7
--- /dev/null
+++ b/src/adaptix/_internal/provider/fields.py
@@ -0,0 +1,55 @@
+from ..model_tools.definitions import BaseField, InputField, OutputField
+from .request_cls import FieldLoc, InputFieldLoc, LocMap, OutputFieldLoc, TypeHintLoc
+
+
+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,
+        )
+    )
+
+
+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 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 field_to_loc_map(field: BaseField) -> LocMap:
+    if isinstance(field, InputField):
+        return input_field_to_loc_map(field)
+    if isinstance(field, OutputField):
+        return output_field_to_loc_map(field)
+    return base_field_to_loc_map(field)
diff --git a/src/adaptix/_internal/provider/loc_stack_filtering.py b/src/adaptix/_internal/provider/loc_stack_filtering.py
new file mode 100644
index 00000000..8e449a89
--- /dev/null
+++ b/src/adaptix/_internal/provider/loc_stack_filtering.py
@@ -0,0 +1,372 @@
+import inspect
+import operator
+import re
+from abc import ABC, abstractmethod
+from copy import copy
+from dataclasses import dataclass
+from functools import reduce
+from inspect import isabstract, isgenerator
+from typing import Any, Callable, ClassVar, Iterable, Optional, Pattern, Protocol, Sequence, Type, TypeVar, Union, final
+
+from ..common import TypeHint, VarTuple
+from ..type_tools import (
+    BaseNormType,
+    NormTV,
+    is_bare_generic,
+    is_generic,
+    is_parametrized,
+    is_protocol,
+    is_subclass_soft,
+    normalize_type,
+)
+from ..type_tools.normalize_type import NotSubscribedError
+from .essential import CannotProvide, Request
+from .request_cls import FieldLoc, GenericParamLoc, Location, LocStack, TypeHintLoc
+
+T = TypeVar('T')
+
+
+class DirectMediator(Protocol):
+    """This is a copy of Mediator protocol but without provide_from_next() method"""
+
+    def provide(self, request: Request[T]) -> T:
+        ...
+
+    def delegating_provide(
+        self,
+        request: Request[T],
+        error_describer: Optional[Callable[[CannotProvide], str]] = None,
+    ) -> T:
+        ...
+
+    def mandatory_provide(
+        self,
+        request: Request[T],
+        error_describer: Optional[Callable[[CannotProvide], str]] = None,
+    ) -> T:
+        ...
+
+    def mandatory_provide_by_iterable(
+        self,
+        requests: Iterable[Request[T]],
+        error_describer: Optional[Callable[[], str]] = None,
+    ) -> Iterable[T]:
+        ...
+
+
+class LocStackChecker(ABC):
+    @abstractmethod
+    def check_loc_stack(self, mediator: DirectMediator, loc_stack: LocStack) -> bool:
+        ...
+
+    @final
+    def __or__(self, other: Any) -> 'LocStackChecker':
+        if isinstance(other, LocStackChecker):
+            return OrLocStackChecker([self, other])
+        return NotImplemented
+
+    @final
+    def __and__(self, other: Any) -> 'LocStackChecker':
+        if isinstance(other, LocStackChecker):
+            return AndLocStackChecker([self, other])
+        return NotImplemented
+
+    @final
+    def __xor__(self, other: Any) -> 'LocStackChecker':
+        if isinstance(other, LocStackChecker):
+            return XorLocStackChecker([self, other])
+        return NotImplemented
+
+    @final
+    def __invert__(self) -> 'LocStackChecker':
+        return InvertLSC(self)
+
+
+class InvertLSC(LocStackChecker):
+    def __init__(self, lsc: LocStackChecker):
+        self._lsc = lsc
+
+    def check_loc_stack(self, mediator: DirectMediator, loc_stack: LocStack) -> bool:
+        return not self._lsc.check_loc_stack(mediator, loc_stack)
+
+
+class BinOperatorLSC(LocStackChecker, ABC):
+    def __init__(self, loc_stack_checkers: Iterable[LocStackChecker]):
+        self._loc_stack_checkers = loc_stack_checkers
+
+    @abstractmethod
+    def _reduce(self, elements: Iterable[bool], /) -> bool:
+        ...
+
+    def check_loc_stack(self, mediator: DirectMediator, loc_stack: LocStack) -> bool:
+        return self._reduce(
+            loc_stack_checker.check_loc_stack(mediator, loc_stack)
+            for loc_stack_checker in self._loc_stack_checkers
+        )
+
+
+class OrLocStackChecker(BinOperatorLSC):
+    _reduce = any  # type: ignore[assignment]
+
+
+class AndLocStackChecker(BinOperatorLSC):
+    _reduce = all  # type: ignore[assignment]
+
+
+class XorLocStackChecker(BinOperatorLSC):
+    def _reduce(self, elements: Iterable[bool], /) -> bool:
+        return reduce(operator.xor, elements)
+
+
+class LastLocMapChecker(LocStackChecker, ABC):
+    _expected_location: ClassVar[Type[Location]]
+
+    def __init_subclass__(cls, **kwargs):
+        param_list = list(inspect.signature(cls._check_location).parameters.values())
+        cls._expected_location = param_list[2].annotation
+
+    @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])
+        return False
+
+    @abstractmethod
+    def _check_location(self, mediator: DirectMediator, loc: Any) -> bool:
+        pass
+
+
+@dataclass(frozen=True)
+class ExactFieldNameLSC(LastLocMapChecker):
+    field_id: str
+
+    def _check_location(self, mediator: DirectMediator, loc: FieldLoc) -> bool:
+        return self.field_id == loc.field_id
+
+
+@dataclass(frozen=True)
+class ReFieldNameLSC(LastLocMapChecker):
+    pattern: Pattern[str]
+
+    def _check_location(self, mediator: DirectMediator, loc: FieldLoc) -> bool:
+        return self.pattern.fullmatch(loc.field_id) is not None
+
+
+@dataclass(frozen=True)
+class ExactTypeLSC(LastLocMapChecker):
+    norm: BaseNormType
+
+    def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
+        try:
+            norm = normalize_type(loc.type)
+        except ValueError:
+            return False
+        return norm == self.norm
+
+
+@dataclass(frozen=True)
+class OriginSubclassLSC(LastLocMapChecker):
+    type_: type
+
+    def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
+        try:
+            norm = normalize_type(loc.type)
+        except ValueError:
+            return False
+        return is_subclass_soft(norm.origin, self.type_)
+
+
+@dataclass(frozen=True)
+class ExactOriginLSC(LastLocMapChecker):
+    origin: Any
+
+    def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> bool:
+        try:
+            norm = normalize_type(loc.type)
+        except ValueError:
+            return False
+        return norm.origin == self.origin
+
+
+@dataclass(frozen=True)
+class GenericParamLSC(LastLocMapChecker):
+    pos: int
+
+    def _check_location(self, mediator: DirectMediator, loc: GenericParamLoc) -> bool:
+        return loc.generic_pos == self.pos
+
+
+@dataclass(frozen=True)
+class LocStackEndChecker(LocStackChecker):
+    loc_stack_checkers: Sequence[LocStackChecker]
+
+    def check_loc_stack(self, mediator: DirectMediator, loc_stack: LocStack) -> bool:
+        if len(loc_stack) < len(self.loc_stack_checkers):
+            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]):
+                return False
+        return True
+
+
+class AnyLocStackChecker(LocStackChecker):
+    def check_loc_stack(self, mediator: DirectMediator, loc_stack: LocStack) -> bool:
+        return True
+
+
+Pred = Union[str, re.Pattern, type, TypeHint, LocStackChecker, 'LocStackPattern']
+
+
+def _create_non_type_hint_loc_stack_checker(pred: Pred) -> Optional[LocStackChecker]:
+    if isinstance(pred, str):
+        if pred.isidentifier():
+            return ExactFieldNameLSC(pred)  # this is only an optimization
+        return ReFieldNameLSC(re.compile(pred))
+
+    if isinstance(pred, re.Pattern):
+        return ReFieldNameLSC(pred)
+
+    if isinstance(pred, LocStackChecker):
+        return pred
+
+    if isinstance(pred, LocStackPattern):
+        return pred.build_loc_stack_checker()
+
+    return None
+
+
+def _create_loc_stack_checker_by_origin(origin):
+    if is_protocol(origin) or isabstract(origin):
+        return OriginSubclassLSC(origin)
+    return ExactOriginLSC(origin)
+
+
+def create_loc_stack_checker(pred: Pred) -> LocStackChecker:
+    result = _create_non_type_hint_loc_stack_checker(pred)
+    if result is not None:
+        return result
+
+    try:
+        norm = normalize_type(pred)
+    except NotSubscribedError:
+        return ExactOriginLSC(pred)
+    except ValueError:
+        raise ValueError(f'Can not create LocStackChecker from {pred}')
+
+    if isinstance(norm, NormTV):
+        raise ValueError(f'Can not create LocStackChecker from {pred} type var')
+
+    if is_bare_generic(pred):
+        return _create_loc_stack_checker_by_origin(norm.origin)
+
+    if is_generic(pred):
+        raise ValueError(
+            f'Can not create LocStackChecker from {pred} generic alias (parametrized generic)'
+        )
+
+    if not is_generic(norm.origin) and not is_parametrized(pred):
+        return _create_loc_stack_checker_by_origin(norm.origin)   # this is only an optimization
+    return ExactTypeLSC(norm)
+
+
+Pat = TypeVar('Pat', bound='LocStackPattern')
+
+
+class LocStackPattern:
+    ANY = AnyLocStackChecker()
+
+    def __init__(self, stack: VarTuple[LocStackChecker]):
+        self._stack = stack
+
+    @classmethod
+    def _from_lsc(cls: Type[Pat], lsc: LocStackChecker) -> Pat:
+        return cls((lsc, ))
+
+    def _extend_stack(self: Pat, elements: Iterable[LocStackChecker]) -> Pat:
+        self_copy = copy(self)
+        self_copy._stack = self._stack + tuple(elements)  # pylint: disable=protected-access
+        return self_copy
+
+    def __getattr__(self: Pat, item: str) -> Pat:
+        if item.startswith('__') and item.endswith('__'):
+            raise AttributeError
+        return self[item]
+
+    def __getitem__(self: Pat, item: Union[Pred, VarTuple[Pred]]) -> Pat:
+        if isinstance(item, tuple) or isgenerator(item):
+            return self._extend_stack(
+                [OrLocStackChecker([self._ensure_loc_stack_checker_from_pred(el) for el in item])]
+            )
+        return self._extend_stack([self._ensure_loc_stack_checker_from_pred(item)])
+
+    def _ensure_loc_stack_checker(self: Pat, other: Union[Pat, LocStackChecker]) -> LocStackChecker:
+        if isinstance(other, LocStackChecker):
+            return other
+        return other.build_loc_stack_checker()
+
+    def __or__(self: Pat, other: Union[Pat, LocStackChecker]) -> Pat:
+        return self._from_lsc(
+            self.build_loc_stack_checker() | self._ensure_loc_stack_checker(other)
+        )
+
+    def __ror__(self: Pat, other: Union[Pat, LocStackChecker]) -> Pat:
+        return self._from_lsc(
+            self._ensure_loc_stack_checker(other) | self.build_loc_stack_checker()
+        )
+
+    def __and__(self: Pat, other: Union[Pat, LocStackChecker]) -> Pat:
+        return self._from_lsc(
+            self.build_loc_stack_checker() & self._ensure_loc_stack_checker(other)
+        )
+
+    def __rand__(self: Pat, other: Union[Pat, LocStackChecker]) -> Pat:
+        return self._from_lsc(
+            self._ensure_loc_stack_checker(other) & self.build_loc_stack_checker()
+        )
+
+    def __xor__(self: Pat, other: Union[Pat, LocStackChecker]) -> Pat:
+        return self._from_lsc(
+            self.build_loc_stack_checker() ^ self._ensure_loc_stack_checker(other)
+        )
+
+    def __rxor__(self: Pat, other: Union[Pat, LocStackChecker]) -> Pat:
+        return self._from_lsc(
+            self._ensure_loc_stack_checker(other) ^ self.build_loc_stack_checker()
+        )
+
+    def __invert__(self: Pat) -> Pat:
+        return self._from_lsc(
+            ~self.build_loc_stack_checker()
+        )
+
+    def __add__(self: Pat, other: Pat) -> Pat:
+        return self._extend_stack(other._stack)
+
+    def _ensure_loc_stack_checker_from_pred(self, pred: Any) -> LocStackChecker:
+        if isinstance(pred, LocStackPattern):
+            raise TypeError(
+                "Can not use LocStackPattern as predicate inside LocStackPattern."
+                " If you want to combine several LocStackPattern, you can use `+` operator"
+            )
+
+        return create_loc_stack_checker(pred)
+
+    def generic_arg(self: Pat, pos: int, pred: Pred) -> Pat:
+        return self._extend_stack(
+            [GenericParamLSC(pos) & self._ensure_loc_stack_checker_from_pred(pred)]
+        )
+
+    def build_loc_stack_checker(self) -> LocStackChecker:
+        if len(self._stack) == 0:
+            raise ValueError(
+                'Can not produce LocStackChecker from LocStackPattern without stack.'
+                ' You need to parametrize P object with predicates.'
+            )
+        if len(self._stack) == 1:
+            return self._stack[0]
+        return LocStackEndChecker(self._stack)
+
+
+P = LocStackPattern(stack=())
diff --git a/src/adaptix/_internal/provider/overlay_schema.py b/src/adaptix/_internal/provider/overlay_schema.py
index 27386c7c..5b53b0bd 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, LocMap, TypeHintLoc
+from .request_cls import LocatedRequest, LocStack, TypeHintLoc
 from .static_provider import StaticProvider, static_provision_action
 
 
@@ -82,20 +82,20 @@ class OverlayRequest(LocatedRequest[Ov], Generic[Ov]):
     overlay_cls: Type[Ov]
 
 
-def provide_schema(overlay: Type[Overlay[Sc]], mediator: Mediator, loc_map: LocMap) -> Sc:
+def provide_schema(overlay: Type[Overlay[Sc]], mediator: Mediator, loc_stack: LocStack) -> Sc:
     stacked_overlay = mediator.mandatory_provide(
         OverlayRequest(
-            loc_map=loc_map,
+            loc_stack=loc_stack,
             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:]:
             try:
                 new_overlay = mediator.delegating_provide(
                     OverlayRequest(
-                        loc_map=loc_map.add(TypeHintLoc(type=parent)),
+                        loc_stack=loc_stack.add_to_last_map(TypeHintLoc(type=parent)),
                         overlay_cls=overlay,
                     )
                 )
diff --git a/src/adaptix/_internal/provider/provider_template.py b/src/adaptix/_internal/provider/provider_template.py
index 6f1504ae..6f8929df 100644
--- a/src/adaptix/_internal/provider/provider_template.py
+++ b/src/adaptix/_internal/provider/provider_template.py
@@ -1,28 +1,28 @@
 from typing import Generic, Optional, Type, TypeVar
 
 from .essential import CannotProvide, Mediator, Request
-from .provider_wrapper import RequestClassDeterminedProvider
-from .request_filtering import AnyRequestChecker, Pred, ProviderWithRC, RequestChecker, create_request_checker
+from .loc_stack_filtering import LocStackChecker, P, Pred, create_loc_stack_checker
+from .provider_wrapper import ProviderWithLSC, RequestClassDeterminedProvider
 from .static_provider import StaticProvider
 
 T = TypeVar('T')
 
 
-class ProviderWithAttachableRC(StaticProvider, ProviderWithRC):
-    _request_checker: RequestChecker = AnyRequestChecker()
+class ProviderWithAttachableLSC(StaticProvider, ProviderWithLSC):
+    _loc_stack_checker: LocStackChecker = P.ANY
 
-    def get_request_checker(self) -> Optional[RequestChecker]:
-        return self._request_checker
+    def get_loc_stack_checker(self) -> Optional[LocStackChecker]:
+        return self._loc_stack_checker
 
 
 def for_predicate(pred: Pred):
-    def decorator(cls: Type[ProviderWithAttachableRC]):
-        if not (isinstance(cls, type) and issubclass(cls, ProviderWithAttachableRC)):
-            raise TypeError(f"Only {ProviderWithAttachableRC} child is allowed")
+    def decorator(cls: Type[ProviderWithAttachableLSC]):
+        if not (isinstance(cls, type) and issubclass(cls, ProviderWithAttachableLSC)):
+            raise TypeError(f"Only {ProviderWithAttachableLSC} child is allowed")
 
         # noinspection PyProtectedMember
         # pylint: disable=protected-access
-        cls._request_checker = create_request_checker(pred)
+        cls._loc_stack_checker = create_loc_stack_checker(pred)
         return cls
 
     return decorator
diff --git a/src/adaptix/_internal/provider/provider_wrapper.py b/src/adaptix/_internal/provider/provider_wrapper.py
index bef7fa5e..53d75fec 100644
--- a/src/adaptix/_internal/provider/provider_wrapper.py
+++ b/src/adaptix/_internal/provider/provider_wrapper.py
@@ -3,7 +3,8 @@
 from typing import Optional, Type, TypeVar
 
 from .essential import AggregateCannotProvide, CannotProvide, Mediator, Provider, Request
-from .request_filtering import ProviderWithRC, RequestChecker
+from .loc_stack_filtering import LocStackChecker
+from .request_cls import LocatedRequest
 
 T = TypeVar('T')
 
@@ -14,25 +15,42 @@ def maybe_can_process_request_cls(self, request_cls: Type[Request]) -> bool:
         ...
 
 
-class BoundingProvider(RequestClassDeterminedProvider, ProviderWithRC):
-    def __init__(self, request_checker: RequestChecker, provider: Provider):
-        self._request_checker = request_checker
+class ProviderWithLSC(Provider, ABC):
+    @abstractmethod
+    def get_loc_stack_checker(self) -> Optional[LocStackChecker]:
+        ...
+
+    def _apply_loc_stack_checker(self, mediator: Mediator, request: Request) -> None:
+        if not isinstance(request, LocatedRequest):
+            raise CannotProvide
+
+        loc_stack_checker = self.get_loc_stack_checker()
+        if loc_stack_checker is None:
+            return
+
+        if not loc_stack_checker.check_loc_stack(mediator, request.loc_stack):
+            raise CannotProvide
+
+
+class BoundingProvider(RequestClassDeterminedProvider, ProviderWithLSC):
+    def __init__(self, loc_stack_checker: LocStackChecker, provider: Provider):
+        self._loc_stack_checker = loc_stack_checker
         self._provider = provider
 
     def apply_provider(self, mediator: Mediator, request: Request[T]) -> T:
-        self._request_checker.check_request(mediator, request)
+        self._apply_loc_stack_checker(mediator, request)
         return self._provider.apply_provider(mediator, request)
 
     def __repr__(self):
-        return f"{type(self).__name__}({self._request_checker}, {self._provider})"
+        return f"{type(self).__name__}({self._loc_stack_checker}, {self._provider})"
 
     def maybe_can_process_request_cls(self, request_cls: Type[Request]) -> bool:
         if isinstance(self._provider, RequestClassDeterminedProvider):
             return self._provider.maybe_can_process_request_cls(request_cls)
         return True
 
-    def get_request_checker(self) -> Optional[RequestChecker]:
-        return self._request_checker
+    def get_loc_stack_checker(self) -> Optional[LocStackChecker]:
+        return self._loc_stack_checker
 
 
 class ConcatProvider(RequestClassDeterminedProvider):
diff --git a/src/adaptix/_internal/provider/request_cls.py b/src/adaptix/_internal/provider/request_cls.py
index a1986f2f..4edf66e1 100644
--- a/src/adaptix/_internal/provider/request_cls.py
+++ b/src/adaptix/_internal/provider/request_cls.py
@@ -1,11 +1,12 @@
 from dataclasses import dataclass, field
-from typing import Any, Mapping, TypeVar
+from typing import Any, Mapping, Tuple, TypeVar
 
 from ..common import TypeHint
-from ..datastructures import ClassMap
+from ..datastructures import ClassMap, 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
 
 T = TypeVar('T')
@@ -23,7 +24,6 @@ class TypeHintLoc(Location):
 
 @dataclass(frozen=True)
 class FieldLoc(Location):
-    owner_type: TypeHint
     field_id: str
     default: Default
     metadata: Mapping[Any, Any] = field(hash=False)
@@ -46,15 +46,28 @@ class GenericParamLoc(Location):
 
 LocMap = ClassMap[Location]
 LR = TypeVar('LR', bound='LocatedRequest')
+LocStackT = TypeVar('LocStackT', bound='LocStack')
+
+
+class LocStack(ImmutableStack[LocMap]):
+    def replace_last(self: LocStackT, loc: LocMap) -> LocStackT:
+        return self[:-1].append_with(loc)
+
+    def add_to_last_map(self: LocStackT, *locs: Location) -> LocStackT:
+        return self.replace_last(self[-1].add(*locs))
 
 
 @dataclass(frozen=True)
 class LocatedRequest(Request[T]):
-    loc_map: LocMap
+    loc_stack: LocStack
+
+    @property
+    def last_map(self) -> LocMap:
+        return self.loc_stack[-1]
 
 
 def get_type_from_request(request: LocatedRequest) -> TypeHint:
-    return request.loc_map.get_or_raise(TypeHintLoc, CannotProvide).type
+    return request.last_map.get_or_raise(TypeHintLoc, CannotProvide).type
 
 
 def try_normalize_type(tp: TypeHint) -> BaseNormType:
@@ -70,3 +83,10 @@ class StrictCoercionRequest(LocatedRequest[bool]):
 
 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
+    raise ValueError
diff --git a/src/adaptix/_internal/provider/request_filtering.py b/src/adaptix/_internal/provider/request_filtering.py
deleted file mode 100644
index c378dd86..00000000
--- a/src/adaptix/_internal/provider/request_filtering.py
+++ /dev/null
@@ -1,440 +0,0 @@
-import re
-from abc import ABC, abstractmethod
-from copy import copy
-from dataclasses import dataclass
-from inspect import isabstract, isgenerator
-from typing import Any, ClassVar, Iterable, Optional, Pattern, Protocol, Sequence, Tuple, Type, TypeVar, Union
-
-from ..common import TypeHint, VarTuple
-from ..type_tools import (
-    BaseNormType,
-    NormTV,
-    is_bare_generic,
-    is_generic,
-    is_parametrized,
-    is_protocol,
-    is_subclass_soft,
-    normalize_type,
-)
-from ..type_tools.normalize_type import NotSubscribedError
-from .essential import AggregateCannotProvide, CannotProvide, Mediator, Provider, Request
-from .request_cls import FieldLoc, GenericParamLoc, LocatedRequest, Location, TypeHintLoc, try_normalize_type
-
-T = TypeVar('T')
-
-
-class DirectMediator(Protocol):
-    """This is copy of Mediator protocol but without provide_from_next() method"""
-
-    def provide(self, request: Request[T]) -> T:
-        ...
-
-    @property
-    def request_stack(self) -> Sequence[Request[Any]]:
-        ...
-
-
-class RequestChecker(ABC):
-    @abstractmethod
-    def check_request(self, mediator: DirectMediator, request: Request) -> None:
-        """Raise CannotProvide if the request does not meet the conditions"""
-
-    def __or__(self, other: Any) -> 'RequestChecker':
-        if isinstance(other, RequestChecker):
-            return OrRequestChecker([self, other])
-        return NotImplemented
-
-    def __and__(self, other: Any) -> 'RequestChecker':
-        if isinstance(other, RequestChecker):
-            return AndRequestChecker([self, other])
-        return NotImplemented
-
-    def __xor__(self, other: Any) -> 'RequestChecker':
-        if isinstance(other, RequestChecker):
-            return XorRequestChecker(self, other)
-        return NotImplemented
-
-    def __invert__(self) -> 'RequestChecker':
-        return NegRequestChecker(self)
-
-
-class ProviderWithRC(Provider, ABC):
-    @abstractmethod
-    def get_request_checker(self) -> Optional[RequestChecker]:
-        ...
-
-
-class OrRequestChecker(RequestChecker):
-    def __init__(self, request_checkers: Iterable[RequestChecker]):
-        self._request_checkers = request_checkers
-
-    def check_request(self, mediator: DirectMediator, request: Request) -> None:
-        sub_errors = []
-
-        for checker in self._request_checkers:
-            try:
-                checker.check_request(mediator, request)
-            except CannotProvide as e:
-                sub_errors.append(e)
-            else:
-                return
-
-        raise AggregateCannotProvide.make('', exceptions=sub_errors)
-
-
-class AndRequestChecker(RequestChecker):
-    def __init__(self, request_checkers: Iterable[RequestChecker]):
-        self._request_checkers = request_checkers
-
-    def check_request(self, mediator: DirectMediator, request: Request) -> None:
-        for checker in self._request_checkers:
-            checker.check_request(mediator, request)
-
-
-class NegRequestChecker(RequestChecker):
-    def __init__(self, rc: RequestChecker):
-        self._rc = rc
-
-    def check_request(self, mediator: DirectMediator, request: Request) -> None:
-        try:
-            self._rc.check_request(mediator, request)
-        except CannotProvide:
-            return
-        raise CannotProvide
-
-
-class XorRequestChecker(RequestChecker):
-    def __init__(self, left: RequestChecker, right: RequestChecker):
-        self._left = left
-        self._right = right
-
-    def check_request(self, mediator: DirectMediator, request: Request) -> None:
-        exceptions = []
-
-        try:
-            self._left.check_request(mediator, request)
-        except CannotProvide as exc:
-            exceptions.append(exc)
-
-        try:
-            self._right.check_request(mediator, request)
-        except CannotProvide as exc:
-            exceptions.append(exc)
-
-        if len(exceptions) == 0:
-            raise CannotProvide
-
-        if len(exceptions) == 2:
-            raise AggregateCannotProvide.make('', exceptions=exceptions)
-
-
-class LocatedRequestChecker(RequestChecker, ABC):
-    LOCATION: ClassVar[Type[Location]]
-
-    def check_request(self, mediator: DirectMediator, request: Request) -> None:
-        if not isinstance(request, LocatedRequest):
-            raise CannotProvide(f'Request must be instance of {LocatedRequest}')
-        if self.LOCATION not in request.loc_map:
-            raise CannotProvide(f'Request location must be instance of {self.LOCATION}')
-        self._check_location(mediator, request.loc_map[self.LOCATION])
-
-    @abstractmethod
-    def _check_location(self, mediator: DirectMediator, loc: Any) -> None:
-        ...
-
-
-@dataclass(frozen=True)
-class ExactFieldNameRC(LocatedRequestChecker):
-    LOCATION = FieldLoc
-    field_id: str
-
-    def _check_location(self, mediator: DirectMediator, loc: FieldLoc) -> None:
-        if self.field_id == loc.field_id:
-            return
-        raise CannotProvide(f'field_id must be a {self.field_id!r}')
-
-
-@dataclass(frozen=True)
-class ReFieldNameRC(LocatedRequestChecker):
-    LOCATION = FieldLoc
-    pattern: Pattern[str]
-
-    def _check_location(self, mediator: DirectMediator, loc: FieldLoc) -> None:
-        if self.pattern.fullmatch(loc.field_id):
-            return
-
-        raise CannotProvide(f'field_id must be matched by {self.pattern!r}')
-
-
-@dataclass(frozen=True)
-class ExactTypeRC(LocatedRequestChecker):
-    LOCATION = TypeHintLoc
-    norm: BaseNormType
-
-    def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> None:
-        if try_normalize_type(loc.type) == self.norm:
-            return
-        raise CannotProvide(f'{loc.type} must be a equal to {self.norm.source}')
-
-
-@dataclass(frozen=True)
-class OriginSubclassRC(LocatedRequestChecker):
-    LOCATION = TypeHintLoc
-    type_: type
-
-    def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> None:
-        norm = try_normalize_type(loc.type)
-        if is_subclass_soft(norm.origin, self.type_):
-            return
-        raise CannotProvide(f'{loc.type} must be a subclass of {self.type_}')
-
-
-@dataclass(frozen=True)
-class ExactOriginRC(LocatedRequestChecker):
-    LOCATION = TypeHintLoc
-    origin: Any
-
-    def _check_location(self, mediator: DirectMediator, loc: TypeHintLoc) -> None:
-        if try_normalize_type(loc.type).origin == self.origin:
-            return
-        raise CannotProvide(f'{loc.type} must have origin {self.origin}')
-
-
-class ExactOriginMergedProvider(Provider):
-    def __init__(self, origins_to_providers: Sequence[Tuple[ExactOriginRC, Provider]]):
-        self.origin_to_provider = {
-            request_checker.origin: provider
-            for request_checker, provider in reversed(origins_to_providers)
-        }
-
-    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.loc_map.get_or_raise(
-            TypeHintLoc,
-            lambda: CannotProvide(f'Request location must be instance of {TypeHintLoc}')
-        )
-        norm = try_normalize_type(loc.type)
-        try:
-            provider = self.origin_to_provider[norm.origin]
-        except KeyError:
-            raise CannotProvide from None
-
-        return provider.apply_provider(mediator, request)
-
-
-class RequestStackCutterMediator(DirectMediator):
-    def __init__(self, mediator: DirectMediator, end_offset: int):
-        self._mediator = mediator
-        self._end_offset = end_offset
-
-    def provide(self, request: Request[T]) -> T:
-        return self._mediator.provide(request)
-
-    @property
-    def request_stack(self) -> Sequence[Request[Any]]:
-        return self._mediator.request_stack[:-self._end_offset or None]
-
-
-class ExtraStackMediator(DirectMediator):
-    def __init__(self, mediator: Mediator, extra_stack: Sequence[Request[Any]]):
-        self._mediator = mediator
-        self._extra_stack = extra_stack
-
-    def provide(self, request: Request[T]) -> T:
-        return self._mediator.provide(request, extra_stack=self._extra_stack)
-
-    @property
-    def request_stack(self) -> Sequence[Request[Any]]:
-        return [*self._mediator.request_stack, *self._extra_stack]
-
-
-@dataclass(frozen=True)
-class StackEndRC(RequestChecker):
-    request_checkers: Sequence[RequestChecker]
-
-    def check_request(self, mediator: DirectMediator, request: Request[T]) -> None:
-        stack = mediator.request_stack
-        offset = len(stack) - len(self.request_checkers)
-
-        if offset < 0:
-            raise CannotProvide("Request stack is too small")
-
-        for i, (checker, stack_request) in enumerate(zip(self.request_checkers, stack[offset:]), start=0):
-            checker.check_request(
-                RequestStackCutterMediator(mediator, i),
-                stack_request,
-            )
-
-
-@dataclass(frozen=True)
-class GenericParamRC(LocatedRequestChecker):
-    LOCATION = GenericParamLoc
-    pos: int
-
-    def _check_location(self, mediator: DirectMediator, loc: GenericParamLoc) -> None:
-        if loc.generic_pos == self.pos:
-            return
-        raise CannotProvide(f'Generic param position {loc.generic_pos} must be equal to {self.pos}')
-
-
-class AnyRequestChecker(RequestChecker):
-    def check_request(self, mediator: DirectMediator, request: Request) -> None:
-        return
-
-
-Pred = Union[str, re.Pattern, type, TypeHint, RequestChecker, 'RequestPattern']
-
-
-def _create_non_type_hint_request_checker(pred: Pred) -> Optional[RequestChecker]:
-    if isinstance(pred, str):
-        if pred.isidentifier():
-            return ExactFieldNameRC(pred)  # this is only an optimization
-        return ReFieldNameRC(re.compile(pred))
-
-    if isinstance(pred, re.Pattern):
-        return ReFieldNameRC(pred)
-
-    if isinstance(pred, RequestChecker):
-        return pred
-
-    if isinstance(pred, RequestPattern):
-        return pred.build_request_checker()
-
-    return None
-
-
-def _create_request_checker_by_origin(origin):
-    if is_protocol(origin) or isabstract(origin):
-        return OriginSubclassRC(origin)
-    return ExactOriginRC(origin)
-
-
-def create_request_checker(pred: Pred) -> RequestChecker:
-    result = _create_non_type_hint_request_checker(pred)
-    if result is not None:
-        return result
-
-    try:
-        norm = normalize_type(pred)
-    except NotSubscribedError:
-        return ExactOriginRC(pred)
-    except ValueError:
-        raise ValueError(f'Can not create RequestChecker from {pred}')
-
-    if isinstance(norm, NormTV):
-        raise ValueError(f'Can not create RequestChecker from {pred} type var')
-
-    if is_bare_generic(pred):
-        return _create_request_checker_by_origin(norm.origin)
-
-    if is_generic(pred):
-        raise ValueError(
-            f'Can not create RequestChecker from {pred} generic alias (parametrized generic)'
-        )
-
-    if not is_generic(norm.origin) and not is_parametrized(pred):
-        return _create_request_checker_by_origin(norm.origin)   # this is only an optimization
-    return ExactTypeRC(norm)
-
-
-Pat = TypeVar('Pat', bound='RequestPattern')
-
-
-class RequestPattern:
-    ANY = AnyRequestChecker()
-
-    def __init__(self, stack: VarTuple[RequestChecker]):
-        self._stack = stack
-
-    @classmethod
-    def _from_rc(cls: Type[Pat], rc: RequestChecker) -> Pat:
-        return cls((rc, ))
-
-    def _extend_stack(self: Pat, elements: Iterable[RequestChecker]) -> Pat:
-        self_copy = copy(self)
-        self_copy._stack = self._stack + tuple(elements)  # pylint: disable=protected-access
-        return self_copy
-
-    def __getattr__(self: Pat, item: str) -> Pat:
-        if item.startswith('__') and item.endswith('__'):
-            raise AttributeError
-        return self[item]
-
-    def __getitem__(self: Pat, item: Union[Pred, VarTuple[Pred]]) -> Pat:
-        if isinstance(item, tuple) or isgenerator(item):
-            return self._extend_stack(
-                [OrRequestChecker([self._ensure_request_checker_from_pred(el) for el in item])]
-            )
-        return self._extend_stack([self._ensure_request_checker_from_pred(item)])
-
-    def _ensure_request_checker(self: Pat, other: Union[Pat, RequestChecker]) -> RequestChecker:
-        if isinstance(other, RequestChecker):
-            return other
-        return other.build_request_checker()
-
-    def __or__(self: Pat, other: Union[Pat, RequestChecker]) -> Pat:
-        return self._from_rc(
-            self.build_request_checker() | self._ensure_request_checker(other)
-        )
-
-    def __ror__(self: Pat, other: Union[Pat, RequestChecker]) -> Pat:
-        return self._from_rc(
-            self._ensure_request_checker(other) | self.build_request_checker()
-        )
-
-    def __and__(self: Pat, other: Union[Pat, RequestChecker]) -> Pat:
-        return self._from_rc(
-            self.build_request_checker() & self._ensure_request_checker(other)
-        )
-
-    def __rand__(self: Pat, other: Union[Pat, RequestChecker]) -> Pat:
-        return self._from_rc(
-            self._ensure_request_checker(other) & self.build_request_checker()
-        )
-
-    def __xor__(self: Pat, other: Union[Pat, RequestChecker]) -> Pat:
-        return self._from_rc(
-            self.build_request_checker() ^ self._ensure_request_checker(other)
-        )
-
-    def __rxor__(self: Pat, other: Union[Pat, RequestChecker]) -> Pat:
-        return self._from_rc(
-            self._ensure_request_checker(other) ^ self.build_request_checker()
-        )
-
-    def __invert__(self: Pat) -> Pat:
-        return self._from_rc(
-            ~self.build_request_checker()
-        )
-
-    def __add__(self: Pat, other: Pat) -> Pat:
-        return self._extend_stack(other._stack)
-
-    def _ensure_request_checker_from_pred(self, pred: Any) -> RequestChecker:
-        if isinstance(pred, RequestPattern):
-            raise TypeError(
-                "Can not use RequestPattern as predicate inside RequestPattern."
-                " If you want to combine several RequestPattern, you can use `+` operator"
-            )
-
-        return create_request_checker(pred)
-
-    def generic_arg(self: Pat, pos: int, pred: Pred) -> Pat:
-        return self._extend_stack(
-            [GenericParamRC(pos) & self._ensure_request_checker_from_pred(pred)]
-        )
-
-    def build_request_checker(self) -> RequestChecker:
-        if len(self._stack) == 0:
-            raise ValueError(
-                'Can not produce RequestChecker from RequestPattern without stack.'
-                ' You need to parametrize P object with predicates.'
-            )
-        if len(self._stack) == 1:
-            return self._stack[0]
-        return StackEndRC(self._stack)
-
-
-P = RequestPattern(stack=())
diff --git a/src/adaptix/_internal/provider/shape_provider.py b/src/adaptix/_internal/provider/shape_provider.py
index 4e9e75f9..78347dca 100644
--- a/src/adaptix/_internal/provider/shape_provider.py
+++ b/src/adaptix/_internal/provider/shape_provider.py
@@ -4,23 +4,28 @@
 
 from ..common import TypeHint
 from ..model_tools.definitions import (
+    ClarifiedIntrospectionImpossible,
     DescriptorAccessor,
     InputShape,
     IntrospectionImpossible,
     OutputField,
     OutputShape,
+    PackageIsTooOld,
+    Shape,
     ShapeIntrospector,
 )
 from ..model_tools.introspection.attrs import get_attrs_shape
 from ..model_tools.introspection.class_init import get_class_init_shape
 from ..model_tools.introspection.dataclass import get_dataclass_shape
 from ..model_tools.introspection.named_tuple import get_named_tuple_shape
+from ..model_tools.introspection.sqlalchemy import get_sqlalchemy_shape
 from ..model_tools.introspection.typed_dict import get_typed_dict_shape
 from ..provider.essential import CannotProvide, Mediator
+from ..provider.loc_stack_filtering import create_loc_stack_checker
 from ..type_tools.generic_resolver import GenericResolver, MembersStorage
-from .provider_template import ProviderWithAttachableRC
+from .provider_template import ProviderWithAttachableLSC
+from .provider_wrapper import ConcatProvider
 from .request_cls import LocatedRequest, TypeHintLoc
-from .request_filtering import create_request_checker
 from .static_provider import StaticProvider, static_provision_action
 
 
@@ -38,38 +43,45 @@ class ShapeProvider(StaticProvider):
     def __init__(self, introspector: ShapeIntrospector):
         self._introspector = introspector
 
-    @static_provision_action
-    def _provide_input_shape(self, mediator: Mediator, request: InputShapeRequest) -> InputShape:
-        loc = request.loc_map.get_or_raise(TypeHintLoc, CannotProvide)
+    def __repr__(self):
+        return f"{type(self)}({self._introspector})"
+
+    def _get_shape(self, tp) -> Shape:
         try:
-            shape = self._introspector(loc.type)
-        except IntrospectionImpossible:
-            raise CannotProvide
+            return self._introspector(tp)
+        except PackageIsTooOld as e:
+            raise CannotProvide(message=e.requirement.fail_reason, is_demonstrative=True) from e
+        except ClarifiedIntrospectionImpossible as e:
+            raise CannotProvide(message=e.description, is_demonstrative=True) from e
+        except IntrospectionImpossible 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)
+        shape = self._get_shape(loc.type)
         if shape.input is None:
             raise CannotProvide
-
         return shape.input
 
     @static_provision_action
     def _provide_output_shape(self, mediator: Mediator, request: OutputShapeRequest) -> OutputShape:
-        loc = request.loc_map.get_or_raise(TypeHintLoc, CannotProvide)
-        try:
-            shape = self._introspector(loc.type)
-        except IntrospectionImpossible:
-            raise CannotProvide
-
-        if shape.output is None:
+        loc = request.last_map.get_or_raise(TypeHintLoc, CannotProvide)
+        shape = self._get_shape(loc.type)
+        if shape.input is None:
             raise CannotProvide
-
         return shape.output
 
 
-NAMED_TUPLE_SHAPE_PROVIDER = ShapeProvider(get_named_tuple_shape)
-TYPED_DICT_SHAPE_PROVIDER = ShapeProvider(get_typed_dict_shape)
-DATACLASS_SHAPE_PROVIDER = ShapeProvider(get_dataclass_shape)
-CLASS_INIT_SHAPE_PROVIDER = ShapeProvider(get_class_init_shape)
-ATTRS_SHAPE_PROVIDER = ShapeProvider(get_attrs_shape)
+BUILTIN_SHAPE_PROVIDER = ConcatProvider(
+    ShapeProvider(get_named_tuple_shape),
+    ShapeProvider(get_typed_dict_shape),
+    ShapeProvider(get_dataclass_shape),
+    ShapeProvider(get_attrs_shape),
+    ShapeProvider(get_sqlalchemy_shape),
+    # class init introspection must be the last
+    ShapeProvider(get_class_init_shape),
+)
 
 
 class PropertyExtender(StaticProvider):
@@ -92,7 +104,7 @@ def __init__(
 
     @static_provision_action
     def _provide_output_shape(self, mediator: Mediator[OutputShape], request: OutputShapeRequest) -> OutputShape:
-        tp = request.loc_map.get_or_raise(TypeHintLoc, CannotProvide).type
+        tp = request.last_map.get_or_raise(TypeHintLoc, CannotProvide).type
         shape = mediator.provide_from_next()
 
         additional_fields = tuple(
@@ -133,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.loc_map[TypeHintLoc].type
+            self._initial_request.last_map[TypeHintLoc].type
         )
         if members_storage.meta is None:
             raise CannotProvide
@@ -150,7 +162,7 @@ def _get_members(self, tp) -> MembersStorage[str, Optional[ShapeT]]:
             shape = self._mediator.delegating_provide(
                 replace(
                     self._initial_request,
-                    loc_map=self._initial_request.loc_map.add(TypeHintLoc(type=tp)),
+                    loc_stack=self._initial_request.loc_stack.add_to_last_map(TypeHintLoc(type=tp)),
                 )
             )
         except CannotProvide:
@@ -167,7 +179,7 @@ def _get_members(self, tp) -> MembersStorage[str, Optional[ShapeT]]:
 
 
 def provide_generic_resolved_shape(mediator: Mediator, request: LocatedRequest[ShapeT]) -> ShapeT:
-    if not request.loc_map.has(TypeHintLoc):
+    if not request.last_map.has(TypeHintLoc):
         return mediator.delegating_provide(request)
     return ShapeGenericResolver(mediator, request).provide()
 
@@ -175,11 +187,11 @@ def provide_generic_resolved_shape(mediator: Mediator, request: LocatedRequest[S
 T = TypeVar('T')
 
 
-class SimilarShapeProvider(ProviderWithAttachableRC):
+class SimilarShapeProvider(ProviderWithAttachableLSC):
     def __init__(self, target: TypeHint, prototype: TypeHint, for_input: bool = True, for_output: bool = True):
         self._target = target
         self._prototype = prototype
-        self._request_checker = create_request_checker(self._target)
+        self._loc_stack_checker = create_loc_stack_checker(self._target)
         self._for_input = for_input
         self._for_output = for_output
 
@@ -188,11 +200,11 @@ def _provide_input_shape(self, mediator: Mediator, request: InputShapeRequest) -
         if not self._for_input:
             raise CannotProvide
 
-        self._request_checker.check_request(mediator, request)
+        self._apply_loc_stack_checker(mediator, request)
         shape = mediator.delegating_provide(
             replace(
                 request,
-                loc_map=request.loc_map.add(TypeHintLoc(self._prototype))
+                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(self._prototype)),
             )
         )
         return replace(shape, constructor=self._target)
@@ -202,11 +214,11 @@ def _provide_output_shape(self, mediator: Mediator, request: OutputShapeRequest)
         if not self._for_output:
             raise CannotProvide
 
-        self._request_checker.check_request(mediator, request)
+        self._apply_loc_stack_checker(mediator, request)
         shape = mediator.delegating_provide(
             replace(
                 request,
-                loc_map=request.loc_map.add(TypeHintLoc(self._prototype))
+                loc_stack=request.loc_stack.add_to_last_map(TypeHintLoc(self._prototype)),
             )
         )
         return shape
diff --git a/src/adaptix/_internal/provider/utils.py b/src/adaptix/_internal/provider/utils.py
deleted file mode 100644
index 1315f2e6..00000000
--- a/src/adaptix/_internal/provider/utils.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from typing import Sequence
-
-from .essential import Request
-from .request_cls import FieldLoc, LocatedRequest
-
-
-def find_field_request(request_stack: Sequence[Request]) -> LocatedRequest:
-    for parent_request in request_stack:
-        if isinstance(parent_request, LocatedRequest) and parent_request.loc_map.has(FieldLoc):
-            return parent_request
-    raise ValueError('Owner type is not found')
diff --git a/src/adaptix/_internal/retort/base_retort.py b/src/adaptix/_internal/retort/base_retort.py
index 7da67b63..00176e90 100644
--- a/src/adaptix/_internal/retort/base_retort.py
+++ b/src/adaptix/_internal/retort/base_retort.py
@@ -4,7 +4,8 @@
 from ..common import VarTuple
 from ..provider.essential import Mediator, Provider, Request
 from ..utils import Cloneable, ForbiddingDescriptor
-from .mediator import BuiltinMediator, ErrorRepresentor, IntrospectingRecipeSearcher, RecipeSearcher, RecursionResolving
+from .mediator import BuiltinMediator, ErrorRepresentor, RecursionResolver
+from .routing import IntrospectingRecipeSearcher, RecipeSearcher
 
 
 class RetortMeta(ABCMeta):  # inherits from ABCMeta to be compatible with ABC
@@ -54,7 +55,7 @@ def _get_config_recipe(self) -> VarTuple[Provider]:
     def _get_full_recipe(self) -> Sequence[Provider]:
         return self._full_recipe
 
-    def _calculate_derived(self):
+    def _calculate_derived(self) -> None:
         super()._calculate_derived()
         self._full_recipe = (
             self._inc_instance_recipe
@@ -67,28 +68,27 @@ def _create_searcher(self, full_recipe: Sequence[Provider]) -> RecipeSearcher:
         return IntrospectingRecipeSearcher(full_recipe)
 
     @abstractmethod
-    def _get_recursion_resolving(self) -> RecursionResolving:
+    def _create_recursion_resolver(self) -> RecursionResolver:
         ...
 
     @abstractmethod
     def _get_error_representor(self) -> ErrorRepresentor:
         ...
 
-    def _create_mediator(self, request_stack: Sequence[Request]) -> Mediator:
-        recursion_resolving = self._get_recursion_resolving()
+    def _create_mediator(self) -> Mediator:
+        recursion_resolver = self._create_recursion_resolver()
         error_representor = self._get_error_representor()
         return BuiltinMediator(
             self._searcher,
-            recursion_resolving,
+            recursion_resolver,
             error_representor,
-            request_stack,
         )
 
-    def _provide_from_recipe(self, request: Request[T], request_stack: Sequence[Request]) -> T:
+    def _provide_from_recipe(self, request: Request[T]) -> T:
         """Process request iterating over the result of _get_full_recipe()
         :param request:
         :return: request result
         :raise CannotProvide: request was not processed
         """
-        mediator = self._create_mediator(request_stack)
+        mediator = self._create_mediator()
         return mediator.provide(request)
diff --git a/src/adaptix/_internal/retort/mediator.py b/src/adaptix/_internal/retort/mediator.py
index e2d85c0b..fb78a139 100644
--- a/src/adaptix/_internal/retort/mediator.py
+++ b/src/adaptix/_internal/retort/mediator.py
@@ -1,51 +1,23 @@
 from abc import ABC, abstractmethod
-from itertools import islice
-from typing import Any, Callable, Dict, Generic, Iterable, List, Sequence, Set, Tuple, Type, TypeVar
+from typing import Any, Dict, Generic, Iterable, Optional, TypeVar
 
-from ..common import TypeHint
-from ..datastructures import ClassDispatcher
-from ..provider.essential import AggregateCannotProvide, CannotProvide, Mediator, Provider, Request
-from ..provider.provider_wrapper import RequestClassDeterminedProvider
-from ..provider.request_filtering import ExactOriginMergedProvider, ExactOriginRC, ProviderWithRC
+from ..provider.essential import AggregateCannotProvide, CannotProvide, Mediator, Request
 from ..utils import add_note
+from .routing import RecipeSearcher
 
 T = TypeVar('T')
 
 
-class StubsRecursionResolver(ABC, Generic[T]):
+class RecursionResolver(ABC, Generic[T]):
     @abstractmethod
-    def create_stub(self, request: Request[T]) -> T:
+    def track_recursion(self, request: Request[T]) -> Optional[Any]:
         ...
 
     @abstractmethod
-    def saturate_stub(self, actual: T, stub: T) -> None:
+    def process_request_result(self, request: Request[T], result: T) -> None:
         ...
 
 
-ProvideCallable = Callable[[Mediator, Request[T]], T]
-SearchResult = Tuple[ProvideCallable[T], int]
-
-
-class RecipeSearcher(ABC):
-    """An object that implements iterating over recipe list.
-
-    An offset of each element must belong to [0; max_offset)
-    """
-
-    @abstractmethod
-    def search_candidates(self, search_offset: int, request: Request[T]) -> Iterable[SearchResult[T]]:
-        ...
-
-    @abstractmethod
-    def get_max_offset(self) -> int:
-        ...
-
-    @abstractmethod
-    def clear_cache(self):
-        ...
-
-
-RecursionResolving = ClassDispatcher[Request, StubsRecursionResolver]
 E = TypeVar('E', bound=Exception)
 
 
@@ -55,7 +27,7 @@ def get_no_provider_description(self, request: Request) -> str:
         ...
 
     @abstractmethod
-    def get_request_context_notes(self, request: Request, request_stack: Sequence[Request]) -> Iterable[str]:
+    def get_request_context_notes(self, request: Request) -> Iterable[str]:
         ...
 
 
@@ -63,57 +35,35 @@ class BuiltinMediator(Mediator):
     def __init__(
         self,
         searcher: RecipeSearcher,
-        recursion_resolving: RecursionResolving,
+        recursion_resolver: RecursionResolver,
         error_representor: ErrorRepresentor,
-        request_stack: Sequence[Request[Any]],
     ):
         self.searcher = searcher
-        self.recursion_resolving = recursion_resolving
+        self.recursion_resolver = recursion_resolver
         self.error_representor = error_representor
 
-        self._request_stack = list(request_stack)
-        self._sent_request: Set[Request] = set()
+        self._current_request: Optional[Request] = None
         self.next_offset = 0
         self.recursion_stubs: Dict[Request, Any] = {}
 
-    def provide(self, request: Request[T], *, extra_stack: Sequence[Request[Any]] = ()) -> T:
-        if request in self._sent_request:
-            if request in self.recursion_stubs:
-                return self.recursion_stubs[request]
-            try:
-                resolver = self._get_resolver(request)
-            except KeyError:
-                raise RecursionError("Infinite recursion has been detected that can not be resolved") from None
-
-            stub = resolver.create_stub(request)
-            self.recursion_stubs[request] = stub
+    def provide(self, request: Request[T]) -> T:
+        stub = self.recursion_resolver.track_recursion(request)
+        if stub is not None:
             return stub
 
-        self._request_stack.extend(extra_stack)
-        self._request_stack.append(request)
-        self._sent_request.add(request)
+        self._current_request = request
         try:
             result = self._provide_non_recursive(request, 0)
         finally:
-            del self._request_stack[-1 - len(extra_stack):]
-            self._sent_request.discard(request)
-
-        if request in self.recursion_stubs:
-            resolver = self._get_resolver(request)
-            stub = self.recursion_stubs.pop(request)
-            resolver.saturate_stub(result, stub)
+            self._current_request = None
 
+        self.recursion_resolver.process_request_result(request, result)
         return result
 
     def provide_from_next(self) -> Any:
-        return self._provide_non_recursive(self._request_stack[-1], self.next_offset)
-
-    @property
-    def request_stack(self) -> Sequence[Request[Any]]:
-        return self._request_stack.copy()
-
-    def _get_resolver(self, request: Request) -> StubsRecursionResolver:
-        return self.recursion_resolving.dispatch(type(request))
+        if self._current_request is None:
+            raise ValueError
+        return self._provide_non_recursive(self._current_request, self.next_offset)
 
     def _provide_non_recursive(self, request: Request[T], search_offset: int) -> T:
         init_next_offset = self.next_offset
@@ -144,130 +94,7 @@ def _provide_non_recursive(self, request: Request[T], search_offset: int) -> T:
         )
 
     def _attach_request_context_note(self, exc: E, request: Request) -> E:
-        notes = self.error_representor.get_request_context_notes(request, self.request_stack)
+        notes = self.error_representor.get_request_context_notes(request)
         for note in notes:
             add_note(exc, note)
         return exc
-
-
-class RawRecipeSearcher(RecipeSearcher):
-    def __init__(self, recipe: Sequence[Provider]):
-        self.recipe = recipe
-
-    def search_candidates(self, search_offset: int, request: Request) -> Iterable[SearchResult]:
-        for i, provider in enumerate(
-            islice(self.recipe, search_offset, None),
-            start=search_offset
-        ):
-            yield provider.apply_provider, i + 1
-
-    def clear_cache(self):
-        pass
-
-    def get_max_offset(self) -> int:
-        return len(self.recipe)
-
-
-class Combiner(ABC):
-    @abstractmethod
-    def add_element(self, provider: Provider) -> bool:
-        ...
-
-    @abstractmethod
-    def combine_elements(self) -> Sequence[Provider]:
-        ...
-
-    @abstractmethod
-    def has_elements(self) -> bool:
-        ...
-
-
-class ExactOriginCombiner(Combiner):
-    def __init__(self) -> None:
-        self._combo: List[Tuple[ExactOriginRC, Provider]] = []
-        self._origins: Set[TypeHint] = set()
-
-    def add_element(self, provider: Provider) -> bool:
-        if not isinstance(provider, ProviderWithRC):
-            return False
-        request_checker = provider.get_request_checker()
-        if request_checker is None:
-            return False
-        if not isinstance(request_checker, ExactOriginRC):
-            return False
-        if request_checker.origin in self._origins:
-            return False
-
-        self._combo.append((request_checker, provider))
-        self._origins.add(request_checker.origin)
-        return True
-
-    def combine_elements(self) -> Sequence[Provider]:
-        if len(self._combo) == 1:
-            element = self._combo[0][1]
-            self._combo.clear()
-            self._origins.clear()
-            return [element]
-
-        merged_provider = ExactOriginMergedProvider(self._combo)
-        self._combo.clear()
-        self._origins.clear()
-        return [merged_provider]
-
-    def has_elements(self) -> bool:
-        return bool(self._combo)
-
-
-class IntrospectingRecipeSearcher(RecipeSearcher):
-    def __init__(self, recipe: Sequence[Provider]):
-        self._recipe = recipe
-        self._cls_to_recipe: Dict[Type[Request], Sequence[Provider]] = {}
-
-    def search_candidates(self, search_offset: int, request: Request) -> Iterable[SearchResult]:
-        request_cls = type(request)
-        try:
-            sub_recipe = self._cls_to_recipe[request_cls]
-        except KeyError:
-            sub_recipe = self._collect_candidates(request_cls, self._recipe)
-            self._cls_to_recipe[request_cls] = sub_recipe
-
-        for i, provider in enumerate(
-            islice(sub_recipe, search_offset, None),
-            start=search_offset
-        ):
-            yield provider.apply_provider, i + 1
-
-    def _create_combiner(self) -> Combiner:
-        return ExactOriginCombiner()
-
-    def _merge_providers(self, recipe: Sequence[Provider]) -> Sequence[Provider]:
-        combiner = self._create_combiner()
-
-        result: List[Provider] = []
-        for provider in recipe:
-            is_added = combiner.add_element(provider)
-            if not is_added:
-                if combiner.has_elements():
-                    result.extend(combiner.combine_elements())
-                result.append(provider)
-
-        if combiner.has_elements():
-            result.extend(combiner.combine_elements())
-        return result
-
-    def _collect_candidates(self, request_cls: Type[Request], recipe: Sequence[Provider]) -> Sequence[Provider]:
-        candidates = [
-            provider
-            for provider in recipe
-            if (
-                not isinstance(provider, RequestClassDeterminedProvider)
-                or provider.maybe_can_process_request_cls(request_cls)
-            )
-        ]
-        return self._merge_providers(candidates)
-
-    def clear_cache(self):
-        self._cls_to_recipe = {}
-
-    def get_max_offset(self) -> int:
-        return len(self._recipe)
diff --git a/src/adaptix/_internal/retort/operating_retort.py b/src/adaptix/_internal/retort/operating_retort.py
index 9d1954da..e6574953 100644
--- a/src/adaptix/_internal/retort/operating_retort.py
+++ b/src/adaptix/_internal/retort/operating_retort.py
@@ -1,13 +1,14 @@
 from abc import ABC
-from typing import Iterable, List, Mapping, Optional, Sequence, Type, TypeVar
+from typing import Any, Dict, Iterable, List, Mapping, Optional, Type
 
+from ..common import TypeHint, VarTuple
+from ..conversion.request_cls import CoercerRequest, LinkingRequest
 from ..morphing.request_cls import DumperRequest, LoaderRequest
 from ..provider.essential import AggregateCannotProvide, CannotProvide, Mediator, Provider, Request
-from ..provider.request_cls import FieldLoc, LocatedRequest
-from ..provider.utils import find_field_request
+from ..provider.request_cls import FieldLoc, LocatedRequest, LocMap, LocStack, TypeHintLoc, find_owner_with_field
 from ..utils import copy_exception_dunders, with_module
 from .base_retort import BaseRetort
-from .mediator import ErrorRepresentor, RecursionResolving, StubsRecursionResolver
+from .mediator import ErrorRepresentor, RecursionResolver, T
 
 
 class FuncWrapper:
@@ -20,12 +21,26 @@ def set_func(self, func):
         self.__call__ = func.__call__
 
 
-class FuncRecursionResolver(StubsRecursionResolver):
-    def create_stub(self, request):
-        return FuncWrapper()
+class MorphingRecursionResolver(RecursionResolver):
+    REQUEST_CLASSES: VarTuple[Type[LocatedRequest]] = (LoaderRequest, DumperRequest)
 
-    def saturate_stub(self, actual, stub) -> None:
-        stub.set_func(actual)
+    def __init__(self) -> None:
+        self._loc_map_to_stub: Dict[LocMap, 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:
+            return None
+
+        stub = FuncWrapper()
+        self._loc_map_to_stub[request.last_map] = 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)
 
 
 @with_module('adaptix')
@@ -34,57 +49,101 @@ def __init__(self, message: str):
         self.message = message
 
 
-T = TypeVar('T')
-
-
 class BuiltinErrorRepresentor(ErrorRepresentor):
-    _NO_PROVIDER_DESCRIPTION_MAP: Mapping[Type[Request], str] = {
+    _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",
     }
 
-    def get_no_provider_description(self, request: Request) -> str:
+    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:
-            return self._NO_PROVIDER_DESCRIPTION_MAP[type(request)]
-        except KeyError:
-            return f"There is no provider that can process {request}"
+            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
+        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 coercer for linking `{src_owner}.{src_field} -> {dst_owner}.{dst_field}`"
+
+    def _get_type_desc(self, tp: TypeHint) -> str:
+        try:
+            return tp.__qualname__
+        except AttributeError:
+            return str(tp)
+
+    def get_no_provider_description(self, request: Request) -> str:
+        request_cls = type(request)
+        if request_cls in self._NO_PROVIDER_DESCRIPTION_METHOD:
+            return getattr(self, self._NO_PROVIDER_DESCRIPTION_METHOD[request_cls])(request)
+        if request_cls in self._NO_PROVIDER_DESCRIPTION_CONST:
+            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_request_context_notes(self, request: Request, request_stack: Sequence[Request]) -> Iterable[str]:
-        if isinstance(request, LocatedRequest):
-            location_desc = ', '.join(
-                f'{key}={value!r}'
-                for key, value in sorted(
-                    (
-                        (key, value)
-                        for loc in request.loc_map.values()
-                        for key, value in vars(loc).items()
-                    ),
-                    key=lambda item: self._LOC_KEYS_ORDER.get(item[0], 1000),
-                )
+    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'Location: {location_desc}'
+        )
+        if location_desc:
+            yield f'{loc_desc}: {location_desc}'
 
-            try:
-                field_request = find_field_request(request_stack)
-            except ValueError:
-                pass
-            else:
-                field_loc = field_request.loc_map[FieldLoc]
-                yield f'Exception was raised while processing field {field_loc.field_id!r} of {field_loc.owner_type}'
+    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,
+            )
 
 
 class OperatingRetort(BaseRetort, Provider, ABC):
     """A retort that can operate as Retort but have no predefined providers and no high-level user interface"""
 
     def apply_provider(self, mediator: Mediator, request: Request[T]) -> T:
-        return self._provide_from_recipe(request, mediator.request_stack[:-1])
+        return self._provide_from_recipe(request)
 
     def _facade_provide(self, request: Request[T], *, error_message: str) -> T:
         try:
-            return self._provide_from_recipe(request, [])
+            return self._provide_from_recipe(request)
         except CannotProvide as e:
             cause = self._get_exception_cause(e)
             raise NoSuitableProvider(error_message) from cause
@@ -110,13 +169,8 @@ def _extract_demonstrative_exc(self, exc: AggregateCannotProvide) -> Optional[Ca
         copy_exception_dunders(source=exc, target=new_exc)
         return new_exc
 
-    def _get_recursion_resolving(self) -> RecursionResolving:
-        return RecursionResolving(
-            {
-                LoaderRequest: FuncRecursionResolver(),
-                DumperRequest: FuncRecursionResolver(),
-            }
-        )
+    def _create_recursion_resolver(self) -> RecursionResolver:
+        return MorphingRecursionResolver()
 
     def _get_error_representor(self) -> ErrorRepresentor:
         return BuiltinErrorRepresentor()
diff --git a/src/adaptix/_internal/retort/routing.py b/src/adaptix/_internal/retort/routing.py
new file mode 100644
index 00000000..ecc3a1a5
--- /dev/null
+++ b/src/adaptix/_internal/retort/routing.py
@@ -0,0 +1,177 @@
+from abc import ABC, abstractmethod
+from itertools import islice
+from typing import Callable, Dict, Iterable, List, Sequence, Set, Tuple, Type, TypeVar
+
+from ..common import TypeHint
+from ..provider.essential import CannotProvide, Mediator, Provider, Request
+from ..provider.loc_stack_filtering import ExactOriginLSC
+from ..provider.provider_wrapper import ProviderWithLSC, RequestClassDeterminedProvider
+from ..provider.request_cls import LocatedRequest, TypeHintLoc, try_normalize_type
+
+T = TypeVar('T')
+ProvideCallable = Callable[[Mediator, Request[T]], T]
+SearchResult = Tuple[ProvideCallable[T], int]
+
+
+class RecipeSearcher(ABC):
+    """An object that implements iterating over recipe list.
+
+    An offset of each element must belong to [0; max_offset)
+    """
+
+    @abstractmethod
+    def search_candidates(self, search_offset: int, request: Request[T]) -> Iterable[SearchResult[T]]:
+        ...
+
+    @abstractmethod
+    def get_max_offset(self) -> int:
+        ...
+
+    @abstractmethod
+    def clear_cache(self):
+        ...
+
+
+class RawRecipeSearcher(RecipeSearcher):
+    def __init__(self, recipe: Sequence[Provider]):
+        self.recipe = recipe
+
+    def search_candidates(self, search_offset: int, request: Request) -> Iterable[SearchResult]:
+        for i, provider in enumerate(
+            islice(self.recipe, search_offset, None),
+            start=search_offset
+        ):
+            yield provider.apply_provider, i + 1
+
+    def clear_cache(self):
+        pass
+
+    def get_max_offset(self) -> int:
+        return len(self.recipe)
+
+
+class Combiner(ABC):
+    @abstractmethod
+    def add_element(self, provider: Provider) -> bool:
+        ...
+
+    @abstractmethod
+    def combine_elements(self) -> Sequence[Provider]:
+        ...
+
+    @abstractmethod
+    def has_elements(self) -> bool:
+        ...
+
+
+class ExactOriginCombiner(Combiner):
+    def __init__(self) -> None:
+        self._combo: List[Tuple[ExactOriginLSC, Provider]] = []
+        self._origins: Set[TypeHint] = set()
+
+    def add_element(self, provider: Provider) -> bool:
+        if not isinstance(provider, ProviderWithLSC):
+            return False
+        loc_stack_checker = provider.get_loc_stack_checker()
+        if not isinstance(loc_stack_checker, ExactOriginLSC):
+            return False
+        if loc_stack_checker.origin in self._origins:
+            return False
+
+        self._combo.append((loc_stack_checker, provider))
+        self._origins.add(loc_stack_checker.origin)
+        return True
+
+    def combine_elements(self) -> Sequence[Provider]:
+        if len(self._combo) == 1:
+            element = self._combo[0][1]
+            self._combo.clear()
+            self._origins.clear()
+            return [element]
+
+        merged_provider = ExactOriginMergedProvider(self._combo)
+        self._combo.clear()
+        self._origins.clear()
+        return [merged_provider]
+
+    def has_elements(self) -> bool:
+        return bool(self._combo)
+
+
+class ExactOriginMergedProvider(Provider):
+    def __init__(self, origins_to_providers: Sequence[Tuple[ExactOriginLSC, Provider]]):
+        self.origin_to_provider = {
+            loc_stack_checker.origin: provider
+            for loc_stack_checker, provider in reversed(origins_to_providers)
+        }
+
+    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(
+            TypeHintLoc,
+            lambda: CannotProvide(f'Request location must be instance of {TypeHintLoc}')
+        )
+        norm = try_normalize_type(loc.type)
+        try:
+            provider = self.origin_to_provider[norm.origin]
+        except KeyError:
+            raise CannotProvide from None
+
+        return provider.apply_provider(mediator, request)
+
+
+class IntrospectingRecipeSearcher(RecipeSearcher):
+    def __init__(self, recipe: Sequence[Provider]):
+        self._recipe = recipe
+        self._cls_to_recipe: Dict[Type[Request], Sequence[Provider]] = {}
+
+    def search_candidates(self, search_offset: int, request: Request) -> Iterable[SearchResult]:
+        request_cls = type(request)
+        try:
+            sub_recipe = self._cls_to_recipe[request_cls]
+        except KeyError:
+            sub_recipe = self._collect_candidates(request_cls, self._recipe)
+            self._cls_to_recipe[request_cls] = sub_recipe
+
+        for i, provider in enumerate(
+            islice(sub_recipe, search_offset, None),
+            start=search_offset
+        ):
+            yield provider.apply_provider, i + 1
+
+    def _create_combiner(self) -> Combiner:
+        return ExactOriginCombiner()
+
+    def _merge_providers(self, recipe: Sequence[Provider]) -> Sequence[Provider]:
+        combiner = self._create_combiner()
+
+        result: List[Provider] = []
+        for provider in recipe:
+            is_added = combiner.add_element(provider)
+            if not is_added:
+                if combiner.has_elements():
+                    result.extend(combiner.combine_elements())
+                result.append(provider)
+
+        if combiner.has_elements():
+            result.extend(combiner.combine_elements())
+        return result
+
+    def _collect_candidates(self, request_cls: Type[Request], recipe: Sequence[Provider]) -> Sequence[Provider]:
+        candidates = [
+            provider
+            for provider in recipe
+            if (
+                not isinstance(provider, RequestClassDeterminedProvider)
+                or provider.maybe_can_process_request_cls(request_cls)
+            )
+        ]
+        return self._merge_providers(candidates)
+
+    def clear_cache(self):
+        self._cls_to_recipe = {}
+
+    def get_max_offset(self) -> int:
+        return len(self._recipe)
diff --git a/src/adaptix/_internal/type_tools/norm_utils.py b/src/adaptix/_internal/type_tools/norm_utils.py
index 8c8b2ea6..a23e1b6e 100644
--- a/src/adaptix/_internal/type_tools/norm_utils.py
+++ b/src/adaptix/_internal/type_tools/norm_utils.py
@@ -23,12 +23,12 @@ def strip_tags(norm: BaseNormType) -> BaseNormType:
     return norm
 
 
-T = TypeVar('T')
+N = TypeVar('N', bound=BaseNormType)
 
 
-def strip_annotated(value: T) -> T:
+def strip_annotated(value: N) -> N:
     if HAS_ANNOTATED and isinstance(value, BaseNormType) and value.origin == typing.Annotated:
-        return strip_annotated(value)  # type: ignore[return-value]
+        return strip_annotated(value)
     return value
 
 
diff --git a/src/adaptix/_internal/utils.py b/src/adaptix/_internal/utils.py
index 200a7b88..6fb9c378 100644
--- a/src/adaptix/_internal/utils.py
+++ b/src/adaptix/_internal/utils.py
@@ -1,4 +1,6 @@
 import itertools
+import sys
+import warnings
 from abc import ABC, abstractmethod
 from contextlib import contextmanager
 from copy import copy
@@ -204,3 +206,19 @@ def decorator(cls):
         return cls
 
     return decorator
+
+
+def create_deprecated_alias_getter(module_name, old_name_to_new_name):
+    def __getattr__(name):
+        if name not in old_name_to_new_name:
+            raise AttributeError(f"module {module_name!r} has no attribute {name!r}")
+
+        new_name = old_name_to_new_name[name]
+        warnings.warn(
+            f"Name {name!r} is deprecated, use {new_name!r} instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return getattr(sys.modules[module_name], new_name)
+
+    return __getattr__
diff --git a/src/adaptix/conversion/__init__.py b/src/adaptix/conversion/__init__.py
new file mode 100644
index 00000000..8bb31370
--- /dev/null
+++ b/src/adaptix/conversion/__init__.py
@@ -0,0 +1,21 @@
+from adaptix._internal.conversion.facade.func import convert, get_converter, impl_converter
+from adaptix._internal.conversion.facade.provider import (
+    allow_unlinked_optional,
+    coercer,
+    forbid_unlinked_optional,
+    link,
+)
+from adaptix._internal.conversion.facade.retort import AdornedConversionRetort, ConversionRetort, FilledConversionRetort
+
+__all__ = (
+    'convert',
+    'get_converter',
+    'impl_converter',
+    'link',
+    'coercer',
+    'allow_unlinked_optional',
+    'forbid_unlinked_optional',
+    'AdornedConversionRetort',
+    'FilledConversionRetort',
+    'ConversionRetort',
+)
diff --git a/src/adaptix/load_error.py b/src/adaptix/load_error.py
index c16b7348..af1907bb 100644
--- a/src/adaptix/load_error.py
+++ b/src/adaptix/load_error.py
@@ -1,35 +1,57 @@
 from adaptix._internal.morphing.load_error import (
     AggregateLoadError,
-    BadVariantError,
-    DatetimeFormatMismatch,
+    BadVariantLoadError,
+    DuplicatedValuesLoadError,
     ExcludedTypeLoadError,
-    ExtraFieldsError,
-    ExtraItemsError,
+    ExtraFieldsLoadError,
+    ExtraItemsLoadError,
+    FormatMismatchLoadError,
     LoadError,
     LoadExceptionGroup,
-    MsgError,
-    NoRequiredFieldsError,
-    NoRequiredItemsError,
+    MsgLoadError,
+    MultipleBadVariantLoadError,
+    NoRequiredFieldsLoadError,
+    NoRequiredItemsLoadError,
+    OutOfRangeLoadError,
     TypeLoadError,
     UnionLoadError,
-    ValidationError,
+    ValidationLoadError,
     ValueLoadError,
 )
+from adaptix._internal.utils import create_deprecated_alias_getter
 
 __all__ = (
     'LoadError',
     'LoadExceptionGroup',
     'AggregateLoadError',
     'UnionLoadError',
-    'MsgError',
-    'ExtraFieldsError',
-    'ExtraItemsError',
-    'NoRequiredFieldsError',
-    'NoRequiredItemsError',
+    'MsgLoadError',
+    'ExtraFieldsLoadError',
+    'ExtraItemsLoadError',
+    'NoRequiredFieldsLoadError',
+    'NoRequiredItemsLoadError',
     'TypeLoadError',
     'ExcludedTypeLoadError',
     'ValueLoadError',
-    'ValidationError',
-    'BadVariantError',
-    'DatetimeFormatMismatch',
+    'ValidationLoadError',
+    'BadVariantLoadError',
+    'FormatMismatchLoadError',
+    'DuplicatedValuesLoadError',
+    'OutOfRangeLoadError',
+    'MultipleBadVariantLoadError'
+)
+
+
+__getattr__ = create_deprecated_alias_getter(
+    __name__,
+    {
+        'MsgError': 'MsgLoadError',
+        'ExtraFieldsError': 'ExtraFieldsLoadError',
+        'ExtraItemsError': 'ExtraItemsLoadError',
+        'NoRequiredFieldsError': 'NoRequiredFieldsLoadError',
+        'NoRequiredItemsError': 'NoRequiredItemsLoadError',
+        'ValidationError': 'ValidationLoadError',
+        'BadVariantError': 'BadVariantLoadError',
+        'DatetimeFormatMismatch': 'FormatMismatchLoadError',
+    },
 )
diff --git a/src/adaptix/provider/__init__.py b/src/adaptix/provider/__init__.py
index bf9f473a..39cb346f 100644
--- a/src/adaptix/provider/__init__.py
+++ b/src/adaptix/provider/__init__.py
@@ -1,6 +1,6 @@
 from adaptix._internal.provider.essential import AggregateCannotProvide, CannotProvide, Mediator, Provider, Request
+from adaptix._internal.provider.loc_stack_filtering import LocStackPattern, P, create_loc_stack_checker
 from adaptix._internal.provider.provider_wrapper import Chain
-from adaptix._internal.provider.request_filtering import P, RequestPattern, create_request_checker
 
 __all__ = (
     'CannotProvide',
@@ -9,7 +9,7 @@
     'Provider',
     'Request',
     'Chain',
-    'RequestPattern',
+    'LocStackPattern',
     'P',
-    'create_request_checker',
+    'create_loc_stack_checker',
 )


Test patch:
diff --git a/examples/api_division/test_example.py b/examples/api_division/test_example.py
index 8b3ed6bc..6754a3ad 100644
--- a/examples/api_division/test_example.py
+++ b/examples/api_division/test_example.py
@@ -7,11 +7,11 @@
 
 from adaptix.load_error import (
     AggregateLoadError,
-    BadVariantError,
-    ExtraFieldsError,
+    BadVariantLoadError,
+    ExtraFieldsLoadError,
     TypeLoadError,
     UnionLoadError,
-    ValidationError,
+    ValidationLoadError,
     ValueLoadError,
 )
 
@@ -72,7 +72,7 @@ def test_outer_loading_no_rec_items():
             f'while loading model {Receipt}',
             [
                 with_trail(
-                    ValidationError('At least one item must be presented', []),
+                    ValidationLoadError('At least one item must be presented', []),
                     ['items'],
                 )
             ]
@@ -119,7 +119,7 @@ def test_outer_loading_bad_phone():
                                                     f'while loading model {NotifyEmail}',
                                                     [
                                                         with_trail(
-                                                            BadVariantError({'email'}, 'phone'),
+                                                            BadVariantLoadError({'email'}, 'phone'),
                                                             ['type']
                                                         )
                                                     ]
@@ -155,7 +155,7 @@ def test_outer_loading_bad_receipt_type():
     raises_exc(
         AggregateLoadError(
             f'while loading model {Receipt}',
-            [with_trail(BadVariantError(['INCOME', 'INCOME_REFUND'], 'BAD_TYPE'), ['type'])]
+            [with_trail(BadVariantLoadError(['INCOME', 'INCOME_REFUND'], 'BAD_TYPE'), ['type'])]
         ),
         lambda: outer_receipt_loader(bad_receipt_type_data),
     )
@@ -165,7 +165,7 @@ def test_outer_loading_with_version_tag():
     with_version_data = change(outer_sample_data, ["version"], 1)
 
     raises_exc(
-        ExtraFieldsError(['version'], with_version_data),
+        ExtraFieldsLoadError(['version'], with_version_data),
         lambda: outer_receipt_loader(with_version_data),
     )
 
@@ -186,7 +186,7 @@ def test_outer_loading_bad_item_quantity():
                                     f'while loading model {RecItem!r}',
                                     [
                                         with_trail(
-                                            ValidationError('Value must be > 0', 0),
+                                            ValidationLoadError('Value must be > 0', 0),
                                             ['quantity'],
                                         ),
                                     ]
@@ -219,7 +219,7 @@ def test_outer_loading_bad_item_price():
                                     f'while loading model {RecItem}',
                                     [
                                         with_trail(
-                                            ValidationError('Value must be >= 0', rubles(-10)),
+                                            ValidationLoadError('Value must be >= 0', rubles(-10)),
                                             ['price'],
                                         )
                                     ]
diff --git a/requirements/raw/test_extra_new.txt b/requirements/raw/test_extra_new.txt
index 4956f5a3..270dca8c 100644
--- a/requirements/raw/test_extra_new.txt
+++ b/requirements/raw/test_extra_new.txt
@@ -1,2 +1,3 @@
 -r test_extra_none.txt
-attrs==23.1.0
+attrs==23.2.0
+sqlalchemy==2.0.27
diff --git a/requirements/raw/test_extra_none.txt b/requirements/raw/test_extra_none.txt
index ca7fbb4e..767f0019 100644
--- a/requirements/raw/test_extra_none.txt
+++ b/requirements/raw/test_extra_none.txt
@@ -2,7 +2,7 @@ pytest==7.4.2
 -e ./tests/tests_helpers
 
 phonenumberslite==8.13.26
-sqlalchemy==2.0.23
+sqlalchemy>=2.0.0
 
 dirty-equals==0.7.1.post0
 
diff --git a/requirements/raw/test_extra_old.txt b/requirements/raw/test_extra_old.txt
index 1029b111..b314752c 100644
--- a/requirements/raw/test_extra_old.txt
+++ b/requirements/raw/test_extra_old.txt
@@ -1,2 +1,3 @@
 -r test_extra_none.txt
 attrs==21.3.0
+sqlalchemy==2.0.0
diff --git a/requirements/test_extra_new.txt b/requirements/test_extra_new.txt
index eeb55d5f..98d9e0d4 100644
--- a/requirements/test_extra_new.txt
+++ b/requirements/test_extra_new.txt
@@ -6,7 +6,7 @@
 #
 -e ./tests/tests_helpers
     # via -r requirements/raw/test_extra_none.txt
-attrs==23.1.0
+attrs==23.2.0
     # via -r requirements/raw/test_extra_new.txt
 dirty-equals==0.7.1.post0
     # via -r requirements/raw/test_extra_none.txt
@@ -26,8 +26,10 @@ pytest==7.4.2
     # via -r requirements/raw/test_extra_none.txt
 pytz==2023.3.post1
     # via dirty-equals
-sqlalchemy==2.0.23
-    # via -r requirements/raw/test_extra_none.txt
+sqlalchemy==2.0.27
+    # via
+    #   -r requirements/raw/test_extra_new.txt
+    #   -r requirements/raw/test_extra_none.txt
 typing-extensions==4.9.0
     # via
     #   -r requirements/raw/test_extra_none.txt
diff --git a/requirements/test_extra_old.txt b/requirements/test_extra_old.txt
index ef21d7a6..fdb01d13 100644
--- a/requirements/test_extra_old.txt
+++ b/requirements/test_extra_old.txt
@@ -26,8 +26,10 @@ pytest==7.4.2
     # via -r requirements/raw/test_extra_none.txt
 pytz==2023.3.post1
     # via dirty-equals
-sqlalchemy==2.0.23
-    # via -r requirements/raw/test_extra_none.txt
+sqlalchemy==2.0.0
+    # via
+    #   -r requirements/raw/test_extra_none.txt
+    #   -r requirements/raw/test_extra_old.txt
 typing-extensions==4.9.0
     # via
     #   -r requirements/raw/test_extra_none.txt
diff --git a/tests/conftest.py b/tests/conftest.py
index f75ede08..044299d2 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,5 +1,5 @@
 import pytest
-from tests_helpers import ByTrailSelector
+from tests_helpers import ByTrailSelector, ModelSpecSchema, parametrize_model_spec
 
 from adaptix import DebugTrail
 from adaptix._internal.feature_requirement import HAS_PY_312
@@ -20,4 +20,25 @@ def trail_select(debug_trail):
     return ByTrailSelector(debug_trail)
 
 
+@pytest.fixture
+def model_spec() -> ModelSpecSchema:
+    ...
+
+
+@pytest.fixture
+def src_model_spec() -> ModelSpecSchema:
+    ...
+
+
+@pytest.fixture
+def dst_model_spec() -> ModelSpecSchema:
+    ...
+
+
+def pytest_generate_tests(metafunc):
+    parametrize_model_spec('model_spec', metafunc)
+    parametrize_model_spec('src_model_spec', metafunc)
+    parametrize_model_spec('dst_model_spec', metafunc)
+
+
 collect_ignore_glob = [] if HAS_PY_312 else ['*_312.py']
diff --git a/tests/integration/conversion/__init__.py b/tests/integration/conversion/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/integration/conversion/conftest.py b/tests/integration/conversion/conftest.py
new file mode 100644
index 00000000..0a1cd280
--- /dev/null
+++ b/tests/integration/conversion/conftest.py
@@ -0,0 +1,8 @@
+import pytest
+
+from .local_helpers import FactoryWay
+
+
+@pytest.fixture(params=FactoryWay.params())
+def factory_way(request):
+    return request.param
diff --git a/tests/integration/conversion/local_helpers.py b/tests/integration/conversion/local_helpers.py
new file mode 100644
index 00000000..9d4d7563
--- /dev/null
+++ b/tests/integration/conversion/local_helpers.py
@@ -0,0 +1,12 @@
+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
new file mode 100644
index 00000000..c86f8599
--- /dev/null
+++ b/tests/integration/conversion/test_basics.py
@@ -0,0 +1,280 @@
+from typing import Any, Generic, TypeVar
+
+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
+
+
+@exclude_model_spec(ModelSpec.TYPED_DICT)
+def test_convert(model_spec):
+    from adaptix.conversion import convert
+
+    @model_spec.decorator
+    class SourceModel(*model_spec.bases):
+        field1: Any
+        field2: Any
+
+    @model_spec.decorator
+    class DestModel(*model_spec.bases):
+        field1: Any
+        field2: Any
+
+    assert convert(SourceModel(field1=1, field2=2), DestModel) == DestModel(field1=1, field2=2)
+
+
+def test_copy(model_spec, factory_way):
+    @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)
+
+    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):
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: Any
+        field2: Any
+
+    @dst_model_spec.decorator
+    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)
+
+    assert convert(SourceModel(field1=1, field2=2)) == DestModel(field1=1, field2=2)
+
+
+def test_replace(src_model_spec, dst_model_spec):
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: Any
+        field2: Any
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_model_spec.bases):
+        field1: Any
+        field2: Any
+
+    @impl_converter
+    def convert(a: SourceModel, field2: Any) -> DestModel:
+        ...
+
+    assert convert(SourceModel(field1=1, field2=2), field2=3) == DestModel(field1=1, field2=3)
+
+
+def test_downcast(src_model_spec, dst_model_spec, factory_way):
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: Any
+        field2: Any
+        field3: Any
+
+    @dst_model_spec.decorator
+    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)
+
+    assert convert(SourceModel(field1=1, field2=2, field3=3)) == DestModel(field1=1, field2=2)
+
+
+def test_upcast(src_model_spec, dst_model_spec):
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: Any
+        field2: Any
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_model_spec.bases):
+        field1: Any
+        field2: Any
+        field3: Any
+
+    @impl_converter
+    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):
+    @src_model_spec.decorator
+    class SourceModelNested(*src_model_spec.bases):
+        field1: Any
+
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: Any
+        field2: Any
+        nested: SourceModelNested
+
+    @dst_model_spec.decorator
+    class DestModelNested(*dst_model_spec.bases):
+        field1: Any
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_model_spec.bases):
+        field1: Any
+        field2: Any
+        nested: DestModelNested
+
+    if factory_way == FactoryWay.IMPL_CONVERTER:
+        @impl_converter
+        def convert(a: SourceModel) -> DestModel:
+            ...
+    else:
+        convert = get_converter(SourceModel, DestModel)
+
+    assert convert(
+        SourceModel(
+            field1=1,
+            field2=2,
+            nested=SourceModelNested(field1=3),
+        )
+    ) == DestModel(
+        field1=1,
+        field2=2,
+        nested=DestModelNested(field1=3),
+    )
+
+
+def test_same_nested(src_model_spec, dst_model_spec, factory_way):
+    @src_model_spec.decorator
+    class SourceModelNested(*src_model_spec.bases):
+        field1: Any
+
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: Any
+        field2: Any
+        nested1: SourceModelNested
+        nested2: SourceModelNested
+
+    @dst_model_spec.decorator
+    class DestModelNested(*dst_model_spec.bases):
+        field1: Any
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_model_spec.bases):
+        field1: Any
+        field2: Any
+        nested1: DestModelNested
+        nested2: DestModelNested
+
+    if factory_way == FactoryWay.IMPL_CONVERTER:
+        @impl_converter
+        def convert(a: SourceModel) -> DestModel:
+            ...
+    else:
+        convert = get_converter(SourceModel, DestModel)
+
+    assert convert(
+        SourceModel(
+            field1=1,
+            field2=2,
+            nested1=SourceModelNested(field1=3),
+            nested2=SourceModelNested(field1=4),
+        )
+    ) == DestModel(
+        field1=1,
+        field2=2,
+        nested1=DestModelNested(field1=3),
+        nested2=DestModelNested(field1=4),
+    )
+
+
+@requires(HAS_ANNOTATED)
+def test_annotated_ignoring(src_model_spec, dst_model_spec, factory_way):
+    from typing import Annotated
+
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: Any
+        field2: Annotated[Any, 'meta']
+
+    @dst_model_spec.decorator
+    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)
+
+    assert convert(SourceModel(field1=1, field2=2)) == DestModel(field1=1, field2=2)
+
+
+T = TypeVar('T')
+
+
+@only_generic_models
+def test_same_parametrized_generics(src_model_spec, dst_model_spec, factory_way):
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases, Generic[T]):
+        field1: Any
+        field2: T
+
+    @dst_model_spec.decorator
+    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])
+
+    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):
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases, Generic[T]):
+        field1: Any
+        field2: T
+
+    @dst_model_spec.decorator
+    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)
+
+    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
new file mode 100644
index 00000000..e78a996a
--- /dev/null
+++ b/tests/integration/conversion/test_coercer.py
@@ -0,0 +1,116 @@
+from typing import Any
+
+from adaptix.conversion import coercer, get_converter, impl_converter
+
+from .local_helpers import FactoryWay
+
+
+def test_simple(src_model_spec, dst_model_spec, factory_way):
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: str
+        field2: str
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_model_spec.bases):
+        field1: int
+        field2: int
+
+    if factory_way == FactoryWay.IMPL_CONVERTER:
+        @impl_converter(recipe=[coercer(str, int, func=int)])
+        def convert(a: SourceModel) -> DestModel:
+            ...
+    else:
+        convert = get_converter(SourceModel, DestModel, recipe=[coercer(str, int, func=int)])
+
+    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):
+        field1: Any
+
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: Any
+        field2: Any
+        nested: SourceModelNested
+
+    @dst_model_spec.decorator
+    class DestModelNested(*dst_model_spec.bases):
+        field1: Any
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_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)],
+        )
+
+    assert convert(
+        SourceModel(
+            field1=1,
+            field2=2,
+            nested=SourceModelNested(field1=3),
+        )
+    ) == DestModel(
+        field1=1,
+        field2=2,
+        nested=DestModelNested(field1=13),
+    )
+
+
+def test_any_dest(src_model_spec, dst_model_spec, factory_way):
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: str
+        field2: str
+
+    @dst_model_spec.decorator
+    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)
+
+    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):
+        field1: bool
+        field2: bool
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_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)
+
+    assert convert(SourceModel(field1=False, field2=True)) == DestModel(field1=False, field2=True)
diff --git a/tests/integration/conversion/test_linking.py b/tests/integration/conversion/test_linking.py
new file mode 100644
index 00000000..f216bc8f
--- /dev/null
+++ b/tests/integration/conversion/test_linking.py
@@ -0,0 +1,148 @@
+from typing import Any
+
+from adaptix.conversion import get_converter, 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):
+        field1: Any
+        field2_src: Any
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_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')])
+
+    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):
+        field1: Any
+        field2: Any
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_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')])
+
+    assert convert(SourceModel(field1=1, field2=2)) == DestModel(field1=2, field2=1)
+
+
+def test_upcast(src_model_spec, dst_model_spec):
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: Any
+        field2: Any
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_model_spec.bases):
+        field1: Any
+        field2: Any
+        field3: Any
+
+    @impl_converter(recipe=[link('field4', 'field3')])
+    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):
+        field1_src: Any
+
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: Any
+        field2: Any
+        nested: SourceModelNested
+
+    @dst_model_spec.decorator
+    class DestModelNested(*dst_model_spec.bases):
+        field1_dst: Any
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_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')])
+
+    assert convert(
+        SourceModel(
+            field1=1,
+            field2=2,
+            nested=SourceModelNested(field1_src=3),
+        )
+    ) == DestModel(
+        field1=1,
+        field2=2,
+        nested=DestModelNested(field1_dst=3),
+    )
+
+
+def test_nested_several(src_model_spec, dst_model_spec, factory_way):
+    @src_model_spec.decorator
+    class SourceModelNested(*src_model_spec.bases):
+        field1_src: Any
+
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1_src: Any
+        field2: Any
+        nested: SourceModelNested
+
+    @dst_model_spec.decorator
+    class DestModelNested(*dst_model_spec.bases):
+        field1_dst: Any
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_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')])
+
+    assert convert(
+        SourceModel(
+            field1_src=1,
+            field2=2,
+            nested=SourceModelNested(field1_src=3),
+        )
+    ) == DestModel(
+        field1_dst=1,
+        field2=2,
+        nested=DestModelNested(field1_dst=3),
+    )
diff --git a/tests/integration/conversion/test_policy.py b/tests/integration/conversion/test_policy.py
new file mode 100644
index 00000000..7fc1dc4c
--- /dev/null
+++ b/tests/integration/conversion/test_policy.py
@@ -0,0 +1,32 @@
+from tests_helpers import ModelSpec, exclude_model_spec
+
+from adaptix.conversion import allow_unlinked_optional, get_converter, impl_converter
+
+from .local_helpers import FactoryWay
+
+
+@exclude_model_spec(ModelSpec.TYPED_DICT)
+def test_unbound_optional(src_model_spec, dst_model_spec, factory_way):
+    @src_model_spec.decorator
+    class SourceModel(*src_model_spec.bases):
+        field1: str
+        field2: str
+
+    @dst_model_spec.decorator
+    class DestModel(*dst_model_spec.bases):
+        field1: str
+        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')])
+
+    if dst_model_spec.kind == ModelSpec.SQLALCHEMY:
+        # sqlalchemy set default after flush
+        assert convert(SourceModel(field1='1', field2='2')) == DestModel(field1='1', field2='2')
+    else:
+        assert convert(SourceModel(field1='1', field2='2')) == DestModel(field1='1', field2='2', wild='')
diff --git a/tests/integration/morphing/__init__.py b/tests/integration/morphing/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/integration/conftest.py b/tests/integration/morphing/conftest.py
similarity index 100%
rename from tests/integration/conftest.py
rename to tests/integration/morphing/conftest.py
diff --git a/tests/integration/morphing/real_api/__init__.py b/tests/integration/morphing/real_api/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/integration/real_api/open_library_search.zip b/tests/integration/morphing/real_api/open_library_search.zip
similarity index 100%
rename from tests/integration/real_api/open_library_search.zip
rename to tests/integration/morphing/real_api/open_library_search.zip
diff --git a/tests/integration/real_api/test_open_library_search.py b/tests/integration/morphing/real_api/test_open_library_search.py
similarity index 100%
rename from tests/integration/real_api/test_open_library_search.py
rename to tests/integration/morphing/real_api/test_open_library_search.py
diff --git a/tests/integration/test_attrs.py b/tests/integration/morphing/test_attrs.py
similarity index 100%
rename from tests/integration/test_attrs.py
rename to tests/integration/morphing/test_attrs.py
diff --git a/tests/integration/test_basics.py b/tests/integration/morphing/test_basics.py
similarity index 100%
rename from tests/integration/test_basics.py
rename to tests/integration/morphing/test_basics.py
diff --git a/tests/integration/test_empty.py b/tests/integration/morphing/test_empty.py
similarity index 100%
rename from tests/integration/test_empty.py
rename to tests/integration/morphing/test_empty.py
diff --git a/tests/integration/test_examples.py b/tests/integration/morphing/test_examples.py
similarity index 100%
rename from tests/integration/test_examples.py
rename to tests/integration/morphing/test_examples.py
diff --git a/tests/integration/test_generic_model_312.py b/tests/integration/morphing/test_generic_model_312.py
similarity index 100%
rename from tests/integration/test_generic_model_312.py
rename to tests/integration/morphing/test_generic_model_312.py
diff --git a/tests/integration/test_kline_to_list.py b/tests/integration/morphing/test_kline_to_list.py
similarity index 100%
rename from tests/integration/test_kline_to_list.py
rename to tests/integration/morphing/test_kline_to_list.py
diff --git a/tests/integration/test_recursive_type.py b/tests/integration/morphing/test_recursive_type.py
similarity index 100%
rename from tests/integration/test_recursive_type.py
rename to tests/integration/morphing/test_recursive_type.py
diff --git a/tests/integration/test_rest_api_filtering.py b/tests/integration/morphing/test_rest_api_filtering.py
similarity index 100%
rename from tests/integration/test_rest_api_filtering.py
rename to tests/integration/morphing/test_rest_api_filtering.py
diff --git a/tests/integration/morphing/test_sqlalchemy.py b/tests/integration/morphing/test_sqlalchemy.py
new file mode 100644
index 00000000..83b107c7
--- /dev/null
+++ b/tests/integration/morphing/test_sqlalchemy.py
@@ -0,0 +1,89 @@
+from typing import Optional
+
+from sqlalchemy import ForeignKey
+from sqlalchemy.orm import Mapped, mapped_column, registry, relationship
+from tests_helpers import sqlalchemy_equals
+
+from adaptix import Retort
+
+
+def test_simple(accum):
+    mapper_registry = registry()
+
+    @mapper_registry.mapped
+    class Book:
+        __tablename__ = 'books'
+
+        id: Mapped[int] = mapped_column(primary_key=True)
+        title: Mapped[str]
+        price: Mapped[int]
+
+        __eq__ = sqlalchemy_equals
+
+    retort = Retort(recipe=[accum])
+
+    loader = retort.get_loader(Book)
+    assert loader({'id': 1, 'title': 'abc', 'price': 100}) == Book(id=1, title='abc', price=100)
+
+    dumper = retort.get_dumper(Book)
+    assert dumper(Book(id=1, title='abc', price=100)) == {'id': 1, 'title': 'abc', 'price': 100}
+
+
+def test_relationship(accum):
+    mapper_registry = registry()
+
+    @mapper_registry.mapped
+    class Declarative1:
+        __tablename__ = "DeclarativeModel1"
+
+        id: Mapped[int] = mapped_column(primary_key=True)
+
+        __eq__ = sqlalchemy_equals
+
+    @mapper_registry.mapped
+    class Declarative2:
+        __tablename__ = "DeclarativeModel2"
+
+        id: Mapped[int] = mapped_column(primary_key=True)
+        text: Mapped[str]
+
+        parent_id: Mapped[Optional[int]] = mapped_column(ForeignKey(Declarative1.id))
+        parent: Mapped[Optional[Declarative1]] = relationship()
+
+        __eq__ = sqlalchemy_equals
+
+    retort = Retort(recipe=[accum])
+
+    loader = retort.get_loader(Declarative2)
+    assert (
+        loader({'id': 1, 'text': 'abc', 'parent_id': 100})
+        ==
+        Declarative2(id=1, text='abc', parent_id=100)
+    )
+    assert (
+        loader({'id': 1, 'text': 'abc', 'parent': {'id': 100}})
+        ==
+        Declarative2(id=1, text='abc', parent=Declarative1(id=100))
+    )
+    assert (
+        loader({'id': 1, 'text': 'abc', 'parent_id': 100, 'parent': {'id': 100}})
+        ==
+        Declarative2(id=1, text='abc', parent_id=100, parent=Declarative1(id=100))
+    )
+
+    dumper = retort.get_dumper(Declarative2)
+    assert (
+        dumper(Declarative2(id=1, text='abc', parent_id=100))
+        ==
+        {'id': 1, 'text': 'abc', 'parent_id': 100, 'parent': None}
+    )
+    assert (
+        dumper(Declarative2(id=1, text='abc', parent=Declarative1(id=100)))
+        ==
+        {'id': 1, 'text': 'abc', 'parent_id': None, 'parent': {'id': 100}}
+    )
+    assert (
+        dumper(Declarative2(id=1, text='abc', parent_id=100, parent=Declarative1(id=100)))
+        ==
+        {'id': 1, 'text': 'abc', 'parent_id': 100, 'parent': {'id': 100}}
+    )
diff --git a/tests/integration/test_type_alias_312.py b/tests/integration/morphing/test_type_alias_312.py
similarity index 100%
rename from tests/integration/test_type_alias_312.py
rename to tests/integration/morphing/test_type_alias_312.py
diff --git a/tests/tests_helpers/tests_helpers.py b/tests/tests_helpers/tests_helpers.py
index 1833ae69..d3d583e6 100644
--- a/tests/tests_helpers/tests_helpers.py
+++ b/tests/tests_helpers/tests_helpers.py
@@ -5,20 +5,37 @@
 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, Optional, Reversible, Type, TypeVar, Union
+from typing import (
+    Any,
+    Callable,
+    Dict,
+    Generator,
+    List,
+    Mapping,
+    NamedTuple,
+    Optional,
+    Reversible,
+    Type,
+    TypedDict,
+    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 DistributionVersionRequirement, Requirement
+from adaptix._internal.feature_requirement import HAS_ATTRS_PKG, HAS_PY_311, 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 is_parametrized
+from adaptix._internal.type_tools import get_all_type_hints, is_parametrized
 from adaptix._internal.utils import add_note
 from adaptix.struct_trail import get_trail
 
@@ -229,3 +246,135 @@ def with_notes(exc: E, *notes: Union[str, List[str]]) -> E:
 
 def with_trail(exc: E, sub_trail: Reversible[TrailElement]) -> E:
     return render_trail_as_note(extend_trail(exc, sub_trail))
+
+
+class FailedRequirement(Requirement):
+    def __init__(self, fail_reason: str):
+        self._fail_reason = fail_reason
+        super().__init__()
+
+    def _evaluate(self) -> bool:
+        return False
+
+    @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/unit/conversion/__init__.py b/tests/unit/conversion/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/unit/conversion/facade/__init__.py b/tests/unit/conversion/facade/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/unit/conversion/facade/test_checker.py b/tests/unit/conversion/facade/test_checker.py
new file mode 100644
index 00000000..b28c283c
--- /dev/null
+++ b/tests/unit/conversion/facade/test_checker.py
@@ -0,0 +1,33 @@
+import pytest
+from tests_helpers import full_match_regex_str
+
+from adaptix._internal.conversion.facade.checker import ensure_function_is_stub
+
+
+def test_pass():
+    def func():
+        pass
+
+    ensure_function_is_stub(func)
+
+
+def test_ellipsis():
+    def func():
+        ...
+
+    ensure_function_is_stub(func)
+
+
+def test_docstring():
+    def func():
+        """I'm a func"""
+
+    ensure_function_is_stub(func)
+
+
+def test_exception():
+    def func():
+        return 1 + 2
+
+    with pytest.raises(ValueError, match=full_match_regex_str('Body of function must be empty')):
+        ensure_function_is_stub(func)
diff --git a/tests/unit/model_tools/introspection/test_sqlalchemy.py b/tests/unit/model_tools/introspection/test_sqlalchemy.py
new file mode 100644
index 00000000..52899072
--- /dev/null
+++ b/tests/unit/model_tools/introspection/test_sqlalchemy.py
@@ -0,0 +1,690 @@
+from typing import Optional
+from unittest.mock import ANY
+
+from sqlalchemy import Column, ForeignKey, Integer, String, Table
+from sqlalchemy.orm import Mapped, mapped_column, registry, relationship
+from tests_helpers import requires
+
+from adaptix._internal.feature_requirement import HAS_ANNOTATED
+from adaptix._internal.model_tools.definitions import (
+    DefaultFactory,
+    DefaultValue,
+    InputField,
+    InputShape,
+    NoDefault,
+    OutputField,
+    OutputShape,
+    Param,
+    ParamKind,
+    Shape,
+    create_attr_accessor,
+)
+from adaptix._internal.model_tools.introspection.sqlalchemy import get_sqlalchemy_shape
+
+
+def default_factory():
+    return 2
+
+
+def test_declarative():
+    mapper_registry = registry()
+
+    @mapper_registry.mapped
+    class Declarative1:
+        __tablename__ = "DeclarativeModel1"
+
+        id: Mapped[int] = mapped_column(primary_key=True)
+
+    @mapper_registry.mapped
+    class Declarative2:
+        __tablename__ = "DeclarativeModel2"
+
+        id: Mapped[int] = mapped_column(primary_key=True)
+        text: Mapped[str]
+        nullable_field: Mapped[Optional[int]]
+        field_with_default: Mapped[int] = mapped_column(default=2)
+        field_with_default_factory: Mapped[int] = mapped_column(default=default_factory)
+        field_with_default_context_factory: Mapped[int] = mapped_column(default=lambda ctx: 2)
+        field_with_old_syntax = Column(String(), nullable=False)
+        nullable_field_with_old_syntax = Column(Integer(), nullable=True)
+
+        parent_id: Mapped[Optional[int]] = mapped_column(ForeignKey(Declarative1.id))
+        parent: Mapped[Optional[Declarative1]] = relationship()
+
+    assert (
+        get_sqlalchemy_shape(Declarative2)
+        ==
+        Shape(
+            input=InputShape(
+                constructor=Declarative2,
+                kwargs=None,
+                fields=(
+                    InputField(
+                        type=int,
+                        id="id",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=str,
+                        id="text",
+                        default=NoDefault(),
+                        is_required=True,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=Optional[int],
+                        id="nullable_field",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=int,
+                        id="field_with_default",
+                        default=DefaultValue(2),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=int,
+                        id="field_with_default_factory",
+                        default=DefaultFactory(default_factory),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=int,
+                        id="field_with_default_context_factory",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=str,
+                        id="field_with_old_syntax",
+                        default=NoDefault(),
+                        is_required=True,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=Optional[int],
+                        id="nullable_field_with_old_syntax",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=Optional[int],
+                        id="parent_id",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=Optional[Declarative1],
+                        id="parent",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY,
+                    ),
+                ),
+                overriden_types=frozenset(),
+                params=(
+                    Param(
+                        field_id='id',
+                        name='id',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='text',
+                        name='text',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='nullable_field',
+                        name='nullable_field',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='field_with_default',
+                        name='field_with_default',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='field_with_default_factory',
+                        name='field_with_default_factory',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='field_with_default_context_factory',
+                        name='field_with_default_context_factory',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='field_with_old_syntax',
+                        name='field_with_old_syntax',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='nullable_field_with_old_syntax',
+                        name='nullable_field_with_old_syntax',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='parent_id',
+                        name='parent_id',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='parent',
+                        name='parent',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                )
+            ),
+            output=OutputShape(
+                fields=(
+                    OutputField(
+                        type=int,
+                        id="id",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('id', is_required=True),
+                    ),
+                    OutputField(
+                        type=str,
+                        id="text",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('text', is_required=True),
+                    ),
+                    OutputField(
+                        type=Optional[int],
+                        id="nullable_field",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('nullable_field', is_required=True),
+                    ),
+                    OutputField(
+                        type=int,
+                        id="field_with_default",
+                        default=DefaultValue(2),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('field_with_default', is_required=True),
+                    ),
+                    OutputField(
+                        type=int,
+                        id="field_with_default_factory",
+                        default=DefaultFactory(default_factory),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('field_with_default_factory', is_required=True),
+                    ),
+                    OutputField(
+                        type=int,
+                        id="field_with_default_context_factory",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('field_with_default_context_factory', is_required=True),
+                    ),
+                    OutputField(
+                        type=str,
+                        id="field_with_old_syntax",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('field_with_old_syntax', is_required=True),
+                    ),
+                    OutputField(
+                        type=Optional[int],
+                        id="nullable_field_with_old_syntax",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('nullable_field_with_old_syntax', is_required=True),
+                    ),
+                    OutputField(
+                        type=Optional[int],
+                        id="parent_id",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('parent_id', is_required=True),
+                    ),
+                    OutputField(
+                        type=Optional[Declarative1],
+                        id="parent",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('parent', is_required=True),
+                    ),
+                ),
+                overriden_types=frozenset()
+            )
+        )
+    )
+
+
+def test_imperative():
+    mapper_registry = registry()
+
+    imperative_table1 = Table(
+        "Imperative1",
+        mapper_registry.metadata,
+        Column("id", Integer, primary_key=True),
+    )
+
+    imperative_table2 = Table(
+        "Imperative2",
+        mapper_registry.metadata,
+        Column("id", Integer, primary_key=True),
+        Column("text", String, nullable=False),
+        Column("nullable_field", Integer, nullable=True),
+        Column("field_with_default", Integer, nullable=False, default=2),
+        Column("field_with_default_factory", Integer, nullable=False, default=default_factory),
+        Column("field_with_default_context_factory", Integer, nullable=False, default=lambda ctx: 2),
+        Column("parent_id", ForeignKey(imperative_table1.c.id), nullable=False),
+    )
+
+    class Imperative1:
+        pass
+
+    class Imperative2:
+        pass
+
+    mapper_registry.map_imperatively(
+        Imperative1,
+        imperative_table1,
+    )
+
+    mapper_registry.map_imperatively(
+        Imperative2,
+        imperative_table2,
+        properties={
+            "parent": relationship(Imperative1)
+        }
+    )
+
+    assert (
+        get_sqlalchemy_shape(Imperative2)
+        ==
+        Shape(
+            input=InputShape(
+                constructor=Imperative2,
+                kwargs=None,
+                fields=(
+                    InputField(
+                        type=int,
+                        id="id",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=str,
+                        id="text",
+                        default=NoDefault(),
+                        is_required=True,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=Optional[int],
+                        id="nullable_field",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=int,
+                        id="field_with_default",
+                        default=DefaultValue(2),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=int,
+                        id="field_with_default_factory",
+                        default=DefaultFactory(default_factory),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=int,
+                        id="field_with_default_context_factory",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        id='parent_id',
+                        type=int,
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        is_required=False
+                    ),
+                    InputField(
+                        type=Optional[Imperative1],
+                        id="parent",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                ),
+                params=(
+                    Param(
+                        field_id='id',
+                        name='id',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='text',
+                        name='text',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='nullable_field',
+                        name='nullable_field',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='field_with_default',
+                        name='field_with_default',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='field_with_default_factory',
+                        name='field_with_default_factory',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='field_with_default_context_factory',
+                        name='field_with_default_context_factory',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='parent_id',
+                        name='parent_id',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='parent',
+                        name='parent',
+                        kind=ParamKind.KW_ONLY
+                    ),
+                ),
+                overriden_types=frozenset()
+            ),
+            output=OutputShape(
+                fields=(
+                    OutputField(
+                        type=int,
+                        id="id",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('id', is_required=True),
+                    ),
+                    OutputField(
+                        type=str,
+                        id="text",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('text', is_required=True),
+                    ),
+                    OutputField(
+                        type=Optional[int],
+                        id="nullable_field",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('nullable_field', is_required=True),
+                    ),
+                    OutputField(
+                        type=int,
+                        id="field_with_default",
+                        default=DefaultValue(2),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('field_with_default', is_required=True),
+                    ),
+                    OutputField(
+                        type=int,
+                        id="field_with_default_factory",
+                        default=DefaultFactory(default_factory),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('field_with_default_factory', is_required=True),
+                    ),
+                    OutputField(
+                        type=int,
+                        id="field_with_default_context_factory",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('field_with_default_context_factory', is_required=True),
+                    ),
+                    OutputField(
+                        id='parent_id',
+                        type=int,
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('parent_id', is_required=True)
+                    ),
+                    OutputField(
+                        id='parent',
+                        type=Optional[Imperative1],
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('parent', is_required=True),
+                    )
+                ),
+                overriden_types=frozenset()
+            )
+        )
+    )
+
+
+@requires(HAS_ANNOTATED)
+def test_declarative_annotated():
+    from typing import Annotated
+
+    mapper_registry = registry()
+
+    @mapper_registry.mapped
+    class Declarative1:
+        __tablename__ = "DeclarativeModel1"
+
+        id: Mapped[int] = mapped_column(primary_key=True)
+
+    tp_id = Annotated[int, mapped_column(primary_key=True)]
+    tp_field_with_default = Annotated[int, mapped_column(default=2)]
+    tp_field_with_default_factory = Annotated[int, mapped_column(default=default_factory)]
+    tp_field_with_default_context_factory = Annotated[int, mapped_column(default=lambda ctx: 2)]
+    tp_parent_id = Annotated[Optional[int], mapped_column(ForeignKey(Declarative1.id))]
+
+    @mapper_registry.mapped
+    class Declarative2:
+        __tablename__ = "DeclarativeModel2"
+
+        id: Mapped[tp_id]
+        field_with_default: Mapped[tp_field_with_default]
+        field_with_default_factory: Mapped[tp_field_with_default_factory]
+        field_with_default_context_factory: Mapped[tp_field_with_default_context_factory]
+        parent_id: Mapped[tp_parent_id]
+        parent: Mapped[Optional[Declarative1]] = relationship()
+
+    assert (
+        get_sqlalchemy_shape(Declarative2)
+        ==
+        Shape(
+            input=InputShape(
+                constructor=Declarative2,
+                kwargs=None,
+                fields=(
+                    InputField(
+                        type=tp_id,
+                        id="id",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=tp_field_with_default,
+                        id="field_with_default",
+                        default=DefaultValue(2),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=tp_field_with_default_factory,
+                        id="field_with_default_factory",
+                        default=DefaultFactory(default_factory),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=tp_field_with_default_context_factory,
+                        id="field_with_default_context_factory",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=tp_parent_id,
+                        id="parent_id",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY
+                    ),
+                    InputField(
+                        type=Optional[Declarative1],
+                        id="parent",
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata={},
+                        original=ANY,
+                    ),
+                ),
+                overriden_types=frozenset(),
+                params=(
+                    Param(
+                        field_id='id',
+                        name='id',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='field_with_default',
+                        name='field_with_default',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='field_with_default_factory',
+                        name='field_with_default_factory',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='field_with_default_context_factory',
+                        name='field_with_default_context_factory',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='parent_id',
+                        name='parent_id',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='parent',
+                        name='parent',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                )
+            ),
+            output=OutputShape(
+                fields=(
+                    OutputField(
+                        type=tp_id,
+                        id="id",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('id', is_required=True),
+                    ),
+                    OutputField(
+                        type=tp_field_with_default,
+                        id="field_with_default",
+                        default=DefaultValue(2),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('field_with_default', is_required=True),
+                    ),
+                    OutputField(
+                        type=tp_field_with_default_factory,
+                        id="field_with_default_factory",
+                        default=DefaultFactory(default_factory),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('field_with_default_factory', is_required=True),
+                    ),
+                    OutputField(
+                        type=tp_field_with_default_context_factory,
+                        id="field_with_default_context_factory",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('field_with_default_context_factory', is_required=True),
+                    ),
+                    OutputField(
+                        type=tp_parent_id,
+                        id="parent_id",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('parent_id', is_required=True),
+                    ),
+                    OutputField(
+                        type=Optional[Declarative1],
+                        id="parent",
+                        default=NoDefault(),
+                        metadata={},
+                        original=ANY,
+                        accessor=create_attr_accessor('parent', is_required=True),
+                    ),
+                ),
+                overriden_types=frozenset()
+            )
+        )
+    )
diff --git a/tests/unit/model_tools/introspection/test_typed_dict.py b/tests/unit/model_tools/introspection/test_typed_dict.py
index ccba913b..b9ad5968 100644
--- a/tests/unit/model_tools/introspection/test_typed_dict.py
+++ b/tests/unit/model_tools/introspection/test_typed_dict.py
@@ -665,3 +665,163 @@ class Child(Base, total=False):
             )
         )
     )
+
+
+@requires(HAS_TYPED_DICT_REQUIRED)
+def test_required_annotated():
+    class Base(TypedDict):
+        f1: int
+        f2: typing.Annotated[typing.Required[int], "metadata"]
+        f3: 'typing.NotRequired[typing.Annotated[int, "metadata"]]'
+
+    class Child(Base, total=False):
+        f4: int
+        f5: 'typing.Annotated[typing.Required[int], "metadata"]'
+        f6: typing.NotRequired[typing.Annotated[int, "metadata"]]
+
+    assert (
+        get_typed_dict_shape(Child)
+        ==
+        Shape(
+            input=InputShape(
+                constructor=Child,
+                kwargs=None,
+                fields=(
+                    InputField(
+                        type=int,
+                        id='f1',
+                        default=NoDefault(),
+                        is_required=True,
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                    InputField(
+                        type=typing.Annotated[typing.Required[int], "metadata"],
+                        id='f2',
+                        default=NoDefault(),
+                        is_required=True,
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                    InputField(
+                        type=typing.NotRequired[typing.Annotated[int, "metadata"]],
+                        id='f3',
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                    InputField(
+                        type=int,
+                        id='f4',
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                    InputField(
+                        type=typing.Annotated[typing.Required[int], "metadata"],
+                        id='f5',
+                        default=NoDefault(),
+                        is_required=True,
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                    InputField(
+                        type=typing.NotRequired[typing.Annotated[int, "metadata"]],
+                        id='f6',
+                        default=NoDefault(),
+                        is_required=False,
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                ),
+                params=(
+                    Param(
+                        field_id='f1',
+                        name='f1',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='f2',
+                        name='f2',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='f3',
+                        name='f3',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='f4',
+                        name='f4',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='f5',
+                        name='f5',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                    Param(
+                        field_id='f6',
+                        name='f6',
+                        kind=ParamKind.KW_ONLY,
+                    ),
+                ),
+                overriden_types=frozenset({}),
+            ),
+            output=OutputShape(
+                fields=(
+                    OutputField(
+                        type=int,
+                        id='f1',
+                        default=NoDefault(),
+                        accessor=create_key_accessor('f1', access_error=None),
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                    OutputField(
+                        type=typing.Annotated[typing.Required[int], "metadata"],
+                        id='f2',
+                        default=NoDefault(),
+                        accessor=create_key_accessor('f2', access_error=None),
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                    OutputField(
+                        type=typing.NotRequired[typing.Annotated[int, "metadata"]],
+                        id='f3',
+                        default=NoDefault(),
+                        accessor=create_key_accessor('f3', access_error=KeyError),
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                    OutputField(
+                        type=int,
+                        id='f4',
+                        default=NoDefault(),
+                        accessor=create_key_accessor('f4', access_error=KeyError),
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                    OutputField(
+                        type=typing.Annotated[typing.Required[int], "metadata"],
+                        id='f5',
+                        default=NoDefault(),
+                        accessor=create_key_accessor('f5', access_error=None),
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                    OutputField(
+                        type=typing.NotRequired[typing.Annotated[int, "metadata"]],
+                        id='f6',
+                        default=NoDefault(),
+                        accessor=create_key_accessor('f6', access_error=KeyError),
+                        metadata=MappingProxyType({}),
+                        original=None,
+                    ),
+                ),
+                overriden_types=frozenset({}),
+            )
+        )
+    )
diff --git a/tests/unit/morphing/generic_provider/test_literal_provider.py b/tests/unit/morphing/generic_provider/test_literal_provider.py
index 18f8a601..4a8cfdf4 100644
--- a/tests/unit/morphing/generic_provider/test_literal_provider.py
+++ b/tests/unit/morphing/generic_provider/test_literal_provider.py
@@ -1,11 +1,13 @@
+from enum import Enum
 from typing import Literal
 from uuid import uuid4
 
 import pytest
 from tests_helpers import TestRetort, raises_exc
 
-from adaptix._internal.morphing.generic_provider import LiteralProvider
-from adaptix._internal.morphing.load_error import BadVariantError
+from adaptix._internal.morphing.enum_provider import EnumExactValueProvider
+from adaptix._internal.morphing.generic_provider import LiteralProvider, UnionProvider
+from adaptix._internal.morphing.load_error import BadVariantLoadError
 
 
 @pytest.fixture
@@ -13,6 +15,8 @@ def retort():
     return TestRetort(
         recipe=[
             LiteralProvider(),
+            EnumExactValueProvider(),
+            UnionProvider()
         ]
     )
 
@@ -30,7 +34,7 @@ def test_loader_base(retort, strict_coercion, debug_trail):
     assert loader(10) == 10
 
     raises_exc(
-        BadVariantError({'a', 'b', 10}, 'c'),
+        BadVariantLoadError({'a', 'b', 10}, 'c'),
         lambda: loader("c")
     )
 
@@ -61,11 +65,11 @@ def test_strict_coercion(retort, debug_trail):
     assert _is_exact_one(literal_loader(1))
 
     raises_exc(
-        BadVariantError({0, 1, rnd_val1}, False),
+        BadVariantLoadError({0, 1, rnd_val1}, False),
         lambda: literal_loader(False)
     )
     raises_exc(
-        BadVariantError({0, 1, rnd_val1}, True),
+        BadVariantLoadError({0, 1, rnd_val1}, True),
         lambda: literal_loader(True)
     )
 
@@ -81,10 +85,79 @@ def test_strict_coercion(retort, debug_trail):
     assert bool_loader(True) is True
 
     raises_exc(
-        BadVariantError({False, True, rnd_val2}, 0),
+        BadVariantLoadError({False, True, rnd_val2}, 0),
         lambda: bool_loader(0)
     )
     raises_exc(
-        BadVariantError({False, True, rnd_val2}, 1),
+        BadVariantLoadError({False, True, rnd_val2}, 1),
         lambda: bool_loader(1)
     )
+
+
+def test_loader_with_enums(retort, strict_coercion, debug_trail):
+    class Enum1(Enum):
+        CASE1 = 1
+        CASE2 = 2
+
+    class Enum2(Enum):
+        CASE1 = 1
+        CASE2 = 2
+
+    loader = retort.replace(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    ).get_loader(
+        Literal["a", Enum1.CASE1, 5]
+    )
+
+    assert loader("a") == "a"
+    assert loader(1) == Enum1.CASE1
+    assert loader(5) == 5
+
+    loader = retort.replace(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    ).get_loader(
+        Literal[Enum1.CASE1, Enum2.CASE2, 10]
+    )
+
+    assert loader(1) == Enum1.CASE1
+    assert loader(2) == Enum2.CASE2
+    assert loader(10) == 10
+
+    raises_exc(
+        BadVariantLoadError({Enum1.CASE1.value, Enum2.CASE2.value, 10}, 15),
+        lambda: loader(15)
+    )
+
+
+def test_dumper_with_enums(retort, strict_coercion, debug_trail):
+    class Enum1(Enum):
+        CASE1 = 1
+        CASE2 = 2
+
+    class Enum2(Enum):
+        CASE1 = 1
+        CASE2 = 2
+
+    dumper = retort.replace(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    ).get_dumper(
+        Literal["a", Enum1.CASE1, 5]
+    )
+
+    assert dumper("a") == "a"
+    assert dumper(Enum1.CASE1) == 1
+    assert dumper(5) == 5
+
+    dumper = retort.replace(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    ).get_dumper(
+        Literal[Enum1.CASE1, Enum2.CASE2, 10]
+    )
+
+    assert dumper(Enum1.CASE1) == 1
+    assert dumper(Enum1.CASE2) == 2
+    assert dumper(10) == 10
diff --git a/tests/unit/morphing/generic_provider/test_union_provider.py b/tests/unit/morphing/generic_provider/test_union_provider.py
index 493a9b39..b9bd46a9 100644
--- a/tests/unit/morphing/generic_provider/test_union_provider.py
+++ b/tests/unit/morphing/generic_provider/test_union_provider.py
@@ -7,7 +7,7 @@
 from adaptix import CannotProvide, DebugTrail, NoSuitableProvider, Retort, dumper, loader
 from adaptix._internal.compat import CompatExceptionGroup
 from adaptix._internal.morphing.generic_provider import LiteralProvider, UnionProvider
-from adaptix._internal.morphing.load_error import BadVariantError, LoadError, TypeLoadError, UnionLoadError
+from adaptix._internal.morphing.load_error import BadVariantLoadError, LoadError, TypeLoadError, UnionLoadError
 
 
 @dataclass
@@ -241,14 +241,14 @@ def test_literal(strict_coercion, debug_trail):
 
     if debug_trail == DebugTrail.DISABLE:
         raises_exc(
-            BadVariantError({'a'}, 'b'),
+            BadVariantLoadError({'a'}, 'b'),
             lambda: loader_('b'),
         )
     elif debug_trail in (DebugTrail.FIRST, DebugTrail.ALL):
         raises_exc(
             UnionLoadError(
                 f'while loading {Literal["a", None]}',
-                [TypeLoadError(None, 'b'), BadVariantError({'a'}, 'b')]
+                [TypeLoadError(None, 'b'), BadVariantLoadError({'a'}, 'b')]
             ),
             lambda: loader_('b'),
         )
diff --git a/tests/unit/morphing/model/shape_provider/conftest.py b/tests/unit/morphing/model/shape_provider/conftest.py
index cc6071b7..88505da5 100644
--- a/tests/unit/morphing/model/shape_provider/conftest.py
+++ b/tests/unit/morphing/model/shape_provider/conftest.py
@@ -1,23 +1,3 @@
-from dataclasses import dataclass
-from typing import NamedTuple, TypedDict
-
-import pytest
 from tests_helpers import pretty_typehint_test_id
 
-from .local_helpers import DEFAULT_MODEL_SPEC_PARAMS, ModelSpec
-
 pytest_make_parametrize_id = pretty_typehint_test_id
-
-
-@pytest.fixture(params=DEFAULT_MODEL_SPEC_PARAMS)
-def model_spec(request):
-    if request.param == 'dataclass':
-        return ModelSpec(decorator=dataclass, bases=())
-    if request.param == 'typed_dict':
-        return ModelSpec(decorator=lambda x: x, bases=(TypedDict,))
-    if request.param == 'named_tuple':
-        return ModelSpec(decorator=lambda x: x, bases=(NamedTuple,))
-    if request.param == 'attrs':
-        from attrs import define
-        return ModelSpec(decorator=define, bases=())
-    raise ValueError
diff --git a/tests/unit/morphing/model/shape_provider/local_helpers.py b/tests/unit/morphing/model/shape_provider/local_helpers.py
index 67913ac0..dc8e5df8 100644
--- a/tests/unit/morphing/model/shape_provider/local_helpers.py
+++ b/tests/unit/morphing/model/shape_provider/local_helpers.py
@@ -1,12 +1,7 @@
-from dataclasses import dataclass
-from typing import Any, Mapping
-
-import pytest
-from tests_helpers import cond_list
+from typing import Mapping
 
 from adaptix import Retort, TypeHint
-from adaptix._internal.feature_requirement import HAS_ATTRS_PKG, HAS_PY_311
-from adaptix._internal.provider.request_cls import LocMap, TypeHintLoc
+from adaptix._internal.provider.request_cls import LocMap, LocStack, TypeHintLoc
 from adaptix._internal.provider.shape_provider import (
     InputShapeRequest,
     OutputShapeRequest,
@@ -16,59 +11,18 @@
 
 def assert_fields_types(tp: TypeHint, expected: Mapping[str, TypeHint]) -> None:
     retort = Retort()
-    mediator = retort._create_mediator(request_stack=())
+    mediator = retort._create_mediator()
 
     input_shape = provide_generic_resolved_shape(
         mediator,
-        InputShapeRequest(loc_map=LocMap(TypeHintLoc(type=tp))),
+        InputShapeRequest(loc_stack=LocStack(LocMap(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_map=LocMap(TypeHintLoc(type=tp))),
+        OutputShapeRequest(loc_stack=LocStack(LocMap(TypeHintLoc(type=tp)))),
     )
     output_field_types = {field.id: field.type for field in output_shape.fields}
     assert output_field_types == expected
-
-
-@dataclass
-class ModelSpec:
-    decorator: Any
-    bases: Any
-
-
-DEFAULT_MODEL_SPEC_PARAMS = (
-    'dataclass',
-    *cond_list(
-        HAS_PY_311,
-        [
-            'typed_dict',
-            'named_tuple',
-        ],
-    ),
-    *cond_list(
-        HAS_ATTRS_PKG,
-        [
-            'attrs',
-        ],
-    ),
-)
-
-
-def exclude_model_spec(first_spec: str, *other_specs: str):
-    specs = [first_spec, *other_specs]
-
-    def decorator(func):
-        return pytest.mark.parametrize(
-            'model_spec',
-            [
-                spec
-                for spec in DEFAULT_MODEL_SPEC_PARAMS
-                if spec not in specs
-            ],
-            indirect=True
-        )(func)
-
-    return decorator
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 69921d06..45e750b2 100644
--- a/tests/unit/morphing/model/shape_provider/test_generic_resolving.py
+++ b/tests/unit/morphing/model/shape_provider/test_generic_resolving.py
@@ -2,7 +2,14 @@
 
 import pytest
 from pytest import param
-from tests_helpers import cond_list, load_namespace_keeping_module, requires
+from tests_helpers import (
+    GENERIC_MODELS_REQUIREMENTS,
+    ModelSpec,
+    cond_list,
+    exclude_model_spec,
+    load_namespace_keeping_module,
+    requires,
+)
 
 from adaptix import CannotProvide, Retort
 from adaptix._internal.feature_requirement import (
@@ -14,20 +21,23 @@
     HAS_TV_TUPLE,
     IS_PYPY,
 )
-from adaptix._internal.provider.request_cls import LocMap, TypeHintLoc
+from adaptix._internal.provider.request_cls import LocMap, LocStack, TypeHintLoc
 from adaptix._internal.provider.shape_provider import (
     InputShapeRequest,
     OutputShapeRequest,
     provide_generic_resolved_shape,
 )
 
-from .local_helpers import assert_fields_types, exclude_model_spec
+from .local_helpers import assert_fields_types
 
 T = TypeVar('T')
 K = TypeVar('K')
 V = TypeVar('V')
 
 
+adaptix_model_spec_requirements = GENERIC_MODELS_REQUIREMENTS
+
+
 @pytest.fixture(
     params=[
         pytest.param('data_gen_models.py', id='inheritance'),
@@ -120,7 +130,7 @@ class WithSubUnparametrized(*model_spec.bases, Generic[T]):
     )
 
 
-@exclude_model_spec('named_tuple')
+@exclude_model_spec(ModelSpec.NAMED_TUPLE)
 def test_single_inheritance(model_spec):
     @model_spec.decorator
     class Parent(*model_spec.bases, Generic[T]):
@@ -136,7 +146,7 @@ class Child(Parent[int]):
     )
 
 
-@exclude_model_spec('named_tuple')
+@exclude_model_spec(ModelSpec.NAMED_TUPLE)
 def test_single_inheritance_generic_child(model_spec):
     @model_spec.decorator
     class Parent(*model_spec.bases, Generic[T]):
@@ -161,7 +171,7 @@ class Child(Parent[int], Generic[T]):
     )
 
 
-@exclude_model_spec('named_tuple', 'attrs')
+@exclude_model_spec(ModelSpec.NAMED_TUPLE, ModelSpec.ATTRS)
 def test_multiple_inheritance(model_spec):
     @model_spec.decorator
     class Parent1(*model_spec.bases, Generic[T]):
@@ -199,7 +209,7 @@ class Child(Parent1[int], Parent2[bool], Generic[T]):
 T7 = TypeVar('T7')
 
 
-@exclude_model_spec('named_tuple', 'attrs')
+@exclude_model_spec(ModelSpec.NAMED_TUPLE, ModelSpec.ATTRS)
 @pytest.mark.parametrize('tp', [List, list] if HAS_STD_CLASSES_GENERICS else [List])
 def test_generic_multiple_inheritance(model_spec, tp) -> None:
     @model_spec.decorator
@@ -256,22 +266,22 @@ class Child(Parent1[int, bool], Parent2[str, bytes], Generic[T7]):
 )
 def test_not_a_model(tp):
     retort = Retort()
-    mediator = retort._create_mediator(request_stack=())
+    mediator = retort._create_mediator()
 
     with pytest.raises(CannotProvide):
         provide_generic_resolved_shape(
             mediator,
-            InputShapeRequest(loc_map=LocMap(TypeHintLoc(type=tp))),
+            InputShapeRequest(loc_stack=LocStack(LocMap(TypeHintLoc(type=tp)))),
         )
 
     with pytest.raises(CannotProvide):
         provide_generic_resolved_shape(
             mediator,
-            OutputShapeRequest(loc_map=LocMap(TypeHintLoc(type=tp))),
+            OutputShapeRequest(loc_stack=LocStack(LocMap(TypeHintLoc(type=tp)))),
         )
 
 
-@exclude_model_spec('named_tuple', 'attrs')
+@exclude_model_spec(ModelSpec.NAMED_TUPLE, ModelSpec.ATTRS)
 def test_generic_mixed_inheritance(model_spec):
     @model_spec.decorator
     class Parent1(*model_spec.bases):
@@ -300,7 +310,7 @@ class Child21(Parent2[str], Parent1):
     )
 
 
-@exclude_model_spec('named_tuple')
+@exclude_model_spec(ModelSpec.NAMED_TUPLE)
 def test_generic_parents_with_type_override(model_spec):
     @model_spec.decorator
     class Parent(*model_spec.bases, Generic[T]):
@@ -316,7 +326,7 @@ class Child(Parent[int]):
     )
 
 
-@exclude_model_spec('named_tuple')
+@exclude_model_spec(ModelSpec.NAMED_TUPLE)
 def test_generic_parents_with_type_override_generic(model_spec):
     @model_spec.decorator
     class Parent(*model_spec.bases, Generic[T]):
diff --git a/tests/unit/morphing/model/test_loader_provider.py b/tests/unit/morphing/model/test_loader_provider.py
index 8129314d..e616b318 100644
--- a/tests/unit/morphing/model/test_loader_provider.py
+++ b/tests/unit/morphing/model/test_loader_provider.py
@@ -8,7 +8,16 @@
 
 from adaptix import DebugTrail, ExtraKwargs, Loader, bound
 from adaptix._internal.common import VarTuple
-from adaptix._internal.model_tools.definitions import InputField, InputShape, NoDefault, Param, ParamKind, ParamKwargs
+from adaptix._internal.model_tools.definitions import (
+    Default,
+    DefaultValue,
+    InputField,
+    InputShape,
+    NoDefault,
+    Param,
+    ParamKind,
+    ParamKwargs,
+)
 from adaptix._internal.morphing.load_error import AggregateLoadError, ExcludedTypeLoadError, ValueLoadError
 from adaptix._internal.morphing.model.crown_definitions import (
     ExtraCollect,
@@ -28,11 +37,11 @@
 from adaptix._internal.provider.provider_template import ValueProvider
 from adaptix._internal.provider.shape_provider import InputShapeRequest
 from adaptix.load_error import (
-    ExtraFieldsError,
-    ExtraItemsError,
+    ExtraFieldsLoadError,
+    ExtraItemsLoadError,
     LoadError,
-    NoRequiredFieldsError,
-    NoRequiredItemsError,
+    NoRequiredFieldsLoadError,
+    NoRequiredItemsLoadError,
     TypeLoadError,
 )
 
@@ -60,6 +69,7 @@ class TestField:
     id: str
     param_kind: ParamKind
     is_required: bool
+    default: Default = NoDefault()
 
 
 def shape(*fields: TestField, kwargs: Optional[ParamKwargs] = None):
@@ -68,7 +78,7 @@ def shape(*fields: TestField, kwargs: Optional[ParamKwargs] = None):
             InputField(
                 type=int,
                 id=fld.id,
-                default=NoDefault(),
+                default=fld.default,
                 is_required=fld.is_required,
                 metadata=MappingProxyType({}),
                 original=None,
@@ -165,11 +175,11 @@ def test_direct(debug_ctx, debug_trail, extra_policy, trail_select):
         data = {'a': 1, 'b': 2, 'c': 3}
         raises_exc(
             trail_select(
-                disable=ExtraFieldsError({'c'}, data),
-                first=ExtraFieldsError({'c'}, data),
+                disable=ExtraFieldsLoadError({'c'}, data),
+                first=ExtraFieldsLoadError({'c'}, data),
                 all=AggregateLoadError(
                     f'while loading model {Gauge}',
-                    [ExtraFieldsError({'c'}, data)],
+                    [ExtraFieldsLoadError({'c'}, data)],
                 ),
             ),
             lambda: loader(data),
@@ -190,11 +200,11 @@ def test_direct(debug_ctx, debug_trail, extra_policy, trail_select):
     data = {'a': 1}
     raises_exc(
         trail_select(
-            disable=NoRequiredFieldsError({'b'}, data),
-            first=NoRequiredFieldsError({'b'}, data),
+            disable=NoRequiredFieldsLoadError({'b'}, data),
+            first=NoRequiredFieldsLoadError({'b'}, data),
             all=AggregateLoadError(
                 f'while loading model {Gauge}',
-                [NoRequiredFieldsError({'b'}, data)],
+                [NoRequiredFieldsLoadError({'b'}, data)],
             ),
         ),
         lambda: loader({'a': 1}),
@@ -245,11 +255,11 @@ def test_direct_list(debug_ctx, debug_trail, extra_policy, trail_select, strict_
         data = [1, 2, 3]
         raises_exc(
             trail_select(
-                disable=ExtraItemsError(2, data),
-                first=ExtraItemsError(2, data),
+                disable=ExtraItemsLoadError(2, data),
+                first=ExtraItemsLoadError(2, data),
                 all=AggregateLoadError(
                     f'while loading model {Gauge}',
-                    [ExtraItemsError(2, data)],
+                    [ExtraItemsLoadError(2, data)],
                 ),
             ),
             lambda: loader(data),
@@ -258,11 +268,11 @@ def test_direct_list(debug_ctx, debug_trail, extra_policy, trail_select, strict_
     data = [10]
     raises_exc(
         trail_select(
-            disable=NoRequiredItemsError(2, data),
-            first=NoRequiredItemsError(2, data),
+            disable=NoRequiredItemsLoadError(2, data),
+            first=NoRequiredItemsLoadError(2, data),
             all=AggregateLoadError(
                 f'while loading model {Gauge}',
-                [NoRequiredItemsError(2, data)],
+                [NoRequiredItemsLoadError(2, data)],
             ),
         ),
         lambda: loader(data),
@@ -321,11 +331,11 @@ def test_extra_forbid(debug_ctx, debug_trail, trail_select):
     data = {'a': 1, 'b': 2, 'c': 3}
     raises_exc(
         trail_select(
-            disable=ExtraFieldsError({'c'}, data),
-            first=ExtraFieldsError({'c'}, data),
+            disable=ExtraFieldsLoadError({'c'}, data),
+            first=ExtraFieldsLoadError({'c'}, data),
             all=AggregateLoadError(
                 f'while loading model {Gauge}',
-                [ExtraFieldsError({'c'}, data)],
+                [ExtraFieldsLoadError({'c'}, data)],
             )
         ),
         lambda: loader(data),
@@ -333,11 +343,11 @@ def test_extra_forbid(debug_ctx, debug_trail, trail_select):
     data = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
     raises_exc(
         trail_select(
-            disable=ExtraFieldsError({'c', 'd'}, data),
-            first=ExtraFieldsError({'c', 'd'}, data),
+            disable=ExtraFieldsLoadError({'c', 'd'}, data),
+            first=ExtraFieldsLoadError({'c', 'd'}, data),
             all=AggregateLoadError(
                 f'while loading model {Gauge}',
-                [ExtraFieldsError({'c', 'd'}, data)],
+                [ExtraFieldsLoadError({'c', 'd'}, data)],
             )
         ),
         lambda: loader(data),
@@ -526,9 +536,9 @@ def test_mapping_and_extra_kwargs(debug_ctx, debug_trail, trail_select):
     data = {'a': 1, 'b': 2}
     raises_exc(
         trail_select(
-            disable=NoRequiredFieldsError({'m_a'}, data),
-            first=NoRequiredFieldsError({'m_a'}, data),
-            all=AggregateLoadError(f'while loading model {Gauge}', [NoRequiredFieldsError({'m_a'}, data)])
+            disable=NoRequiredFieldsLoadError({'m_a'}, data),
+            first=NoRequiredFieldsLoadError({'m_a'}, data),
+            all=AggregateLoadError(f'while loading model {Gauge}', [NoRequiredFieldsLoadError({'m_a'}, data)])
         ),
         lambda: loader(data),
     )
@@ -676,9 +686,9 @@ def test_flat_mapping(debug_ctx, debug_trail, is_required, trail_select):
     data = {'a': 1, 'b': 2}
     raises_exc(
         trail_select(
-            disable=NoRequiredFieldsError({'m_a'}, data),
-            first=NoRequiredFieldsError({'m_a'}, data),
-            all=AggregateLoadError(f'while loading model {Gauge}', [NoRequiredFieldsError({'m_a'}, data)])
+            disable=NoRequiredFieldsLoadError({'m_a'}, data),
+            first=NoRequiredFieldsLoadError({'m_a'}, data),
+            all=AggregateLoadError(f'while loading model {Gauge}', [NoRequiredFieldsLoadError({'m_a'}, data)])
         ),
         lambda: loader(data),
     )
@@ -945,9 +955,9 @@ def test_none_crown_at_dict_crown(debug_ctx, debug_trail, extra_policy, trail_se
         data = {'a': 1, 'b': 2, 'c': 3}
         raises_exc(
             trail_select(
-                disable=ExtraFieldsError({'c'}, data),
-                first=ExtraFieldsError({'c'}, data),
-                all=AggregateLoadError(f'while loading model {Gauge}', [ExtraFieldsError({'c'}, data)]),
+                disable=ExtraFieldsLoadError({'c'}, data),
+                first=ExtraFieldsLoadError({'c'}, data),
+                all=AggregateLoadError(f'while loading model {Gauge}', [ExtraFieldsLoadError({'c'}, data)]),
             ),
             lambda: loader(data),
         )
@@ -980,9 +990,9 @@ def test_none_crown_at_list_crown(debug_ctx, debug_trail, extra_policy, trail_se
     data = [1, 2]
     raises_exc(
         trail_select(
-            disable=NoRequiredItemsError(3, data),
-            first=NoRequiredItemsError(3, data),
-            all=AggregateLoadError(f'while loading model {Gauge}', [NoRequiredItemsError(3, data)]),
+            disable=NoRequiredItemsLoadError(3, data),
+            first=NoRequiredItemsLoadError(3, data),
+            all=AggregateLoadError(f'while loading model {Gauge}', [NoRequiredItemsLoadError(3, data)]),
         ),
         lambda: loader(data),
     )
@@ -994,9 +1004,9 @@ def test_none_crown_at_list_crown(debug_ctx, debug_trail, extra_policy, trail_se
         data = [1, 2, 3, 4]
         raises_exc(
             trail_select(
-                disable=ExtraItemsError(3, data),
-                first=ExtraItemsError(3, data),
-                all=AggregateLoadError(f'while loading model {Gauge}', [ExtraItemsError(3, data)]),
+                disable=ExtraItemsLoadError(3, data),
+                first=ExtraItemsLoadError(3, data),
+                all=AggregateLoadError(f'while loading model {Gauge}', [ExtraItemsLoadError(3, data)]),
             ),
             lambda: loader(data),
         )
@@ -1040,7 +1050,7 @@ def test_exception_collection(debug_ctx):
             f'while loading model {Gauge}',
             [
                 with_trail(ValueLoadError('error at a', ...), ['a']),
-                NoRequiredFieldsError({'b'}, data),
+                NoRequiredFieldsLoadError({'b'}, data),
             ]
         ),
         lambda: loader(data),
@@ -1052,8 +1062,8 @@ def test_exception_collection(debug_ctx):
             f'while loading model {Gauge}',
             [
                 with_trail(ValueLoadError('error at a', ...), ['a']),
-                NoRequiredFieldsError({'b'}, data),
-                ExtraFieldsError({'c'}, data),
+                NoRequiredFieldsLoadError({'b'}, data),
+                ExtraFieldsLoadError({'c'}, data),
             ]
         ),
         lambda: loader(data),
@@ -1159,9 +1169,34 @@ def test_empty_list(debug_ctx, debug_trail, extra_policy, trail_select, strict_c
         elif extra_policy == ExtraForbid():
             raises_exc(
                 trail_select(
-                    disable=ExtraItemsError(0, 'abc'),
-                    first=ExtraItemsError(0, 'abc'),
-                    all=AggregateLoadError(f'while loading model {Gauge}', [ExtraItemsError(0, 'abc')]),
+                    disable=ExtraItemsLoadError(0, 'abc'),
+                    first=ExtraItemsLoadError(0, 'abc'),
+                    all=AggregateLoadError(f'while loading model {Gauge}', [ExtraItemsLoadError(0, 'abc')]),
                 ),
                 lambda: loader('abc'),
             )
+
+
+def test_skipped_pos_optional_pos_field(debug_ctx, extra_policy):
+    loader_getter = make_loader_getter(
+        shape=shape(
+            TestField('a', ParamKind.POS_OR_KW, is_required=True),
+            TestField('b', ParamKind.POS_OR_KW, is_required=False, default=DefaultValue(10)),
+            TestField('c', ParamKind.POS_OR_KW, is_required=False, default=DefaultValue(20)),
+        ),
+        name_layout=InputNameLayout(
+            crown=InpDictCrown(
+                {
+                    'a': InpFieldCrown('a'),
+                    'c': InpFieldCrown('c'),
+                },
+                extra_policy=ExtraForbid(),
+            ),
+            extra_move=None,
+        ),
+        debug_trail=DebugTrail.ALL,
+        debug_ctx=debug_ctx,
+    )
+    loader = loader_getter()
+
+    assert loader({'a': 1, 'c': 3}) == gauge(1, c=3)
diff --git a/tests/unit/morphing/name_layout/test_provider.py b/tests/unit/morphing/name_layout/test_provider.py
index ef50d6e4..45588c41 100644
--- a/tests/unit/morphing/name_layout/test_provider.py
+++ b/tests/unit/morphing/name_layout/test_provider.py
@@ -62,9 +62,9 @@
 )
 from adaptix._internal.morphing.name_layout.provider import BuiltinNameLayoutProvider
 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, TypeHintLoc
-from adaptix._internal.provider.request_filtering import AnyRequestChecker
+from adaptix._internal.provider.request_cls import LocMap, LocStack, TypeHintLoc
 from adaptix._internal.provider.shape_provider import InputShapeRequest, OutputShapeRequest
 
 
@@ -175,23 +175,23 @@ def make_layouts(
     retort.get_loader(Stub)
     retort.get_dumper(Stub)
     inp_request = InputNameLayoutRequest(
-        loc_map=loc_map,
+        loc_stack=LocStack(loc_map),
         shape=input_shape,
     )
     out_request = OutputNameLayoutRequest(
-        loc_map=loc_map,
+        loc_stack=LocStack(loc_map),
         shape=output_shape,
     )
     inp_name_layout = retort.provide(inp_request)
     out_name_layout = retort.provide(out_request)
     retort.provide(
         LoaderRequest(
-            loc_map=loc_map,
+            loc_stack=LocStack(loc_map),
         )
     )
     retort.provide(
         DumperRequest(
-            loc_map=loc_map,
+            loc_stack=LocStack(loc_map),
         )
     )
     return Layouts(inp_name_layout, out_name_layout)
@@ -200,7 +200,7 @@ def make_layouts(
 DEFAULT_NAME_MAPPING = name_mapping(
     chain=None,
     skip=(),
-    only=AnyRequestChecker(),
+    only=P.ANY,
     map={},
     trim_trailing_underscore=True,
     name_style=None,
@@ -325,7 +325,7 @@ def test_name_filtering():
     assert_flat_name_mapping(
         name_mapping(
             skip=['a', 'xxx'],
-            only=AnyRequestChecker(),
+            only=P.ANY,
             map={},
         ),
         {
@@ -349,7 +349,7 @@ def test_name_filtering():
     assert_flat_name_mapping(
         name_mapping(
             skip=['b'],
-            only=AnyRequestChecker(),
+            only=P.ANY,
             map={},
         ),
         {
diff --git a/tests/unit/morphing/test_concrete_provider.py b/tests/unit/morphing/test_concrete_provider.py
index 413435b2..bbd3fb31 100644
--- a/tests/unit/morphing/test_concrete_provider.py
+++ b/tests/unit/morphing/test_concrete_provider.py
@@ -11,7 +11,7 @@
 from adaptix import Retort
 from adaptix._internal.feature_requirement import HAS_PY_311, IS_PYPY
 from adaptix._internal.morphing.concrete_provider import DatetimeFormatProvider
-from adaptix.load_error import DatetimeFormatMismatch, TypeLoadError, ValueLoadError
+from adaptix.load_error import FormatMismatchLoadError, TypeLoadError, ValueLoadError
 
 
 def check_any_dt(loader):
@@ -119,7 +119,7 @@ def test_datetime_format_provider(strict_coercion, debug_trail):
     check_any_dt(loader)
 
     raises_exc(
-        DatetimeFormatMismatch("%Y-%m-%d", "some string"),
+        FormatMismatchLoadError("%Y-%m-%d", "some string"),
         lambda: loader("some string")
     )
 
diff --git a/tests/unit/morphing/test_constant_length_tuple_provider.py b/tests/unit/morphing/test_constant_length_tuple_provider.py
index ada2d226..52e44e88 100644
--- a/tests/unit/morphing/test_constant_length_tuple_provider.py
+++ b/tests/unit/morphing/test_constant_length_tuple_provider.py
@@ -12,7 +12,7 @@
 from adaptix._internal.morphing.concrete_provider import INT_LOADER_PROVIDER, STR_LOADER_PROVIDER
 from adaptix._internal.morphing.constant_length_tuple_provider import ConstantLengthTupleProvider
 from adaptix._internal.morphing.load_error import AggregateLoadError
-from adaptix.load_error import ExcludedTypeLoadError, ExtraItemsError, NoRequiredItemsError, TypeLoadError
+from adaptix.load_error import ExcludedTypeLoadError, ExtraItemsLoadError, NoRequiredItemsLoadError, TypeLoadError
 
 
 def string_dumper(data):
@@ -227,11 +227,11 @@ def test_loading_not_enough_fields(retort):
 
     loader_ = retort.get_loader(Tuple[int, int])
     raises_exc(
-        ExtraItemsError(2, (1, 2, 3)),
+        ExtraItemsLoadError(2, (1, 2, 3)),
         lambda: loader_([1, 2, 3])
     )
     raises_exc(
-        NoRequiredItemsError(2, (1,)),
+        NoRequiredItemsLoadError(2, (1,)),
         lambda: loader_([1])
     )
 
@@ -245,11 +245,11 @@ def test_dumping_not_enough_fields(retort):
 
     dumper_ = retort.get_dumper(Tuple[int, int])
     raises_exc(
-        ExtraItemsError(2, [1, 2, 3]),
+        ExtraItemsLoadError(2, [1, 2, 3]),
         lambda: dumper_([1, 2, 3])
     )
     raises_exc(
-        NoRequiredItemsError(2, [1]),
+        NoRequiredItemsLoadError(2, [1]),
         lambda: dumper_([1])
     )
 
diff --git a/tests/unit/morphing/test_dict_provider.py b/tests/unit/morphing/test_dict_provider.py
index 8670feba..7ed1de8a 100644
--- a/tests/unit/morphing/test_dict_provider.py
+++ b/tests/unit/morphing/test_dict_provider.py
@@ -1,13 +1,15 @@
 import collections.abc
-from typing import Dict
+from collections import defaultdict
+from typing import DefaultDict, Dict, List
 
 import pytest
 from tests_helpers import TestRetort, raises_exc, with_trail
 
-from adaptix import DebugTrail, dumper, loader
+from adaptix import DebugTrail, default_dict, dumper, loader
 from adaptix._internal.compat import CompatExceptionGroup
 from adaptix._internal.morphing.concrete_provider import STR_LOADER_PROVIDER
-from adaptix._internal.morphing.dict_provider import DictProvider
+from adaptix._internal.morphing.dict_provider import DefaultDictProvider, DictProvider
+from adaptix._internal.morphing.iterable_provider import IterableProvider
 from adaptix._internal.morphing.load_error import AggregateLoadError
 from adaptix._internal.struct_trail import ItemKey
 from adaptix.load_error import TypeLoadError
@@ -24,8 +26,10 @@ def retort():
     return TestRetort(
         recipe=[
             DictProvider(),
+            DefaultDictProvider(),
             STR_LOADER_PROVIDER,
             dumper(str, string_dumper),
+            IterableProvider()
         ]
     )
 
@@ -190,3 +194,39 @@ def test_dumping(retort, debug_trail):
             ),
             lambda: dumper_({'a': 'b', 0: 'd'}),
         )
+
+
+def test_defaultdict_loading(retort, strict_coercion, debug_trail):
+    loader_ = retort.replace(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    ).get_loader(
+        DefaultDict[str, str],
+    )
+
+    assert loader_({'a': 'b', 'c': 'd'}) == defaultdict(None, {'a': 'b', 'c': 'd'})
+
+
+def test_defaultdict_loader(retort, strict_coercion, debug_trail):
+    df = list
+    loader_ = retort.replace(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    ).extend(
+        recipe=[
+            default_dict(defaultdict, default_factory=df),
+        ]
+    ).get_loader(DefaultDict[str, List[str]])
+
+    assert loader_({'a': ['b', 'c']}) == defaultdict(list, {'a': ['b', 'c']})
+    assert loader_({'a': ['b', 'c']}).default_factory == df
+
+
+def test_defaultdict_dumping(retort, debug_trail):
+    dumper_ = retort.replace(
+        debug_trail=debug_trail,
+    ).get_dumper(
+        DefaultDict[str, str],
+    )
+
+    assert dumper_(defaultdict(None, {'a': 'b', 'c': 'd'})) == {'a': 'b', 'c': 'd'}
diff --git a/tests/unit/morphing/test_enum_provider.py b/tests/unit/morphing/test_enum_provider.py
index 5422c344..49778875 100644
--- a/tests/unit/morphing/test_enum_provider.py
+++ b/tests/unit/morphing/test_enum_provider.py
@@ -1,11 +1,29 @@
-from enum import Enum, IntEnum
+from enum import Enum, Flag, IntEnum, auto
+from typing import Iterable, Mapping, Union
 
 import pytest
-from tests_helpers import TestRetort, raises_exc
-
-from adaptix import dumper, enum_by_value, loader
-from adaptix._internal.morphing.enum_provider import EnumExactValueProvider, EnumNameProvider
-from adaptix.load_error import BadVariantError, MsgError
+from tests_helpers import TestRetort, parametrize_bool, raises_exc, with_cause, with_notes
+
+from adaptix import (
+    CannotProvide,
+    NameStyle,
+    NoSuitableProvider,
+    Retort,
+    dumper,
+    enum_by_name,
+    enum_by_value,
+    flag_by_member_names,
+    loader,
+)
+from adaptix._internal.morphing.enum_provider import EnumExactValueProvider
+from adaptix._internal.morphing.load_error import (
+    DuplicatedValuesLoadError,
+    ExcludedTypeLoadError,
+    MultipleBadVariantLoadError,
+    OutOfRangeLoadError,
+    TypeLoadError,
+)
+from adaptix.load_error import BadVariantLoadError, MsgLoadError
 
 
 class MyEnum(Enum):
@@ -24,31 +42,47 @@ def _missing_(cls, value: object) -> 'MyEnumWithMissingHook':
         raise ValueError
 
 
+class FlagEnum(Flag):
+    CASE_ONE = auto()
+    CASE_TWO = auto()
+    CASE_THREE = CASE_ONE | CASE_TWO
+    CASE_FOUR = auto()
+    CASE_EIGHT = auto()
+
+
+class FlagEnumWithSkippedBit(Flag):
+    CASE_ONE = 1
+    CASE_FOUR = 4
+
+
+class FlagEnumWithNegativeValue(Flag):
+    CASE_ONE = -1
+
+
 def test_name_provider(strict_coercion, debug_trail):
-    retort = TestRetort(
+    retort = Retort(
         strict_coercion=strict_coercion,
         debug_trail=debug_trail,
         recipe=[
-            EnumNameProvider(),
+            enum_by_name()
         ],
     )
 
     loader = retort.get_loader(MyEnum)
-
     assert loader("V1") == MyEnum.V1
 
     raises_exc(
-        BadVariantError(['V1'], '1'),
+        BadVariantLoadError(['V1'], '1'),
         lambda: loader("1")
     )
 
     raises_exc(
-        BadVariantError(['V1'], 1),
+        BadVariantLoadError(['V1'], 1),
         lambda: loader(1)
     )
 
     raises_exc(
-        BadVariantError(['V1'], MyEnum.V1),
+        BadVariantLoadError(['V1'], MyEnum.V1),
         lambda: loader(MyEnum.V1)
     )
 
@@ -57,6 +91,28 @@ def test_name_provider(strict_coercion, debug_trail):
     assert dumper(MyEnum.V1) == "V1"
 
 
+@pytest.mark.parametrize("mapping_options", [{"name_style": NameStyle.CAMEL}, {"map": {"V1": "v1"}}])
+def test_name_provider_with_mapping(strict_coercion, debug_trail, mapping_options):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+        recipe=[
+            enum_by_name(**mapping_options)
+        ]
+    )
+
+    loader = retort.get_loader(MyEnum)
+    assert loader("v1") == MyEnum.V1
+
+    raises_exc(
+        BadVariantLoadError(["v1"], "V1"),
+        lambda: loader("V1")
+    )
+
+    dumper = retort.get_dumper(MyEnum)
+    assert dumper(MyEnum.V1) == "v1"
+
+
 @pytest.mark.parametrize('enum_cls', [MyEnum, MyEnumWithMissingHook])
 def test_exact_value_provider(strict_coercion, debug_trail, enum_cls):
     retort = TestRetort(
@@ -72,17 +128,17 @@ def test_exact_value_provider(strict_coercion, debug_trail, enum_cls):
     assert loader("1") == enum_cls.V1
 
     raises_exc(
-        BadVariantError(['1'], 'V1'),
+        BadVariantLoadError(['1'], 'V1'),
         lambda: loader("V1")
     )
 
     raises_exc(
-        BadVariantError(['1'], 1),
+        BadVariantLoadError(['1'], 1),
         lambda: loader(1)
     )
 
     raises_exc(
-        BadVariantError(['1'], enum_cls.V1),
+        BadVariantLoadError(['1'], enum_cls.V1),
         lambda: loader(enum_cls.V1)
     )
 
@@ -104,12 +160,12 @@ def test_exact_value_provider_int_enum(strict_coercion, debug_trail):
     assert int_enum_loader(1) == MyIntEnum.V1
 
     raises_exc(
-        BadVariantError([1], MyEnum.V1),
+        BadVariantLoadError([1], MyEnum.V1),
         lambda: int_enum_loader(MyEnum.V1),
     )
 
     raises_exc(
-        BadVariantError([1], 'V1'),
+        BadVariantLoadError([1], 'V1'),
         lambda: int_enum_loader("V1")
     )
 
@@ -140,15 +196,243 @@ def test_value_provider(strict_coercion, debug_trail):
     assert enum_loader(1) == MyEnum.V1
 
     raises_exc(
-        MsgError('Bad enum value', "V1"),
+        MsgLoadError('Bad enum value', "V1"),
         lambda: enum_loader("V1")
     )
 
     raises_exc(
-        MsgError('Bad enum value', MyEnum.V1),
+        MsgLoadError('Bad enum value', MyEnum.V1),
         lambda: enum_loader(MyEnum.V1)
     )
 
     enum_dumper = retort.get_dumper(MyEnum)
 
     assert enum_dumper(MyEnum.V1) == "PREFIX 1"
+
+
+def test_flag_by_exact_value(strict_coercion, debug_trail):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    )
+
+    loader = retort.get_loader(FlagEnum)
+    assert loader(1) == FlagEnum.CASE_ONE
+    assert loader(3) == FlagEnum.CASE_THREE
+    assert loader(5) == FlagEnum.CASE_ONE | FlagEnum.CASE_FOUR
+
+    dumper = retort.get_dumper(FlagEnum)
+    assert dumper(FlagEnum.CASE_ONE) == 1
+    assert dumper(FlagEnum.CASE_ONE | FlagEnum.CASE_FOUR) == 5
+
+    raises_exc(
+        OutOfRangeLoadError(0, 15, 16), lambda: loader(16)
+    )
+    raises_exc(
+        OutOfRangeLoadError(0, 15, -1), lambda: loader(-1)
+    )
+
+
+@pytest.mark.parametrize('data', [{"data": 1}, "1", [1], None])
+def test_flag_by_exact_value_loader_with_bad_types(strict_coercion, debug_trail, data):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    )
+    loader = retort.get_loader(FlagEnum)
+    raises_exc(TypeLoadError(int, data), lambda: loader(data))
+
+
+def test_flag_by_exact_value_loader_creation_fail(strict_coercion, debug_trail):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+    )
+
+    raises_exc(
+        with_cause(
+            NoSuitableProvider(f'Cannot produce loader for type {FlagEnumWithSkippedBit}'),
+            with_notes(
+                with_notes(
+                    CannotProvide(
+                        'Cannot create a loader for flag with skipped bits',
+                        is_terminal=True,
+                        is_demonstrative=True,
+                    ),
+                    f"Location: type={FlagEnumWithSkippedBit}",
+                ),
+            )
+        ),
+        lambda: retort.get_loader(FlagEnumWithSkippedBit)
+    )
+    raises_exc(
+        with_cause(
+            NoSuitableProvider(f'Cannot produce loader for type {FlagEnumWithNegativeValue}'),
+            with_notes(
+                with_notes(
+                    CannotProvide(
+                        'Cannot create a loader for flag with negative values',
+                        is_terminal=True,
+                        is_demonstrative=True,
+                    ),
+                    f"Location: type={FlagEnumWithNegativeValue}",
+                ),
+            )
+        ),
+        lambda: retort.get_loader(FlagEnumWithNegativeValue)
+    )
+
+
+@parametrize_bool("allow_single_value", "allow_duplicates", "allow_compound")
+def test_flag_by_member_names(
+    strict_coercion, debug_trail, allow_single_value, allow_duplicates, allow_compound
+):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+        recipe=[
+            flag_by_member_names(
+                allow_single_value=allow_single_value,
+                allow_duplicates=allow_duplicates,
+                allow_compound=allow_compound
+            )
+        ]
+    )
+
+    loader = retort.get_loader(FlagEnum)
+    assert loader(["CASE_ONE"]) == FlagEnum.CASE_ONE
+    assert loader(["CASE_ONE", "CASE_TWO"]) == FlagEnum.CASE_THREE
+    assert loader(["CASE_ONE", "CASE_FOUR"]) == FlagEnum.CASE_ONE | FlagEnum.CASE_FOUR
+
+    if allow_compound:
+        variants = ["CASE_ONE", "CASE_TWO", "CASE_THREE", "CASE_FOUR", "CASE_EIGHT"]
+    else:
+        variants = ["CASE_ONE", "CASE_TWO", "CASE_FOUR", "CASE_EIGHT"]
+
+    raises_exc(
+        MultipleBadVariantLoadError(
+            allowed_values=variants,
+            invalid_values=["NOT_EXISTING_CASE_1", "NOT_EXISTING_CASE_2"],
+            input_value=["CASE_ONE", "NOT_EXISTING_CASE_1", "NOT_EXISTING_CASE_2"],
+        ),
+        lambda: loader(["CASE_ONE", "NOT_EXISTING_CASE_1", "NOT_EXISTING_CASE_2"])
+    )
+
+    data_with_compound = ["CASE_THREE"]
+    if allow_compound:
+        assert loader(data_with_compound) == FlagEnum.CASE_THREE
+    else:
+        raises_exc(
+            MultipleBadVariantLoadError(variants, ["CASE_THREE"], data_with_compound),
+            lambda: loader(data_with_compound)
+        )
+
+    data_with_duplicates = ["CASE_ONE", "CASE_ONE"]
+    if allow_duplicates:
+        assert loader(data_with_duplicates) == FlagEnum.CASE_ONE
+    else:
+        raises_exc(
+            DuplicatedValuesLoadError(data_with_duplicates),
+            lambda: loader(data_with_duplicates)
+        )
+
+    dumper = retort.get_dumper(FlagEnum)
+    assert dumper(FlagEnum.CASE_ONE) == ["CASE_ONE"]
+    assert dumper(FlagEnum.CASE_ONE & FlagEnum.CASE_TWO) == []
+    assert dumper(~FlagEnum.CASE_TWO) == ["CASE_ONE", "CASE_FOUR", "CASE_EIGHT"]
+
+    compound = FlagEnum.CASE_THREE
+    compound_with_non_compound = FlagEnum.CASE_FOUR | FlagEnum.CASE_THREE
+    if allow_compound:
+        assert dumper(compound) == ["CASE_THREE"]
+        assert dumper(compound_with_non_compound) == ["CASE_THREE", "CASE_FOUR"]
+    else:
+        assert dumper(compound) == ["CASE_ONE", "CASE_TWO"]
+        assert dumper(compound_with_non_compound) == ["CASE_ONE", "CASE_TWO", "CASE_FOUR"]
+
+
+@parametrize_bool("allow_single_value", "allow_duplicates", "allow_compound")
+def test_flag_by_member_names_with_bad_types(
+    strict_coercion, debug_trail, allow_single_value, allow_duplicates, allow_compound
+):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+        recipe=[
+            flag_by_member_names(
+                allow_single_value=allow_single_value,
+                allow_duplicates=allow_duplicates,
+                allow_compound=allow_compound
+            )
+        ]
+    )
+
+    loader = retort.get_loader(FlagEnum)
+    expected_type = Union[str, Iterable[str]] if allow_single_value else Iterable[str]
+
+    dict_data = {"CASE_ONE": 1}
+    if strict_coercion:
+        raises_exc(
+            ExcludedTypeLoadError(expected_type, Mapping, dict_data),
+            lambda: loader(dict_data)
+        )
+    else:
+        assert loader(dict_data) == FlagEnum.CASE_ONE
+
+    str_data = "CASE_ONE"
+    if allow_single_value:
+        assert loader(str_data) == loader([str_data])
+    else:
+        raises_exc(
+            TypeLoadError(expected_type, str_data),
+            lambda: loader(str_data)
+        )
+
+    raises_exc(
+        TypeLoadError(expected_type, 1),
+        lambda: loader(1)
+    )
+    raises_exc(
+        TypeLoadError(expected_type, None),
+        lambda: loader(None)
+    )
+
+
+def test_flag_by_member_names_with_mapping(strict_coercion, debug_trail):
+    retort = Retort(
+        strict_coercion=strict_coercion,
+        debug_trail=debug_trail,
+        recipe=[
+            flag_by_member_names(
+                name_style=NameStyle.CAMEL,
+                map={
+                    "CASE_ONE": "CASE_1",
+                    #  maps given by cases have higher priority that
+                    #  ones given by names.
+                    FlagEnum.CASE_ONE: "caseFirst",
+                    "CASE_TWO": "caseSecond",
+                    "ignore": "ignore"
+                }
+            ),
+        ]
+    )
+
+    loader = retort.get_loader(FlagEnum)
+    assert loader(["caseFirst"]) == FlagEnum.CASE_ONE
+    assert loader(["caseFirst", "caseSecond"]) == FlagEnum.CASE_THREE
+    assert loader(["caseThree"]) == FlagEnum.CASE_THREE
+
+    variants = ["caseFirst", "caseSecond", "caseThree", "caseFour", "caseEight"]
+    raises_exc(
+        MultipleBadVariantLoadError(
+            allowed_values=variants,
+            input_value=["caseThree", "CASE_TWO", "CASE_1"],
+            invalid_values=["CASE_TWO", "CASE_1"]
+        ),
+        lambda: loader(["caseThree", "CASE_TWO", "CASE_1"])
+    )
+
+    dumper = retort.get_dumper(FlagEnum)
+    assert dumper(FlagEnum.CASE_ONE) == ["caseFirst"]
+    assert dumper(FlagEnum.CASE_TWO) == ["caseSecond"]
+    assert dumper(FlagEnum.CASE_ONE | FlagEnum.CASE_TWO) == ["caseThree"]
diff --git a/tests/unit/provider/test_loc_stack_filtering.py b/tests/unit/provider/test_loc_stack_filtering.py
new file mode 100644
index 00000000..5d4fec99
--- /dev/null
+++ b/tests/unit/provider/test_loc_stack_filtering.py
@@ -0,0 +1,394 @@
+import collections.abc
+import typing
+from contextlib import nullcontext
+from dataclasses import dataclass
+from typing import Any, Dict, Generic, Iterable, List, Optional, Type, TypeVar, Union, overload
+
+import pytest
+from tests_helpers import cond_list, full_match_regex_str
+
+from adaptix import Chain, P, Retort, loader
+from adaptix._internal.common import TypeHint
+from adaptix._internal.feature_requirement import HAS_ANNOTATED
+from adaptix._internal.model_tools.definitions import NoDefault
+from adaptix._internal.provider.loc_stack_filtering import (
+    ExactOriginLSC,
+    ExactTypeLSC,
+    LocStackEndChecker,
+    OriginSubclassLSC,
+    create_loc_stack_checker,
+)
+from adaptix._internal.provider.request_cls import FieldLoc, GenericParamLoc, LocMap, LocStack, TypeHintLoc
+from adaptix._internal.type_tools import normalize_type
+
+
+@overload
+def param_result(*values: Any, id: Optional[str] = None, result: Any):
+    ...
+
+
+@overload
+def param_result(*values: Any, id: Optional[str] = None, raises: Type[Exception]):
+    ...
+
+
+@overload
+def param_result(*values: Any, id: Optional[str] = None, raises: Type[Exception], exact_match: str):
+    ...
+
+
+@overload
+def param_result(*values: Any, id: Optional[str] = None, raises: Type[Exception], match: str):
+    ...
+
+
+def param_result(*values, result=None, raises=None, exact_match=None, match=None, id=None):
+    if raises is not None:
+        context = pytest.raises(
+            raises, match=full_match_regex_str(exact_match) if exact_match is not None else match
+        )
+    else:
+        context = None
+    if id is None:
+        if len(values) == 1:
+            id = str(values[0])
+        else:
+            id = str(values)
+    return pytest.param(*values, result, context, id=id)
+
+
+def create_mediator():
+    return Retort()._create_mediator()
+
+
+@pytest.mark.parametrize(
+    ['loc_stack', 'result', 'context'],
+    [
+        param_result(
+            LocStack(
+                LocMap(TypeHintLoc(int)),
+            ),
+            result=False,
+            id='1-item',
+        ),
+        param_result(
+            LocStack(
+                LocMap(TypeHintLoc(int)),
+                LocMap(TypeHintLoc(str)),
+            ),
+            result=False,
+            id='2-items',
+        ),
+        param_result(
+            LocStack(
+                LocMap(TypeHintLoc(int)),
+                LocMap(TypeHintLoc(str)),
+                LocMap(TypeHintLoc(bool)),
+            ),
+            result=True,
+            id='ok',
+        ),
+        param_result(
+            LocStack(
+                LocMap(TypeHintLoc(int)),
+                LocMap(TypeHintLoc(str)),
+                LocMap(TypeHintLoc(str)),
+            ),
+            result=False,
+            id='last-is-bad',
+        ),
+        param_result(
+            LocStack(
+                LocMap(TypeHintLoc(int)),
+                LocMap(TypeHintLoc(int)),
+                LocMap(TypeHintLoc(str)),
+                LocMap(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)),
+            ),
+            result=True,
+            id='extra-stack',
+        ),
+    ],
+)
+def test_stack_end_rc(loc_stack, result, context):
+    checker = LocStackEndChecker(
+        [
+            create_loc_stack_checker(int),
+            create_loc_stack_checker(str),
+            create_loc_stack_checker(bool),
+        ]
+    )
+    assert checker.check_loc_stack(create_mediator(), loc_stack) == result
+
+
+@pytest.mark.parametrize(
+    ['loc_stack', 'result', 'context'],
+    [
+        param_result(
+            LocStack(
+                LocMap(TypeHintLoc(bool)),
+                LocMap(TypeHintLoc(int)),
+                LocMap(TypeHintLoc(str)),
+                LocMap(TypeHintLoc(bool)),
+            ),
+            result=True,
+            id='ok',
+        ),
+        param_result(
+            LocStack(
+                LocMap(TypeHintLoc(int)),
+                LocMap(TypeHintLoc(str)),
+                LocMap(TypeHintLoc(bool)),
+            ),
+            result=False,
+            id='too-small',
+        ),
+        param_result(
+            LocStack(
+                LocMap(TypeHintLoc(str)),
+                LocMap(TypeHintLoc(int)),
+                LocMap(TypeHintLoc(str)),
+                LocMap(TypeHintLoc(bool)),
+            ),
+            result=False,
+            id='bad',
+        ),
+    ],
+)
+def test_nested_start_rc(loc_stack, result, context):
+    checker = LocStackEndChecker(
+        [
+            P.ANY,
+            LocStackEndChecker(
+                [
+                    create_loc_stack_checker(bool),
+                    create_loc_stack_checker(int),
+                    create_loc_stack_checker(str),
+                ]
+            ),
+            create_loc_stack_checker(bool),
+        ]
+    )
+    assert checker.check_loc_stack(create_mediator(), loc_stack) == result
+
+
+@dataclass
+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={},
+        )
+    )
+
+
+@pytest.mark.parametrize(
+    ['pattern', 'loc_stack', 'result', 'context'],
+    [
+        param_result(
+            P[int],
+            LocStack(LocMap(TypeHintLoc(int))),
+            result=True,
+            id='int-ok',
+        ),
+        param_result(
+            P[int],
+            LocStack(LocMap(TypeHintLoc(str))),
+            result=False,
+            id='int-fail',
+        ),
+        param_result(
+            P[WithUserName].user_name,
+            LocStack(
+                LocMap(TypeHintLoc(WithUserName)),
+                field_loc_map('user_name', str),
+            ),
+            result=True,
+            id='model-attr-ok-1',
+        ),
+        param_result(
+            P[WithUserName].user_name,
+            LocStack(
+                LocMap(TypeHintLoc(WithUserName)),
+                field_loc_map('user_name', int),
+            ),
+            result=True,
+            id='model-attr-ok-2',
+        ),
+        param_result(
+            P[WithUserName].user_id,
+            LocStack(
+                LocMap(TypeHintLoc(WithUserName)),
+                field_loc_map('user_name', int),
+            ),
+            result=False,
+            id='model-attr-fail',
+        ),
+        param_result(
+            P[WithUserName] + P.user_name,
+            LocStack(
+                LocMap(TypeHintLoc(WithUserName)),
+                field_loc_map('user_name', int),
+            ),
+            result=True,
+            id='model-attr-ok-concat',
+        ),
+        param_result(
+            P[Dict].generic_arg(0, str),
+            LocStack(
+                LocMap(TypeHintLoc(Dict)),
+                LocMap(TypeHintLoc(str), GenericParamLoc(0)),
+            ),
+            result=True,
+            id='generic-arg-ok',
+        ),
+        param_result(
+            P[Dict].generic_arg(0, str),
+            LocStack(
+                LocMap(TypeHintLoc(Dict)),
+                LocMap(TypeHintLoc(str), GenericParamLoc(1)),
+            ),
+            result=False,
+            id='generic-arg-fail',
+        ),
+    ],
+)
+def test_request_pattern(pattern, loc_stack, result, context):
+    assert pattern.build_loc_stack_checker().check_loc_stack(create_mediator(), loc_stack) == result
+
+
+def test_generic_pattern_create_fail():
+    with pytest.raises(
+        TypeError,
+        match=full_match_regex_str(
+            'Can not use LocStackPattern as predicate inside LocStackPattern.'
+            ' If you want to combine several LocStackPattern, you can use `+` operator'
+        )
+    ):
+        P[WithUserName][P.user_name]
+
+
+def plus_one(data):
+    return data + 1
+
+
+def plus_two(data):
+    return data + 2
+
+
+def test_request_pattern_generic_arg_dict_override():
+    retort = Retort(
+        recipe=[
+            loader(P[Dict].generic_arg(0, int), plus_one, Chain.LAST),
+            loader(P[Dict].generic_arg(1, int), plus_two, Chain.LAST),
+        ]
+    )
+
+    loaded_dict = retort.load({10: 20}, Dict[int, int])
+    assert loaded_dict == {11: 22}
+
+
+T = TypeVar('T')
+
+
+class MyGeneric(Generic[T]):
+    pass
+
+
+@pytest.mark.parametrize(
+    ['value', 'result', 'context'],
+    [
+        param_result(
+            int,
+            result=ExactOriginLSC(int),
+        ),
+        param_result(
+            List,
+            result=ExactOriginLSC(list),
+        ),
+        param_result(
+            dict,
+            result=ExactOriginLSC(dict),
+        ),
+        param_result(
+            Iterable,
+            result=OriginSubclassLSC(collections.abc.Iterable),
+        ),
+        param_result(
+            Iterable[int],
+            result=ExactTypeLSC(normalize_type(Iterable[int])),
+        ),
+        param_result(
+            List[int],
+            result=ExactTypeLSC(normalize_type(List[int])),
+        ),
+        param_result(
+            List[T],
+            raises=ValueError,
+            exact_match='Can not create LocStackChecker from typing.List[~T] generic alias (parametrized generic)',
+        ),
+        param_result(
+            Union,
+            result=ExactOriginLSC(Union)
+        ),
+        *cond_list(
+            HAS_ANNOTATED,
+            lambda: [
+                param_result(
+                    typing.Annotated,
+                    result=ExactOriginLSC(typing.Annotated)
+                ),
+                param_result(
+                    typing.Annotated[int, 'meta'],
+                    result=ExactTypeLSC(normalize_type(typing.Annotated[int, 'meta']))
+                ),
+                param_result(
+                    typing.Annotated[List[int], 'meta'],
+                    result=ExactTypeLSC(normalize_type(typing.Annotated[list[int], 'meta']))
+                ),
+                param_result(
+                    typing.Annotated[list, 'meta'],
+                    raises=ValueError,
+                    exact_match=(
+                        "Can not create LocStackChecker from"
+                        " typing.Annotated[list, 'meta'] generic alias (parametrized generic)"
+                    ),
+                ),
+                param_result(
+                    typing.Annotated[List[T], 'meta'],
+                    raises=ValueError,
+                    exact_match=(
+                        "Can not create LocStackChecker from"
+                        " typing.Annotated[typing.List[~T], 'meta'] generic alias (parametrized generic)"
+                    ),
+                ),
+                param_result(
+                    typing.Annotated[Dict[int, T], 'meta'],
+                    raises=ValueError,
+                    exact_match=(
+                        "Can not create LocStackChecker from"
+                        " typing.Annotated[typing.Dict[int, ~T], 'meta'] generic alias (parametrized generic)"
+                    ),
+                ),
+            ]
+        ),
+    ],
+)
+def test_create_request_checker(value, result, context):
+    with context or nullcontext():
+        assert create_loc_stack_checker(value) == result
diff --git a/tests/unit/provider/test_overlay_schema.py b/tests/unit/provider/test_overlay_schema.py
index 0a52cbae..72051acd 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, TypeHintLoc
+from adaptix._internal.provider.request_cls import LocMap, LocStack, TypeHintLoc
 from adaptix._internal.provider.static_provider import StaticProvider, static_provision_action
 
 
@@ -64,7 +64,7 @@ def test_simple():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocMap())
+        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap()))
     ) == MySchema(
         number=1,
         char_list=('a', 'b'),
@@ -93,7 +93,7 @@ def test_chaining():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocMap())
+        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap()))
     ) == MySchema(
         number=1,
         char_list=('a', 'b', 'c', 'd'),
@@ -120,7 +120,7 @@ def test_chaining():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocMap())
+        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap()))
     ) == MySchema(
         number=2,
         char_list=('c', 'd', 'a', 'b'),
@@ -147,7 +147,7 @@ def test_chaining():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocMap())
+        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap()))
     ) == MySchema(
         number=2,
         char_list=('a', 'b', 'c', 'd'),
@@ -174,7 +174,7 @@ def test_chaining():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocMap())
+        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap()))
     ) == MySchema(
         number=1,
         char_list=('c', 'd'),
@@ -201,7 +201,7 @@ def test_chaining():
                 chain=None,
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocMap())
+        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap()))
     ) == MySchema(
         number=2,
         char_list=('c', 'd'),
@@ -244,7 +244,7 @@ def test_typehint_location():
                 ),
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocMap(TypeHintLoc(type=MyClass2)))
+        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap(TypeHintLoc(type=MyClass2))))
     ) == MySchema(
         number=1,
         char_list=('a', 'b', 'c', 'd'),
@@ -289,7 +289,7 @@ def test_typehint_location():
                 ),
             ),
         ],
-        provide_action=lambda m: provide_schema(MyOverlay, m, LocMap(TypeHintLoc(type=MyClass2)))
+        provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap(TypeHintLoc(type=MyClass2))))
     ) == MySchema(
         number=1,
         char_list=('a', 'b', 'c', 'd', 'e', 'f'),
@@ -322,5 +322,5 @@ def test_omitted_fields():
                     chain=None,
                 ),
             ],
-            provide_action=lambda m: provide_schema(MyOverlay, m, LocMap())
+            provide_action=lambda m: provide_schema(MyOverlay, m, LocStack(LocMap()))
         )
diff --git a/tests/unit/provider/test_request_filtering.py b/tests/unit/provider/test_request_filtering.py
deleted file mode 100644
index 65a6e7f6..00000000
--- a/tests/unit/provider/test_request_filtering.py
+++ /dev/null
@@ -1,401 +0,0 @@
-import collections.abc
-import typing
-from contextlib import nullcontext
-from dataclasses import dataclass
-from typing import Any, Dict, Generic, Iterable, List, Type, TypeVar, Union, overload
-
-import pytest
-from tests_helpers import cond_list, full_match_regex_str
-
-from adaptix import CannotProvide, Chain, P, Request, Retort, loader
-from adaptix._internal.common import TypeHint
-from adaptix._internal.feature_requirement import HAS_ANNOTATED
-from adaptix._internal.model_tools.definitions import NoDefault
-from adaptix._internal.provider.request_cls import FieldLoc, GenericParamLoc, LocatedRequest, LocMap, TypeHintLoc
-from adaptix._internal.provider.request_filtering import (
-    AnyRequestChecker,
-    ExactOriginRC,
-    ExactTypeRC,
-    OriginSubclassRC,
-    RequestPattern,
-    StackEndRC,
-    create_request_checker,
-)
-from adaptix._internal.retort.mediator import BuiltinMediator, RawRecipeSearcher, RecursionResolving
-from adaptix._internal.retort.operating_retort import BuiltinErrorRepresentor
-from adaptix._internal.type_tools import normalize_type
-
-
-def create_mediator(*elements: Request[Any]):
-    return BuiltinMediator(
-        searcher=RawRecipeSearcher([]),
-        recursion_resolving=RecursionResolving(),
-        request_stack=elements,
-        error_representor=BuiltinErrorRepresentor(),
-    )
-
-
-def test_stack_end_rc():
-    checker = StackEndRC(
-        [
-            create_request_checker(int),
-            create_request_checker(str),
-            create_request_checker(bool),
-        ]
-    )
-
-    with pytest.raises(CannotProvide, match="Request stack is too small"):
-        checker.check_request(
-            create_mediator(
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-            ),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-        )
-
-    with pytest.raises(CannotProvide, match="Request stack is too small"):
-        checker.check_request(
-            create_mediator(
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-            ),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-        )
-
-    with pytest.raises(CannotProvide):
-        checker.check_request(
-            create_mediator(
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-            ),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-        )
-
-    checker.check_request(
-        create_mediator(
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-        ),
-        LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-    )
-    checker.check_request(
-        create_mediator(
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-        ),
-        LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-    )
-    checker.check_request(
-        create_mediator(
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-        ),
-        LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-    )
-
-
-def test_nested_start_rc():
-    checker = StackEndRC(
-        [
-            AnyRequestChecker(),
-            StackEndRC(
-                [
-                    create_request_checker(bool),
-                    create_request_checker(int),
-                    create_request_checker(str),
-                ]
-            ),
-            create_request_checker(bool),
-        ]
-    )
-
-    checker.check_request(
-        create_mediator(
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-        ),
-        LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-    )
-
-    with pytest.raises(CannotProvide, match="Request stack is too small"):
-        checker.check_request(
-            create_mediator(
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-            ),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-        )
-
-    with pytest.raises(CannotProvide):
-        checker.check_request(
-            create_mediator(
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-                LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-            ),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(bool))),
-        )
-
-
-def check_request_pattern(
-    request_pattern: RequestPattern,
-    request_or_stack: Union[Request, List[Request]],
-    *,
-    fail: bool,
-) -> None:
-    if isinstance(request_or_stack, list):
-        request = request_or_stack[-1]
-        stack = request_or_stack
-    else:
-        request = request_or_stack
-        stack = []
-
-    with pytest.raises(CannotProvide) if fail else nullcontext():
-        request_pattern.build_request_checker().check_request(
-            create_mediator(*stack),
-            request,
-        )
-
-
-@dataclass
-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={},
-            owner_type=WithUserName,
-        )
-    )
-
-
-def test_request_pattern():
-    check_request_pattern(
-        P[int],
-        LocatedRequest(loc_map=LocMap(TypeHintLoc(int))),
-        fail=False,
-    )
-    check_request_pattern(
-        P[int],
-        LocatedRequest(loc_map=LocMap(TypeHintLoc(str))),
-        fail=True,
-    )
-
-    check_request_pattern(
-        P[WithUserName].user_name,
-        [
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(WithUserName))),
-            LocatedRequest(loc_map=field_loc_map('user_name', str)),
-        ],
-        fail=False,
-    )
-    check_request_pattern(
-        P[WithUserName].user_name,
-        [
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(WithUserName))),
-            LocatedRequest(loc_map=field_loc_map('user_name', int)),
-        ],
-        fail=False,
-    )
-    check_request_pattern(
-        P[WithUserName].user_id,
-        [
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(WithUserName))),
-            LocatedRequest(loc_map=field_loc_map('user_name', int)),
-        ],
-        fail=True,
-    )
-
-    with pytest.raises(
-        TypeError,
-        match=full_match_regex_str(
-            'Can not use RequestPattern as predicate inside RequestPattern.'
-            ' If you want to combine several RequestPattern, you can use `+` operator'
-        )
-    ):
-        P[WithUserName][P.user_name]
-
-    check_request_pattern(
-        P[WithUserName] + P.user_name,
-        [
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(WithUserName))),
-            LocatedRequest(loc_map=field_loc_map('user_name', int)),
-        ],
-        fail=False,
-    )
-
-
-def test_request_pattern_generic_arg():
-    check_request_pattern(
-        P[Dict].generic_arg(0, str),
-        [
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(Dict))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(str), GenericParamLoc(0))),
-        ],
-        fail=False,
-    )
-    check_request_pattern(
-        P[Dict].generic_arg(0, str),
-        [
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(Dict))),
-            LocatedRequest(loc_map=LocMap(TypeHintLoc(str), GenericParamLoc(1))),
-        ],
-        fail=True,
-    )
-
-
-def plus_one(data):
-    return data + 1
-
-
-def plus_two(data):
-    return data + 2
-
-
-def test_request_pattern_generic_arg_dict_override():
-    retort = Retort(
-        recipe=[
-            loader(P[Dict].generic_arg(0, int), plus_one, Chain.LAST),
-            loader(P[Dict].generic_arg(1, int), plus_two, Chain.LAST),
-        ]
-    )
-
-    loaded_dict = retort.load({10: 20}, Dict[int, int])
-    assert loaded_dict == {11: 22}
-
-
-T = TypeVar('T')
-
-
-@overload
-def param_result(value: Any, *, result: Any):
-    ...
-
-
-@overload
-def param_result(value: Any, *, raises: Type[Exception]):
-    ...
-
-
-@overload
-def param_result(value: Any, *, raises: Type[Exception], exact_match: str):
-    ...
-
-
-@overload
-def param_result(value: Any, *, raises: Type[Exception], match: str):
-    ...
-
-
-def param_result(value, *, result=None, raises=None, exact_match=None, match=None):
-    if raises is not None:
-        context = pytest.raises(
-            raises, match=full_match_regex_str(exact_match) if exact_match is not None else match
-        )
-    else:
-        context = None
-    return pytest.param(value, result, context, id=str(value))
-
-
-class MyGeneric(Generic[T]):
-    pass
-
-
-@pytest.mark.parametrize(
-    ['value', 'result', 'context'],
-    [
-        param_result(
-            int,
-            result=ExactOriginRC(int),
-        ),
-        param_result(
-            List,
-            result=ExactOriginRC(list),
-        ),
-        param_result(
-            dict,
-            result=ExactOriginRC(dict),
-        ),
-        param_result(
-            Iterable,
-            result=OriginSubclassRC(collections.abc.Iterable),
-        ),
-        param_result(
-            Iterable[int],
-            result=ExactTypeRC(normalize_type(Iterable[int])),
-        ),
-        param_result(
-            List[int],
-            result=ExactTypeRC(normalize_type(List[int])),
-        ),
-        param_result(
-            List[T],
-            raises=ValueError,
-            exact_match='Can not create RequestChecker from typing.List[~T] generic alias (parametrized generic)',
-        ),
-        param_result(
-            Union,
-            result=ExactOriginRC(Union)
-        ),
-    ] + cond_list(
-        HAS_ANNOTATED,
-        lambda: [
-            param_result(
-                typing.Annotated,
-                result=ExactOriginRC(typing.Annotated)
-            ),
-            param_result(
-                typing.Annotated[int, 'meta'],
-                result=ExactTypeRC(normalize_type(typing.Annotated[int, 'meta']))
-            ),
-            param_result(
-                typing.Annotated[List[int], 'meta'],
-                result=ExactTypeRC(normalize_type(typing.Annotated[list[int], 'meta']))
-            ),
-            param_result(
-                typing.Annotated[list, 'meta'],
-                raises=ValueError,
-                exact_match=(
-                    "Can not create RequestChecker from"
-                    " typing.Annotated[list, 'meta'] generic alias (parametrized generic)"
-                ),
-            ),
-            param_result(
-                typing.Annotated[List[T], 'meta'],
-                raises=ValueError,
-                exact_match=(
-                    "Can not create RequestChecker from"
-                    " typing.Annotated[typing.List[~T], 'meta'] generic alias (parametrized generic)"
-                ),
-            ),
-            param_result(
-                typing.Annotated[Dict[int, T], 'meta'],
-                raises=ValueError,
-                exact_match=(
-                    "Can not create RequestChecker from"
-                    " typing.Annotated[typing.Dict[int, ~T], 'meta'] generic alias (parametrized generic)"
-                ),
-            ),
-        ]
-    ),
-)
-def test_create_request_checker(value, result, context):
-    if context is None:
-        assert create_request_checker(value) == result
-    else:
-        with context:
-            create_request_checker(value)
diff --git a/tests/unit/retort/test_mediator.py b/tests/unit/retort/test_mediator.py
deleted file mode 100644
index 500a87b5..00000000
--- a/tests/unit/retort/test_mediator.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from dataclasses import dataclass
-from itertools import permutations
-from typing import Optional, Sequence, Type
-
-import pytest
-
-from adaptix import CannotProvide, Mediator, Provider, Request
-from adaptix._internal.retort.mediator import BuiltinMediator, RawRecipeSearcher, RecursionResolving
-from adaptix._internal.retort.operating_retort import BuiltinErrorRepresentor
-
-
-@dataclass(frozen=True)
-class SampleRequest1(Request[None]):
-    pass
-
-
-@dataclass(frozen=True)
-class SampleRequest2(Request[None]):
-    pass
-
-
-@dataclass(frozen=True)
-class SampleRequest3(Request[None]):
-    pass
-
-
-@dataclass
-class StackAsserter(Provider):
-    request_type: Type[Request]
-    expected_stack: Sequence[Request]
-    send_next: Optional[Request]
-
-    def apply_provider(self, mediator: Mediator, request: Request):
-        if not isinstance(request, self.request_type):
-            raise CannotProvide
-
-        assert list(self.expected_stack) == list(mediator.request_stack)
-
-        if self.send_next is not None:
-            mediator.delegating_provide(self.send_next)
-
-
-ASSERTER_LIST = [
-    StackAsserter(SampleRequest1, [SampleRequest1()], send_next=SampleRequest2()),
-    StackAsserter(SampleRequest2, [SampleRequest1(), SampleRequest2()], send_next=SampleRequest3()),
-    StackAsserter(SampleRequest3, [SampleRequest1(), SampleRequest2(), SampleRequest3()], send_next=None),
-]
-
-
-@pytest.mark.parametrize(
-    'order',
-    [
-        pytest.param(order, id=str(order))
-        for order in permutations([1, 2, 3])
-    ],
-)
-def test_request_stack(order):
-    searcher = RawRecipeSearcher(recipe=[ASSERTER_LIST[value - 1] for value in order])
-
-    mediator = BuiltinMediator(
-        searcher=searcher,
-        recursion_resolving=RecursionResolving(),
-        request_stack=[],
-        error_representor=BuiltinErrorRepresentor(),
-    )
-
-    mediator.delegating_provide(
-        SampleRequest1(),
-    )
diff --git a/tests/unit/retort/test_operating_retort.py b/tests/unit/retort/test_operating_retort.py
new file mode 100644
index 00000000..c427ef49
--- /dev/null
+++ b/tests/unit/retort/test_operating_retort.py
@@ -0,0 +1,220 @@
+from dataclasses import dataclass
+
+from tests_helpers import raises_exc, with_cause, with_notes
+
+from adaptix import AggregateCannotProvide, CannotProvide, NoSuitableProvider, Retort
+from adaptix.conversion import get_converter
+
+
+def test_cannot_produce_loader():
+    retort = Retort()
+
+    @dataclass
+    class Stub:
+        f1: int
+        f2: memoryview
+        f3: memoryview
+
+    raises_exc(
+        with_cause(
+            NoSuitableProvider(f'Cannot produce loader for type {Stub}'),
+            with_notes(
+                AggregateCannotProvide(
+                    'Cannot create loader for model. Loaders for some fields cannot be created',
+                    [
+                        with_notes(
+                            CannotProvide(
+                                'There is no provider that can create specified 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",
+                        ),
+                        with_notes(
+                            CannotProvide(
+                                'There is no provider that can create specified 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",
+                        ),
+                    ],
+                    is_terminal=True,
+                    is_demonstrative=True,
+                ),
+                f"Location: type={Stub}",
+            )
+        ),
+        lambda: retort.get_loader(Stub),
+    )
+
+
+def test_cannot_produce_dumper():
+    retort = Retort()
+
+    @dataclass
+    class Stub:
+        f1: int
+        f2: memoryview
+        f3: memoryview
+
+    raises_exc(
+        with_cause(
+            NoSuitableProvider(f'Cannot produce dumper for type {Stub}'),
+            with_notes(
+                AggregateCannotProvide(
+                    'Cannot create dumper for model. Dumpers for some fields cannot be created',
+                    [
+                        with_notes(
+                            CannotProvide(
+                                'There is no provider that can create specified 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)",
+                        ),
+                        with_notes(
+                            CannotProvide(
+                                'There is no provider that can create specified 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)",
+                        ),
+                    ],
+                    is_terminal=True,
+                    is_demonstrative=True,
+                ),
+                f"Location: type={Stub}",
+            )
+        ),
+        lambda: retort.get_dumper(Stub),
+    )
+
+
+def test_cannot_produce_converter_no_linking_required():
+    @dataclass
+    class Book:
+        title: str
+        price: int
+
+    @dataclass
+    class BookDTO:
+        title: str
+        price: int
+        author: str
+
+    raises_exc(
+        with_cause(
+            NoSuitableProvider(
+                f'Cannot produce converter for'
+                f' <Signature (src: {Book.__module__}.{Book.__qualname__}, /)'
+                f' -> {BookDTO.__module__}.{BookDTO.__qualname__}>'
+            ),
+            AggregateCannotProvide(
+                'Linkings for some fields are not found',
+                [
+                    with_notes(
+                        CannotProvide(
+                            f'Cannot find paired field of `{BookDTO.__qualname__}.author` for linking',
+                            is_terminal=False,
+                            is_demonstrative=True,
+                        ),
+                        'Note: This is a required filed, so it must take value',
+                    ),
+                ],
+                is_terminal=True,
+                is_demonstrative=True,
+            ),
+        ),
+        lambda: get_converter(Book, BookDTO),
+    )
+
+
+def test_cannot_produce_converter_no_linking_optional():
+    @dataclass
+    class Book:
+        title: str
+        price: int
+
+    @dataclass
+    class BookDTO:
+        title: str
+        price: int
+        author: str = ''
+
+    raises_exc(
+        with_cause(
+            NoSuitableProvider(
+                f'Cannot produce converter for'
+                f' <Signature (src: {Book.__module__}.{Book.__qualname__}, /)'
+                f' -> {BookDTO.__module__}.{BookDTO.__qualname__}>'
+            ),
+            AggregateCannotProvide(
+                'Linkings for some fields are not found',
+                [
+                    with_notes(
+                        CannotProvide(
+                            f'Cannot find paired field of `{BookDTO.__qualname__}.author` 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,
+            ),
+        ),
+        lambda: get_converter(Book, BookDTO),
+    )
+
+
+def test_cannot_produce_converter_no_coercer():
+    @dataclass
+    class Book:
+        title: str
+        price: int
+        author: int
+
+    @dataclass
+    class BookDTO:
+        title: str
+        price: int
+        author: str
+
+    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__}.author -> {BookDTO.__qualname__}.author`',
+                        is_terminal=False,
+                        is_demonstrative=True,
+                    ),
+                ],
+                is_terminal=True,
+                is_demonstrative=True,
+            ),
+        ),
+        lambda: get_converter(Book, BookDTO),
+    )
diff --git a/tests/unit/test_name_style.py b/tests/unit/test_name_style.py
index ab753c2b..725c8010 100644
--- a/tests/unit/test_name_style.py
+++ b/tests/unit/test_name_style.py
@@ -1,47 +1,48 @@
 import pytest
 
 from adaptix import NameStyle
-from adaptix._internal.name_style import convert_snake_style, is_snake_case
+from adaptix._internal.name_style import convert_snake_style, is_snake_style
 
 
-def test_is_snake_case():
-    assert is_snake_case('a')
-    assert is_snake_case('a_b')
-    assert is_snake_case('a_')
-    assert is_snake_case('_a')
-    assert is_snake_case('a_b_')
-    assert is_snake_case('_a_')
-    assert is_snake_case('_a_b')
-    assert is_snake_case('_a_b_')
+def test_is_snake_style():
+    assert is_snake_style('a')
+    assert is_snake_style('a_b')
+    assert is_snake_style('a_')
+    assert is_snake_style('_a')
+    assert is_snake_style('a_b_')
+    assert is_snake_style('_a_')
+    assert is_snake_style('_a_b')
+    assert is_snake_style('_a_b_')
 
-    assert is_snake_case('1')
-    assert is_snake_case('1_2')
-    assert is_snake_case('1_')
-    assert is_snake_case('_1')
-    assert is_snake_case('1_2_')
-    assert is_snake_case('_1_')
-    assert is_snake_case('_1_2')
-    assert is_snake_case('_1_2_')
+    assert is_snake_style('1')
+    assert is_snake_style('1_2')
+    assert is_snake_style('1_')
+    assert is_snake_style('_1')
+    assert is_snake_style('1_2_')
+    assert is_snake_style('_1_')
+    assert is_snake_style('_1_2')
+    assert is_snake_style('_1_2_')
 
-    assert is_snake_case('a_1')
-    assert is_snake_case('a_1_')
+    assert is_snake_style('a_1')
+    assert is_snake_style('a_1_')
 
-    assert is_snake_case('a__b')
-    assert is_snake_case('a__b_')
+    assert is_snake_style('a__b')
+    assert is_snake_style('a__b_')
 
-    assert is_snake_case('_')
-    assert is_snake_case('___')
+    assert is_snake_style('_')
+    assert is_snake_style('___')
 
-    assert not is_snake_case('A_1_')
-    assert not is_snake_case('Aa_1_')
+    assert is_snake_style('A_1_')
+    assert is_snake_style('Aa_1_')
 
-    assert not is_snake_case('123%')
-    assert not is_snake_case('_123%')
+    assert not is_snake_style('123%')
+    assert not is_snake_style('_123%')
 
 
 def check_conversion(style, maps):
     for src, trg in maps.items():
         assert convert_snake_style(src, style) == trg
+        assert convert_snake_style(src.upper(), style) == trg
 
 
 def test_snake_case_conversion():
@@ -146,6 +147,6 @@ def test_snake_case_conversion_fail():
             convert_snake_style('___', style)
 
     for style in NameStyle:
-        for name in ['AbcXyz', 'abcXyz', 'abcxyz?']:
+        for name in ['abcxyz?']:
             with pytest.raises(ValueError):
                 convert_snake_style(name, style)