New Upstream Snapshot - ruby-dry-types

Ready changes

Summary

Merged new upstream version: 1.7.1 (was: 1.5.1).

Diff

diff --git a/.devtools/templates/changelog.erb b/.devtools/templates/changelog.erb
index 6ed7fdf..941112e 100644
--- a/.devtools/templates/changelog.erb
+++ b/.devtools/templates/changelog.erb
@@ -32,7 +32,7 @@
 - <%= log %>
 <% end %>
 <% end %>
-<% curr_ver = r.date ? "v#{r.version}" : 'master' %>
+<% curr_ver = r.date ? "v#{r.version}" : 'main' %>
 <% prev_rel = releases[idx + 1] %>
 <% if prev_rel %>
 <% ver_range = "v#{prev_rel.version}...#{curr_ver}" %>
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index 7cb51d4..0000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1 +0,0 @@
-github: [solnic, timriley]
diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
deleted file mode 100644
index 41e6b12..0000000
--- a/.github/ISSUE_TEMPLATE/bug-report.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-name: "\U0001F41B Bug report"
-about: See CONTRIBUTING.md for more information
-title: ''
-labels: bug, help wanted
-assignees: ''
-
----
-
-## Describe the bug
-
-A clear and concise description of what the bug is.
-
-## To Reproduce
-
-Provide detailed steps to reproduce, **an executable script would be best**.
-
-## Expected behavior
-
-A clear and concise description of what you expected to happen.
-
-## My environment
-
-- Affects my production application: **YES/NO**
-- Ruby version: ...
-- OS: ...
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index 0e02877..0000000
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-blank_issues_enabled: false
-contact_links:
-  - name: Community Support
-    url: https://discourse.dry-rb.org
-    about: Please ask and answer questions here.
diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md
deleted file mode 100644
index 44533b5..0000000
--- a/.github/SUPPORT.md
+++ /dev/null
@@ -1,3 +0,0 @@
-## Support
-
-If you need help with any of the dry-rb libraries, feel free to ask questions on our [discussion forum](https://discourse.dry-rb.org/). This is the best place to seek help. Make sure to search for a potential solution in past threads before posting your question. Thanks! :heart:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 6ea7463..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,83 +0,0 @@
----
-name: ci
-'on':
-  push:
-    paths:
-    - ".github/workflows/ci.yml"
-    - lib/**
-    - "*.gemspec"
-    - spec/**
-    - Rakefile
-    - Gemfile
-    - Gemfile.devtools
-    - ".rubocop.yml"
-    - project.yml
-  pull_request:
-    branches:
-    - master
-  create: 
-jobs:
-  tests:
-    runs-on: ubuntu-latest
-    strategy:
-      fail-fast: false
-      matrix:
-        ruby:
-        - '3.0'
-        - '2.7'
-        - '2.6'
-        - '2.5'
-        - jruby
-        include:
-        - ruby: '3.0'
-          coverage: 'true'
-        - ruby: '2.7'
-          dry_logic_from_master: 'true'
-    env:
-      COVERAGE: "${{matrix.coverage}}"
-      COVERAGE_TOKEN: "${{secrets.CODACY_PROJECT_TOKEN}}"
-      DRY_LOGIC_FROM_MASTER: "${{matrix.dry_logic_from_master}}"
-    steps:
-    - name: Checkout
-      uses: actions/checkout@v1
-    - name: Install package dependencies
-      run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS"
-    - name: Set up Ruby
-      uses: ruby/setup-ruby@v1
-      with:
-        ruby-version: "${{matrix.ruby}}"
-    - name: Install latest bundler
-      run: |
-        gem install bundler --no-document
-        bundle config set without 'tools benchmarks docs'
-    - name: Bundle install
-      run: bundle install --jobs 4 --retry 3
-    - name: Run all tests
-      run: bundle exec rake
-    - name: Run codacy-coverage-reporter
-      uses: codacy/codacy-coverage-reporter-action@master
-      if: env.COVERAGE == 'true' && env.COVERAGE_TOKEN != ''
-      with:
-        project-token: "${{secrets.CODACY_PROJECT_TOKEN}}"
-        coverage-reports: coverage/coverage.xml
-  release:
-    runs-on: ubuntu-latest
-    if: contains(github.ref, 'tags') && github.event_name == 'create'
-    needs: tests
-    env:
-      GITHUB_LOGIN: dry-bot
-      GITHUB_TOKEN: "${{secrets.GH_PAT}}"
-    steps:
-    - uses: actions/checkout@v1
-    - name: Install package dependencies
-      run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS"
-    - name: Set up Ruby
-      uses: ruby/setup-ruby@v1
-      with:
-        ruby-version: 2.6
-    - name: Install dependencies
-      run: gem install ossy --no-document
-    - name: Trigger release workflow
-      run: |
-        tag=$(echo $GITHUB_REF | cut -d / -f 3)
-        ossy gh w dry-rb/devtools release --payload "{\"tag\":\"$tag\",\"sha\":\"${{github.sha}}\",\"tag_creator\":\"$GITHUB_ACTOR\",\"repo\":\"$GITHUB_REPOSITORY\",\"repo_name\":\"${{github.event.repository.name}}\"}"
diff --git a/.github/workflows/custom/ci.yml b/.github/workflows/custom/ci.yml
deleted file mode 100644
index 13a4c4a..0000000
--- a/.github/workflows/custom/ci.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-jobs:
-  tests:
-    strategy:
-      matrix:
-        include:
-          - ruby: "2.7"
-            dry_logic_from_master: "true"
-    env:
-      DRY_LOGIC_FROM_MASTER: ${{matrix.dry_logic_from_master}}
diff --git a/.github/workflows/docsite.yml b/.github/workflows/docsite.yml
deleted file mode 100644
index 3b4ade8..0000000
--- a/.github/workflows/docsite.yml
+++ /dev/null
@@ -1,62 +0,0 @@
-# this file is managed by dry-rb/devtools project
-
-name: docsite
-
-on:
-  push:
-    paths:
-      - docsite/**
-      - .github/workflows/docsite.yml
-    branches:
-      - master
-      - release-**
-    tags:
-
-jobs:
-  update-docs:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-        with:
-          fetch-depth: 0
-      - run: |
-          git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
-      - name: Set up Ruby
-        uses: actions/setup-ruby@v1
-        with:
-          ruby-version: "2.6.x"
-      - name: Set up git user
-        run: |
-          git config --local user.email "dry-bot@dry-rb.org"
-          git config --local user.name "dry-bot"
-      - name: Install dependencies
-        run: gem install ossy --no-document
-      - name: Update release branches
-        run: |
-          branches=`git log --format=%B -n 1 $GITHUB_SHA | grep "docsite:release-" || echo "nothing"`
-
-          if [[ ! $branches -eq "nothing" ]]; then
-            for b in $branches
-            do
-              name=`echo $b | ruby -e 'puts gets[/:(.+)/, 1].gsub(/\s+/, "")'`
-
-              echo "merging $GITHUB_SHA to $name"
-
-              git checkout -b $name --track origin/$name
-
-              echo `git log -n 1`
-
-              git cherry-pick $GITHUB_SHA -m 1
-            done
-
-            git push --all "https://dry-bot:${{secrets.GH_PAT}}@github.com/$GITHUB_REPOSITORY.git"
-
-            git checkout master
-          else
-            echo "no need to update branches"
-          fi
-      - name: Trigger dry-rb.org deploy
-        env:
-          GITHUB_LOGIN: dry-bot
-          GITHUB_TOKEN: ${{secrets.GH_PAT}}
-        run: ossy github workflow dry-rb/dry-rb.org ci
diff --git a/.github/workflows/sync_configs.yml b/.github/workflows/sync_configs.yml
deleted file mode 100644
index 96da6e7..0000000
--- a/.github/workflows/sync_configs.yml
+++ /dev/null
@@ -1,53 +0,0 @@
-# this file is managed by dry-rb/devtools project
-
-name: sync
-
-on:
-  repository_dispatch:
-  push:
-    branches:
-      - "master"
-
-jobs:
-  main:
-    runs-on: ubuntu-latest
-    if: (github.event_name == 'repository_dispatch' && github.event.action == 'sync_configs') || github.event_name != 'repository_dispatch'
-    env:
-      GITHUB_LOGIN: dry-bot
-      GITHUB_TOKEN: ${{ secrets.GH_PAT }}
-    steps:
-      - name: Checkout ${{github.repository}}
-        uses: actions/checkout@v1
-      - name: Checkout devtools
-        uses: actions/checkout@v2
-        with:
-          repository: dry-rb/devtools
-          path: tmp/devtools
-      - name: Setup git user
-        run: |
-          git config --local user.email "dry-bot@dry-rb.org"
-          git config --local user.name "dry-bot"
-      - name: Set up Ruby
-        uses: ruby/setup-ruby@v1
-        with:
-          ruby-version: 2.6
-      - name: Install dependencies
-        run: gem install ossy --no-document
-      - name: Compile file templates
-        run: tmp/devtools/bin/compile-templates
-      - name: Update workflow files from devtools
-        run: tmp/devtools/bin/sync-workflows
-      - name: Update configuration files from devtools
-        run: tmp/devtools/bin/sync-shared-files
-      - name: Update changelog.yml from commit
-        run: tmp/devtools/bin/update-changelog-from-commit $GITHUB_SHA
-      - name: Compile CHANGELOG.md
-        run: tmp/devtools/bin/compile-changelog
-      - name: Commit
-        run: |
-          git add -A
-          git commit -m "[devtools] sync" || echo "nothing to commit"
-      - name: Push changes
-        run: |
-          git pull --rebase origin master
-          git push https://dry-bot:${{secrets.GH_PAT}}@github.com/${{github.repository}}.git HEAD:master
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 6b4af38..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,11 +0,0 @@
-/.bundle/
-/.yardoc
-/Gemfile.lock
-/_yardoc/
-/coverage/
-/doc/
-/pkg/
-/spec/reports/
-/tmp/
-log/
-.vscode
diff --git a/.repobot.yml b/.repobot.yml
new file mode 100644
index 0000000..9d36bd7
--- /dev/null
+++ b/.repobot.yml
@@ -0,0 +1,25 @@
+###########################################################
+# DO NOT EDIT THIS FILE
+#
+# This is a config synced from dry-rb/template-gem repo
+###########################################################
+
+sources:
+  - repo: dry-rb/template-gem
+    sync:
+      - "!.github/workflows/ci.yml.erb"
+      - ".repobot.yml.erb"
+      - ".devtools/templates/*.sync:${{dir}}/${{name}}"
+      - ".github/**/*.*"
+      - ".rspec"
+      - ".rubocop.yml"
+      - "gemspec.erb:dry-types.gemspec"
+      - "spec/support/*"
+      - "CODE_OF_CONDUCT.md"
+      - "CONTRIBUTING.md"
+      - "LICENSE.erb"
+      - "README.md.erb"
+      - "Gemfile.devtools"
+  - repo: repobot-app/workflows
+    sync:
+      - ".github/workflows/*.yml"
diff --git a/.rubocop.yml b/.rubocop.yml
index 4f50629..111de35 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,8 +1,10 @@
-# this file is managed by dry-rb/devtools project
+# This is a config synced from dry-rb/template-gem repo
 
 AllCops:
-  TargetRubyVersion: 2.7
+  TargetRubyVersion: 3.0
+  NewCops: disable
   Exclude:
+    - benchmarks/*.rb
     - spec/support/coverage.rb
     - spec/support/warnings.rb
     - spec/support/rspec_options.rb
@@ -30,11 +32,21 @@ Layout/SpaceInsideHashLiteralBraces:
 Layout/LineLength:
   Max: 100
   Exclude:
-    - "spec/**/*_spec.rb"
+    - "spec/**/*.rb"
+
+Lint/AmbiguousBlockAssociation:
+  Enabled: true
+  # because 'expect { foo }.to change { bar }' is fine
+  Exclude:
+    - "spec/**/*.rb"
 
 Lint/BooleanSymbol:
   Enabled: false
 
+Lint/ConstantDefinitionInBlock:
+  Exclude:
+    - "spec/**/*.rb"
+
 Lint/RaiseException:
   Enabled: false
 
@@ -45,6 +57,10 @@ Lint/SuppressedException:
   Exclude:
     - "spec/spec_helper.rb"
 
+Lint/LiteralAsCondition:
+  Exclude:
+    - "spec/**/*.rb"
+
 Naming/PredicateName:
   Enabled: false
 
@@ -55,6 +71,9 @@ Naming/FileName:
 Naming/MethodName:
   Enabled: false
 
+Naming/MethodParameterName:
+  Enabled: false
+
 Naming/MemoizedInstanceVariableName:
   Enabled: false
 
@@ -101,7 +120,7 @@ Style/BlockDelimiters:
 
 Style/ClassAndModuleChildren:
   Exclude:
-    - "spec/**/*_spec.rb"
+    - "spec/**/*.rb"
 
 Style/ConditionalAssignment:
   Enabled: false
@@ -118,6 +137,9 @@ Style/EachWithObject:
 Style/FormatString:
   Enabled: false
 
+Style/FormatStringToken:
+  Enabled: false
+
 Style/GuardClause:
   Enabled: false
 
@@ -133,6 +155,9 @@ Style/LambdaCall:
 Style/ParallelAssignment:
   Enabled: false
 
+Style/RaiseArgs:
+  Enabled: false
+
 Style/StabbyLambdaParentheses:
   Enabled: false
 
@@ -147,10 +172,94 @@ Style/StringLiteralsInInterpolation:
 
 Style/SymbolArray:
   Exclude:
-    - "spec/**/*_spec.rb"
+    - "spec/**/*.rb"
 
 Style/TrailingUnderscoreVariable:
   Enabled: false
 
 Style/MultipleComparison:
   Enabled: false
+
+Style/Next:
+  Enabled: false
+
+Style/AccessorGrouping:
+  Enabled: false
+
+Style/EmptyLiteral:
+  Enabled: false
+
+Style/Semicolon:
+  Exclude:
+    - "spec/**/*.rb"
+
+Style/HashAsLastArrayItem:
+  Exclude:
+    - "spec/**/*.rb"
+
+Style/CaseEquality:
+  Exclude:
+    - "lib/dry/monads/**/*.rb"
+    - "lib/dry/struct/**/*.rb"
+    - "lib/dry/types/**/*.rb"
+    - "spec/**/*.rb"
+
+Style/ExplicitBlockArgument:
+  Exclude:
+    - "lib/dry/types/**/*.rb"
+
+Style/CombinableLoops:
+  Enabled: false
+
+Style/EmptyElse:
+  Enabled: false
+
+Style/DoubleNegation:
+  Enabled: false
+
+Style/MultilineBlockChain:
+  Enabled: false
+
+Style/NumberedParametersLimit:
+  Max: 2
+
+Lint/UnusedBlockArgument:
+  Exclude:
+    - "spec/**/*.rb"
+
+Lint/Debugger:
+  Exclude:
+    - "bin/console"
+
+Lint/BinaryOperatorWithIdenticalOperands:
+  Exclude:
+    - "spec/**/*.rb"
+
+Metrics/ParameterLists:
+  Exclude:
+    - "spec/**/*.rb"
+
+Lint/EmptyBlock:
+  Exclude:
+    - "spec/**/*.rb"
+
+Lint/UselessMethodDefinition:
+  Exclude:
+    - "spec/**/*.rb"
+
+Lint/SelfAssignment:
+  Enabled: false
+
+Lint/EmptyClass:
+  Enabled: false
+
+Naming/ConstantName:
+  Exclude:
+    - "spec/**/*.rb"
+
+Naming/VariableNumber:
+  Exclude:
+    - "spec/**/*.rb"
+
+Naming/BinaryOperatorParameterName:
+  Enabled: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 494b47f..6be3551 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,45 @@
 <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
 
+## 1.7.1 2023-02-17
+
+
+### Fixed
+
+- Warning from jruby about overwritten keyword (@flash-gordon + @klobuczek in #454)
+
+
+[Compare v1.7.0...v1.7.1](https://github.com/dry-rb/dry-types/compare/v1.7.0...v1.7.1)
+
+## 1.7.0 2022-11-04
+
+
+### Changed
+
+- This version is compatible with recently released dry-rb dependencies (@flash-gordon)
+- Updated to dry-core 1.0 (@flash-gordon + @solnic)
+- Dependency on dry-container was dropped (@flash-gordon)
+
+[Compare v1.6.1...v1.7.0](https://github.com/dry-rb/dry-types/compare/v1.6.1...v1.7.0)
+
+## 1.6.1 2022-10-15
+
+
+### Changed
+
+- Fix issues with internal const_missing and Inflector/Module constants (@flash-gordon + @solnic)
+
+[Compare v1.6.0...v1.6.1](https://github.com/dry-rb/dry-types/compare/v1.6.0...v1.6.1)
+
+## 1.6.0 2022-10-15
+
+
+### Changed
+
+- Optimize `PredicateRegistry` for Ruby 2.7+ (see #420 for more details) (@casperisfine)
+- Use zeitwerk for auto-loading (@flash-gordon)
+
+[Compare v1.5.1...v1.6.0](https://github.com/dry-rb/dry-types/compare/v1.5.1...v1.6.0)
+
 ## 1.5.1 2021-02-16
 
 
diff --git a/CODEOWNERS b/CODEOWNERS
deleted file mode 100644
index 3c531ac..0000000
--- a/CODEOWNERS
+++ /dev/null
@@ -1 +0,0 @@
-* @solnic
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 20feae4..6eb38ce 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,13 +1,132 @@
-# Contributor Code of Conduct
+# Contributor Covenant Code of Conduct
 
-As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
+## Our Pledge
 
-We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
 
-Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
 
-Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
+## Our Standards
 
-Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
+Examples of behavior that contributes to a positive environment for our
+community include:
 
-This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.4.0, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct)
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+  and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the overall
+  community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or advances of
+  any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email address,
+  without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+team at dry-rb.org.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of
+actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or permanent
+ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the
+community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.1, available at
+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
+
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
+[https://www.contributor-covenant.org/translations][translations].
+
+[homepage]: https://www.contributor-covenant.org
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 80f24a3..c5368c0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,7 +10,7 @@ Report a feature request **only after discussing it first on [discourse.dry-rb.o
 
 ## Reporting questions, support requests, ideas, concerns etc.
 
-**PLEASE DON'T** - use [discourse.dry-rb.org](http://discourse.dry-rb.org) instead.
+**PLEASE DON'T** - use [discourse.dry-rb.org](https://discourse.dry-rb.org) instead.
 
 # Pull Request Guidelines
 
diff --git a/Gemfile b/Gemfile
index 4276aec..1e1bc2f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,16 +6,12 @@ eval_gemfile "Gemfile.devtools"
 
 gemspec
 
-gem "dry-logic", github: "dry-rb/dry-logic", branch: "master" if ENV["DRY_LOGIC_FROM_MASTER"].eql?("true")
+gem "dry-monads"
 
 group :test do
   gem "dry-struct"
 end
 
-group :tools do
-  gem "pry-byebug", platform: :mri
-end
-
 group :benchmarks do
   platform :mri do
     gem "attrio"
diff --git a/Gemfile.devtools b/Gemfile.devtools
index 69dc4f0..e719a95 100644
--- a/Gemfile.devtools
+++ b/Gemfile.devtools
@@ -1,18 +1,18 @@
 # frozen_string_literal: true
 
-# this file is managed by dry-rb/devtools project
+# This file is synced from dry-rb/template-gem repo
 
-git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
+gem "rake", ">= 12.3.3"
 
 group :test do
   gem "simplecov", require: false, platforms: :ruby
   gem "simplecov-cobertura", require: false, platforms: :ruby
   gem "rexml", require: false
 
-  gem "warning" if RUBY_VERSION >= "2.4.0"
+  gem "warning"
 end
 
 group :tools do
-  # this is the same version that we use on codacy
-  gem "rubocop", "0.82.0"
+  gem "rubocop", "~> 1.40.0"
+  gem "byebug"
 end
diff --git a/LICENSE b/LICENSE
index 0fd8d66..9f9f214 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2015-2021 dry-rb team
+Copyright (c) 2015-2023 dry-rb team
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
diff --git a/README.md b/README.md
index dc9549a..6bebd34 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,21 @@
+<!--- this file is synced from dry-rb/template-gem project -->
 [gem]: https://rubygems.org/gems/dry-types
 [actions]: https://github.com/dry-rb/dry-types/actions
-[codacy]: https://www.codacy.com/gh/dry-rb/dry-types
-[chat]: https://dry-rb.zulipchat.com
-[inchpages]: http://inch-ci.org/github/dry-rb/dry-types
 
-# dry-types [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
-
-[![Gem Version](https://badge.fury.io/rb/dry-types.svg)][gem]
-[![CI Status](https://github.com/dry-rb/dry-types/workflows/ci/badge.svg)][actions]
-[![Codacy Badge](https://api.codacy.com/project/badge/Grade/f2d71613195f4da993acb9ac9d6ea336)][codacy]
-[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/f2d71613195f4da993acb9ac9d6ea336)][codacy]
-[![Inline docs](http://inch-ci.org/github/dry-rb/dry-types.svg?branch=master)][inchpages]
+# dry-types [![Gem Version](https://badge.fury.io/rb/dry-types.svg)][gem] [![CI Status](https://github.com/dry-rb/dry-types/workflows/ci/badge.svg)][actions]
 
 ## Links
 
-* [User documentation](http://dry-rb.org/gems/dry-types)
+* [User documentation](https://dry-rb.org/gems/dry-types)
 * [API documentation](http://rubydoc.info/gems/dry-types)
+* [Forum](https://discourse.dry-rb.org)
 
 ## Supported Ruby versions
 
 This library officially supports the following Ruby versions:
 
-* MRI >= `2.5`
-* jruby >= `9.2`
+* MRI `>= 2.7.0`
+* jruby `>= 9.4` (not tested on CI)
 
 ## License
 
diff --git a/bin/console b/bin/console
index 36b19b4..050ba2c 100755
--- a/bin/console
+++ b/bin/console
@@ -7,7 +7,7 @@ require "dry/types"
 Dry::Types.load_extensions(:maybe)
 
 module Types
-  include Dry::Types()
+  include Dry.Types()
 end
 
 begin
diff --git a/changelog.yml b/changelog.yml
index 4399e8e..7f95cb9 100644
--- a/changelog.yml
+++ b/changelog.yml
@@ -1,4 +1,23 @@
 ---
+- version: 1.7.1
+  date: 2023-02-17
+  fixed:
+  - "Warning from jruby about overwritten keyword (@flash-gordon + @klobuczek in #454)"
+- version: 1.7.0
+  date: 2022-11-04
+  changed:
+  - "This version is compatible with recently released dry-rb dependencies (@flash-gordon)"
+  - "Updated to dry-core 1.0 (@flash-gordon + @solnic)"
+  - "Dependency on dry-container was dropped (@flash-gordon)"
+- version: 1.6.1
+  date: 2022-10-15
+  changed:
+  - "Fix issues with internal const_missing and Inflector/Module constants (@flash-gordon + @solnic)"
+- version: 1.6.0
+  date: 2022-10-15
+  changed:
+  - "Optimize `PredicateRegistry` for Ruby 2.7+ (see #420 for more details) (@casperisfine)"
+  - "Use zeitwerk for auto-loading (@flash-gordon)"
 - version: 1.5.1
   date: '2021-02-16'
   fixed:
diff --git a/debian/changelog b/debian/changelog
index 755e72d..ecfd88f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+ruby-dry-types (1.7.1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 25 Feb 2023 16:23:18 -0000
+
 ruby-dry-types (1.5.1-1) unstable; urgency=low
 
   * Team upload.
diff --git a/docsite/source/extensions/maybe.html.md b/docsite/source/extensions/maybe.html.md
index adb9f35..8591c86 100644
--- a/docsite/source/extensions/maybe.html.md
+++ b/docsite/source/extensions/maybe.html.md
@@ -4,54 +4,73 @@ layout: gem-single
 name: dry-types
 ---
 
-The [dry-monads gem](/gems/dry-monads/) provides approach to handling optional values by returning a [_Monad_](/gems/dry-monads/) object. This allows you to pass your type to a `Maybe(x)` block that only executes if `x` returns `Some` or `None`.
+The [dry-monads gem](/gems/dry-monads/) provides an approach to handling optional values by returning a [`Maybe`](/gems/dry-monads/) object from operations that can return `nil`.
 
-> NOTE: Requires the [dry-monads gem](/gems/dry-monads/) to be loaded.
-1. Load the `:maybe` extension in your application.
+`dry-types` has an extension that can return `Maybe`s from calls to types. That is, it wraps the return result in either:
+- a `Some` object (with the resulting value)
+- a `None` object (when the value would be `nil`)
+
+
+> **NOTE**: You must `require 'dry-monads'`, and include `Dry::Monads[:maybe]`
+
+1. Load `dry-monads` with `Maybe` and the `dry-types` `:maybe` extension in your application.
 
 ```ruby
+require 'dry-monads'
+include Dry::Monads[:maybe] # This should be inside your class
 require 'dry-types'
 
 Dry::Types.load_extensions(:maybe)
-module Types
-  include Dry.Types()
-end
+Types = Dry.Types()
 ```
 
-2. Append `.maybe` to a _Type_ to return a _Monad_ object  
+2. Append `.maybe` to a `Type` to return a `Maybe` object
 
 ```ruby
-x = Types::Maybe::Strict::Integer[nil]
-Maybe(x) { puts(x) }
-x = Types::Maybe::Coercible::String[nil]
-Maybe(x) { puts(x) }
-x = Types::Maybe::Strict::Integer[123]
-Maybe(x) { puts(x) }
-x = Types::Maybe::Strict::String[123]
-Maybe(x) { puts(x) }
+Types::Strict::Integer.maybe[nil]     # => None
+Types::Strict::Integer.maybe[123]     # => Some(123)
+
+Types::Coercible::String.maybe[nil]   # => None
+Types::Coercible::String.maybe[123]   # => Some("123")
+
+Types::Coercible::Float.maybe[nil]    # => None
+Types::Coercible::Float.maybe['12.3'] # => Some(12.3)
+
+Types::Strict::String.maybe[123]      # => raises Dry::Types::ConstraintError
+Types::Strict::Integer.maybe["foo"]   # => raises Dry::Types::ConstraintError
+
 ```
 
+If you want to capture the errors (e.g. to return messages) instead of raising them, you may want to use the [`:monads` extension](docs::extensions/monads) instead, which returns a `Result`.
+
+Or, if you prefer, instead of calling `.maybe` you can use the `Maybe::` namespaced types instead.
+
+The following examples are identical to the ones above:
+
 ```ruby
-Types::Maybe::Strict::Integer[nil] # None
-Types::Maybe::Strict::Integer[123] # Some(123)
-Types::Maybe::Coercible::Float[nil] # None
-Types::Maybe::Coercible::Float['12.3'] # Some(12.3)
-# 'Maybe' types can also accessed by calling '.maybe' on a regular type:
-Types::Strict::Integer.maybe # equivalent to Types::Maybe::Strict::Integer
+Types::Maybe::Strict::Integer[nil]     # => None
+Types::Maybe::Strict::Integer[123]     # => Some(123)
+
+Types::Maybe::Coercible::String[nil]   # => None
+Types::Maybe::Coercible::String[123]   # => Some("123")
+
+Types::Maybe::Coercible::Float[nil]    # => None
+Types::Maybe::Coercible::Float['12.3'] # => Some(12.3)
+
+Types::Maybe::Strict::String[123]      # => raises Dry::Types::ConstraintError
+Types::Maybe::Strict::Integer["foo"]   # => raises Dry::Types::ConstraintError
 ```
 
-You can define your own optional types:
+### Mapping methods on `Maybe`
+Since these are `dry-monads` `Maybe` objects, you can `#fmap` methods to them: applying the method to the value when the value is `Some` (and keeping the `None` when the value is `None`).
+
+You can `#fmap` these, and then use `#value_or` to return the underlying value out of a `Some` or return a default when the value is `None`.
 
 ``` ruby
 maybe_string = Types::Strict::String.maybe
-maybe_string[nil]
-# => None
-maybe_string[nil].fmap(&:upcase)
-# => None
-maybe_string['something']
-# => Some('something')
-maybe_string['something'].fmap(&:upcase)
-# => Some('SOMETHING')
-maybe_string['something'].fmap(&:upcase).value_or('NOTHING')
-# => "SOMETHING"
+maybe_string[nil]                 # => None
+maybe_string[nil].fmap(&:upcase)  # => None
+maybe_string['something']                                    # => Some('something')
+maybe_string['something'].fmap(&:upcase)                     # => Some('SOMETHING')
+maybe_string['something'].fmap(&:upcase).value_or('NOTHING') # => "SOMETHING"
 ```
diff --git a/docsite/source/extensions/monads.html.md b/docsite/source/extensions/monads.html.md
index 27774ca..f6d8092 100644
--- a/docsite/source/extensions/monads.html.md
+++ b/docsite/source/extensions/monads.html.md
@@ -4,40 +4,46 @@ layout: gem-single
 name: dry-types
 ---
 
-The monads extension makes `Dry::Types::Result` objects compatible with `dry-monads`.
+The `:monads` extension provides a `#to_monad` method that returns a `Result` compatible with [`dry-monads`](/gems/dry-monads/).
 
-To enable the extension:
+The `.try` method returns a simple `Result` that's defined within `dry-types` (i.e. `Dry::Types::Result`).
+If you want to use the result with `dry-monads`, you can load this extension and call `#to_monad` on the `Dry::Types::Result` to get a `Result` that's defined in `dry-monads` (i.e. `Dry::Monads::Result`). This will let you use the `dry-monads` methods on the result.
+
+To enable the `:monads` extension:
 
 ```ruby
 require 'dry/types'
 Dry::Types.load_extensions(:monads)
+Types = Dry.Types()
 ```
 
-After loading the extension, you can leverage monad API:
+After loading the extension, you can leverage the `.to_monad` method:
 
 ```ruby
-Types = Dry.Types()
-
 result = Types::String.try('Jane')
-result.class #=> Dry::Types::Result::Success
-monad = result.to_monad
-monad.class #=> Dry::Monads::Result::Success
-monad.value!  # => 'Jane'
+result.class            # => Dry::Types::Result::Success
+monad = result.to_monad # => Success("Jane")
+monad.class             # => Dry::Monads::Result::Success
+monad.value!            # => 'Jane'
+
 result = Types::String.try(nil)
-result.class #=> Dry::Types::Result::Failure
-monad = result.to_monad
-monad.class #=> Dry::Monads::Result::Failure
-monad.failure  # => [#<Dry::Types::ConstraintError>, nil]
-Types::String.try(nil)
-  .to_monad
+result.class            # => Dry::Types::Result::Failure
+monad = result.to_monad # => Failure([...])
+monad.class             # => Dry::Monads::Result::Failure
+monad.failure           # => [#<Dry::Types::ConstraintError: ...>, nil]
+monad
   .fmap { |result| puts "passed: #{result.inspect}" }
   .or   { |error, input| puts "input '#{input.inspect}' failed with error: #{error.to_s}" }
 ```
 
-This can be useful when used with `dry-monads` and the [`do` notation](/gems/dry-monads/1.0/do-notation/):
+Note that you must use the `.try` method and not the `.[]` method, since that skips the intermediate `Result` object and just returns the value. If you want to use the `.[]` method and also have errors be raised rather than captured in `Failure`, then you can consider using the [`:maybe` extension](docs::extensions/maybe) instead.
+
+## `dry-monads` Do notation
+This can be useful with [`do` notation](/gems/dry-monads/1.3/do-notation/) in `dry-monads`.
 
 ```ruby
 require 'dry/types'
+require 'dry/monads'
 Types = Dry.Types()
 Dry::Types.load_extensions(:monads)
 
diff --git a/dry-types.gemspec b/dry-types.gemspec
index 6a6cd8d..319c8be 100644
--- a/dry-types.gemspec
+++ b/dry-types.gemspec
@@ -1,41 +1,41 @@
 # frozen_string_literal: true
-# this file is managed by dry-rb/devtools project
 
-lib = File.expand_path('lib', __dir__)
+# this file is synced from dry-rb/template-gem project
+
+lib = File.expand_path("lib", __dir__)
 $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
-require 'dry/types/version'
+require "dry/types/version"
 
 Gem::Specification.new do |spec|
-  spec.name          = 'dry-types'
+  spec.name          = "dry-types"
   spec.authors       = ["Piotr Solnica"]
   spec.email         = ["piotr.solnica@gmail.com"]
-  spec.license       = 'MIT'
+  spec.license       = "MIT"
   spec.version       = Dry::Types::VERSION.dup
 
   spec.summary       = "Type system for Ruby supporting coercions, constraints and complex types like structs, value objects, enums etc"
   spec.description   = spec.summary
-  spec.homepage      = 'https://dry-rb.org/gems/dry-types'
+  spec.homepage      = "https://dry-rb.org/gems/dry-types"
   spec.files         = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-types.gemspec", "lib/**/*"]
-  spec.bindir        = 'bin'
+  spec.bindir        = "bin"
   spec.executables   = []
-  spec.require_paths = ['lib']
+  spec.require_paths = ["lib"]
 
-  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
-  spec.metadata['changelog_uri']     = 'https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md'
-  spec.metadata['source_code_uri']   = 'https://github.com/dry-rb/dry-types'
-  spec.metadata['bug_tracker_uri']   = 'https://github.com/dry-rb/dry-types/issues'
+  spec.metadata["allowed_push_host"] = "https://rubygems.org"
+  spec.metadata["changelog_uri"]     = "https://github.com/dry-rb/dry-types/blob/main/CHANGELOG.md"
+  spec.metadata["source_code_uri"]   = "https://github.com/dry-rb/dry-types"
+  spec.metadata["bug_tracker_uri"]   = "https://github.com/dry-rb/dry-types/issues"
 
-  spec.required_ruby_version = ">= 2.5.0"
+  spec.required_ruby_version = ">= 2.7.0"
 
   # to update dependencies edit project.yml
   spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
-  spec.add_runtime_dependency "dry-container", "~> 0.3"
-  spec.add_runtime_dependency "dry-core", "~> 0.5", ">= 0.5"
-  spec.add_runtime_dependency "dry-inflector", "~> 0.1", ">= 0.1.2"
-  spec.add_runtime_dependency "dry-logic", "~> 1.0", ">= 1.0.2"
+  spec.add_runtime_dependency "dry-core", "~> 1.0"
+  spec.add_runtime_dependency "dry-inflector", "~> 1.0"
+  spec.add_runtime_dependency "dry-logic", "~> 1.4"
+  spec.add_runtime_dependency "zeitwerk", "~> 2.6"
 
   spec.add_development_dependency "bundler"
-  spec.add_development_dependency "dry-monads", "~> 1.0"
   spec.add_development_dependency "rake"
   spec.add_development_dependency "rspec"
   spec.add_development_dependency "yard"
diff --git a/lib/dry/types.rb b/lib/dry/types.rb
index e1d8e98..9462d3b 100644
--- a/lib/dry/types.rb
+++ b/lib/dry/types.rb
@@ -3,37 +3,64 @@
 require "bigdecimal"
 require "date"
 require "set"
+require "zeitwerk"
 
 require "concurrent/map"
 
-require "dry/container"
-require "dry/core/extensions"
-require "dry/core/constants"
-require "dry/core/class_attributes"
+require "dry/core"
+require "dry/logic"
 
+require "dry/types/constraints"
+require "dry/types/errors"
 require "dry/types/version"
-require "dry/types/container"
+
+# This must be required explicitly as it may conflict with dry-inflector
 require "dry/types/inflector"
-require "dry/types/type"
-require "dry/types/printable"
-require "dry/types/nominal"
-require "dry/types/constructor"
 require "dry/types/module"
 
-require "dry/types/errors"
-
 module Dry
   # Main library namespace
   #
   # @api public
   module Types
-    extend Dry::Core::Extensions
-    extend Dry::Core::ClassAttributes
-    extend Dry::Core::Deprecations[:'dry-types']
-    include Dry::Core::Constants
+    extend ::Dry::Core::Extensions
+    extend ::Dry::Core::ClassAttributes
+    extend ::Dry::Core::Deprecations[:"dry-types"]
+    include ::Dry::Core::Constants
 
     TYPE_SPEC_REGEX = /(.+)<(.+)>/.freeze
 
+    def self.loader
+      @loader ||= ::Zeitwerk::Loader.new.tap do |loader|
+        root = ::File.expand_path("..", __dir__)
+        loader.tag = "dry-types"
+        loader.inflector = ::Zeitwerk::GemInflector.new("#{root}/dry-types.rb")
+        loader.inflector.inflect("json" => "JSON")
+        loader.push_dir(root)
+        loader.ignore(
+          "#{root}/dry-types.rb",
+          "#{root}/dry/types/extensions",
+          "#{root}/dry/types/printer",
+          "#{root}/dry/types/spec/types.rb",
+          "#{root}/dry/types/{#{%w[
+            compat
+            constraints
+            core
+            errors
+            extensions
+            inflector
+            module
+            json
+            params
+            printer
+            version
+          ].join(",")}}.rb"
+        )
+      end
+    end
+
+    loader.setup
+
     # @see Dry.Types
     def self.module(*namespaces, default: :nominal, **aliases)
       ::Module.new(container, *namespaces, default: default, **aliases)
@@ -120,7 +147,7 @@ module Dry
     #
     # @return [String]
     def self.identifier(klass)
-      Inflector.underscore(klass).tr("/", ".")
+      Types::Inflector.underscore(klass).tr("/", ".")
     end
 
     # Cached type map
@@ -134,7 +161,7 @@ module Dry
 
     # @api private
     def self.const_missing(const)
-      underscored = Inflector.underscore(const)
+      underscored = Types::Inflector.underscore(const)
 
       if container.keys.any? { |key| key.split(".")[0] == underscored }
         raise ::NameError,
@@ -180,7 +207,7 @@ module Dry
   #
   #   module Types
   #     # imports all types as constants, uses modules for namespaces
-  #     include Dry::Types()
+  #     include Dry.Types()
   #   end
   #   # strict types are exported by default
   #   Types::Integer
@@ -191,7 +218,7 @@ module Dry
   # @example changing default types
   #
   #   module Types
-  #     include Dry::Types(default: :nominal)
+  #     include Dry.Types(default: :nominal)
   #   end
   #   Types::Integer
   #   # => #<Dry::Types[Nominal<Integer>]>
@@ -199,7 +226,7 @@ module Dry
   # @example cherry-picking namespaces
   #
   #   module Types
-  #     include Dry::Types(:strict, :coercible)
+  #     include Dry.Types(:strict, :coercible)
   #   end
   #   # cherry-picking discards default types,
   #   # provide the :default option along with the list of
@@ -208,7 +235,7 @@ module Dry
   #
   # @example custom names
   #   module Types
-  #     include Dry::Types(coercible: :Kernel)
+  #     include Dry.Types(coercible: :Kernel)
   #   end
   #   Types::Kernel::Integer
   #   # => #<Dry::Types[Constructor<Nominal<Integer> fn=Kernel.Integer>]>
diff --git a/lib/dry/types/array.rb b/lib/dry/types/array.rb
index f3053f7..e6cb92a 100644
--- a/lib/dry/types/array.rb
+++ b/lib/dry/types/array.rb
@@ -1,8 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/array/member"
-require "dry/types/array/constructor"
-
 module Dry
   module Types
     # Array type can be used to define an array with optional member type
diff --git a/lib/dry/types/array/constructor.rb b/lib/dry/types/array/constructor.rb
index c0c7584..e3e184b 100644
--- a/lib/dry/types/array/constructor.rb
+++ b/lib/dry/types/array/constructor.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/constructor"
-
 module Dry
   module Types
     # @api public
diff --git a/lib/dry/types/array/member.rb b/lib/dry/types/array/member.rb
index 36d06a2..e984a74 100644
--- a/lib/dry/types/array/member.rb
+++ b/lib/dry/types/array/member.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/array/constructor"
-
 module Dry
   module Types
     class Array < Nominal
@@ -72,7 +70,7 @@ module Dry
         # @return [Result,Logic::Result]
         #
         # @api public
-        def try(input, &block)
+        def try(input, &block) # rubocop:disable Metrics/PerceivedComplexity
           if primitive?(input)
             output = []
 
diff --git a/lib/dry/types/builder.rb b/lib/dry/types/builder.rb
index 6ab487c..e647d71 100644
--- a/lib/dry/types/builder.rb
+++ b/lib/dry/types/builder.rb
@@ -1,12 +1,11 @@
 # frozen_string_literal: true
 
-require "dry/core/deprecations"
-
 module Dry
   module Types
     # Common API for building types and composition
     #
     # @api public
+    # rubocop:disable Metrics/ModuleLength
     module Builder
       include Dry::Core::Constants
 
@@ -32,8 +31,29 @@ module Dry
       #
       # @api private
       def |(other)
-        klass = constrained? && other.constrained? ? Sum::Constrained : Sum
-        klass.new(self, other)
+        compose(other, Sum)
+      end
+
+      # Compose two types into an Intersection type
+      #
+      # @param [Type] other
+      #
+      # @return [Intersection, Intersection::Constrained]
+      #
+      # @api private
+      def &(other)
+        compose(other, Intersection)
+      end
+
+      # Compose two types into an Implication type
+      #
+      # @param [Type] other
+      #
+      # @return [Implication, Implication::Constrained]
+      #
+      # @api private
+      def >(other)
+        compose(other, Implication)
       end
 
       # Turn a type into an optional type
@@ -76,7 +96,7 @@ module Dry
             " value every time. Call `.freeze` when setting the default"\
             " or pass `shared: true` to discard this warning."\
             "\n#{where}",
-            tag: :'dry-types'
+            tag: :"dry-types"
           )
         end
 
@@ -105,7 +125,7 @@ module Dry
           if values.length == 1 && values[0].is_a?(::Hash)
             values[0]
           else
-            ::Hash[values.zip(values)]
+            values.zip(values).to_h
           end
 
         Enum.new(constrained(included_in: mapping.keys), mapping: mapping)
@@ -147,7 +167,7 @@ module Dry
       # @return [Constructor]
       #
       # @api public
-      def fallback(value = Undefined, shared: false, &_fallback)
+      def fallback(value = Undefined, shared: false, &_fallback) # rubocop:disable Metrics/PerceivedComplexity
         if Undefined.equal?(value) && !block_given?
           raise ::ArgumentError, "fallback value or a block must be given"
         end
@@ -167,7 +187,7 @@ module Dry
             " value every time. Call `.freeze` when setting the fallback"\
             " or pass `shared: true` to discard this warning."\
             "\n#{where}",
-            tag: :'dry-types'
+            tag: :"dry-types"
           )
         end
 
@@ -181,12 +201,21 @@ module Dry
           end
         end
       end
+
+      private
+
+      # @api private
+      def compose(other, composition_class)
+        klass =
+          if constrained? && other.constrained?
+            composition_class::Constrained
+          else
+            composition_class
+          end
+
+        klass.new(self, other)
+      end
     end
+    # rubocop:enable Metrics/ModuleLength
   end
 end
-
-require "dry/types/default"
-require "dry/types/constrained"
-require "dry/types/enum"
-require "dry/types/lax"
-require "dry/types/sum"
diff --git a/lib/dry/types/builder_methods.rb b/lib/dry/types/builder_methods.rb
index 76f9b08..69371e5 100644
--- a/lib/dry/types/builder_methods.rb
+++ b/lib/dry/types/builder_methods.rb
@@ -80,7 +80,7 @@ module Dry
       # @param [#call,nil] block Value constructor
       #
       # @return [Dry::Types::Type]
-      def Constructor(klass, cons = nil, &block)
+      def Constructor(klass, cons = nil, &block) # rubocop:disable Metrics/PerceivedComplexity:
         if klass.is_a?(Type)
           if cons || block
             klass.constructor(cons || block)
diff --git a/lib/dry/types/coercions/params.rb b/lib/dry/types/coercions/params.rb
index 3e8dfb6..edbdeb7 100644
--- a/lib/dry/types/coercions/params.rb
+++ b/lib/dry/types/coercions/params.rb
@@ -12,9 +12,10 @@ module Dry
       module Params
         TRUE_VALUES = %w[1 on On ON t true True TRUE T y yes Yes YES Y].freeze
         FALSE_VALUES = %w[0 off Off OFF f false False FALSE F n no No NO N].freeze
-        BOOLEAN_MAP = ::Hash[
-          TRUE_VALUES.product([true]) + FALSE_VALUES.product([false])
-        ].merge(true => true, false => false).freeze
+        BOOLEAN_MAP = EMPTY_HASH.merge(
+          [true, *TRUE_VALUES].to_h { |v| [v, true] },
+          [false, *FALSE_VALUES].to_h { |v| [v, false] }
+        ).freeze
 
         extend Coercions
 
diff --git a/lib/dry/types/compat.rb b/lib/dry/types/compat.rb
index e69de29..8e9b8f9 100644
--- a/lib/dry/types/compat.rb
+++ b/lib/dry/types/compat.rb
@@ -0,0 +1 @@
+# frozen_string_literal: true
diff --git a/lib/dry/types/compiler.rb b/lib/dry/types/compiler.rb
index bb618af..39a254e 100644
--- a/lib/dry/types/compiler.rb
+++ b/lib/dry/types/compiler.rb
@@ -1,12 +1,10 @@
 # frozen_string_literal: true
 
-require "dry/core/deprecations"
-
 module Dry
   module Types
     # @api private
     class Compiler
-      extend ::Dry::Core::Deprecations[:'dry-types']
+      extend ::Dry::Core::Deprecations[:"dry-types"]
 
       attr_reader :registry
 
diff --git a/lib/dry/types/composition.rb b/lib/dry/types/composition.rb
new file mode 100644
index 0000000..d792f20
--- /dev/null
+++ b/lib/dry/types/composition.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+require "dry/core/equalizer"
+require "dry/types/options"
+require "dry/types/meta"
+
+module Dry
+  module Types
+    module Composition
+      include Type
+      include Builder
+      include Options
+      include Meta
+      include Printable
+      include Dry::Equalizer(:left, :right, :options, :meta, inspect: false, immutable: true)
+
+      # @return [Type]
+      attr_reader :left
+
+      # @return [Type]
+      attr_reader :right
+
+      module Constrained
+        def rule
+          left.rule.public_send(self.class.operator, right.rule)
+        end
+
+        def constrained?
+          true
+        end
+      end
+
+      def self.included(base)
+        composition_name = Inflector.demodulize(base)
+        ast_type = Inflector.underscore(composition_name).to_sym
+        base.define_singleton_method(:ast_type) { ast_type }
+        base.define_singleton_method(:composition_name) { composition_name }
+        base.const_set("Constrained", Class.new(base) { include Constrained })
+      end
+
+      # @param [Type] left
+      # @param [Type] right
+      # @param [Hash] options
+      #
+      # @api private
+      def initialize(left, right, **options)
+        super
+        @left, @right = left, right
+        freeze
+      end
+
+      # @return [String]
+      #
+      # @api public
+      def name
+        [left, right].map(&:name).join(" #{self.class.operator} ")
+      end
+
+      # @return [false]
+      #
+      # @api public
+      def default?
+        false
+      end
+
+      # @return [false]
+      #
+      # @api public
+      def constrained?
+        false
+      end
+
+      # @return [Boolean]
+      #
+      # @api public
+      def optional?
+        false
+      end
+
+      # @param [Object] input
+      #
+      # @return [Object]
+      #
+      # @api private
+      def call_unsafe(input)
+        raise NotImplementedError
+      end
+
+      # @param [Object] input
+      #
+      # @return [Object]
+      #
+      # @api private
+      def call_safe(input, &block)
+        raise NotImplementedError
+      end
+
+      # @param [Object] input
+      #
+      # @api public
+      def try(input)
+        raise NotImplementedError
+      end
+
+      # @api private
+      def success(input)
+        result = try(input)
+        if result.success?
+          result
+        else
+          raise ArgumentError, "Invalid success value '#{input}' for #{inspect}"
+        end
+      end
+
+      # @api private
+      def failure(input, _error = nil)
+        result = try(input)
+        if result.failure?
+          result
+        else
+          raise ArgumentError, "Invalid failure value '#{input}' for #{inspect}"
+        end
+      end
+
+      # @param [Object] value
+      #
+      # @return [Boolean]
+      #
+      # @api private
+      def primitive?(value)
+        raise NotImplementedError
+      end
+
+      # @see Nominal#to_ast
+      #
+      # @api public
+      def to_ast(meta: true)
+        [self.class.ast_type,
+         [left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
+      end
+
+      # Wrap the type with a proc
+      #
+      # @return [Proc]
+      #
+      # @api public
+      def to_proc
+        proc { |value| self.(value) }
+      end
+    end
+  end
+end
diff --git a/lib/dry/types/constrained.rb b/lib/dry/types/constrained.rb
index 7d481d7..d1711fc 100644
--- a/lib/dry/types/constrained.rb
+++ b/lib/dry/types/constrained.rb
@@ -1,10 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/equalizer"
-require "dry/types/decorator"
-require "dry/types/constraints"
-require "dry/types/constrained/coercible"
-
 module Dry
   module Types
     # Constrained types apply rules to the input
diff --git a/lib/dry/types/constraints.rb b/lib/dry/types/constraints.rb
index e7f47ce..6032c5d 100644
--- a/lib/dry/types/constraints.rb
+++ b/lib/dry/types/constraints.rb
@@ -1,9 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/logic/rule_compiler"
-require "dry/logic/predicates"
-require "dry/logic/rule/predicate"
-
 module Dry
   # Helper methods for constraint types
   #
@@ -17,8 +13,8 @@ module Dry
     def self.Rule(options)
       rule_compiler.(
         options.map { |key, val|
-          Logic::Rule::Predicate.build(
-            Logic::Predicates[:"#{key}?"]
+          ::Dry::Logic::Rule::Predicate.build(
+            ::Dry::Logic::Predicates[:"#{key}?"]
           ).curry(val).to_ast
         }
       ).reduce(:and)
@@ -28,7 +24,7 @@ module Dry
     #
     # @api private
     def self.rule_compiler
-      @rule_compiler ||= Logic::RuleCompiler.new(Logic::Predicates)
+      @rule_compiler ||= ::Dry::Logic::RuleCompiler.new(::Dry::Logic::Predicates)
     end
   end
 end
diff --git a/lib/dry/types/constructor.rb b/lib/dry/types/constructor.rb
index db3abab..c1d86ba 100644
--- a/lib/dry/types/constructor.rb
+++ b/lib/dry/types/constructor.rb
@@ -1,10 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/equalizer"
-require "dry/types/fn_container"
-require "dry/types/constructor/function"
-require "dry/types/constructor/wrapper"
-
 module Dry
   module Types
     # Constructor types apply a function to the input that is supposed to return
@@ -27,9 +22,9 @@ module Dry
       # @param [#call, nil] block
       #
       # @api public
-      def self.new(input, **options, &block)
+      def self.new(input, fn: Undefined, **options, &block)
         type = input.is_a?(Builder) ? input : Nominal.new(input)
-        super(type, **options, fn: Function[options.fetch(:fn, block)])
+        super(type, **options, fn: Function[Undefined.default(fn, block)])
       end
 
       # @param [Builder, Object] input
@@ -49,13 +44,12 @@ module Dry
 
       # @api private
       def self.wrapper_type
-        @wrapper_type ||= begin
+        @wrapper_type ||=
           if self < Wrapper
             self
           else
             const_set(:Wrapping, ::Class.new(self).include(Wrapper))
           end
-        end
       end
 
       # Instantiate a new constructor type instance
@@ -191,7 +185,7 @@ module Dry
         if type.respond_to?(method)
           response = type.public_send(method, *args, &block)
 
-          if response.is_a?(Type) && type.class.equal?(response.class)
+          if response.is_a?(Type) && response.instance_of?(type.class)
             response.constructor_type[response, **options]
           else
             response
diff --git a/lib/dry/types/constructor/function.rb b/lib/dry/types/constructor/function.rb
index 758dbc3..7ff908b 100644
--- a/lib/dry/types/constructor/function.rb
+++ b/lib/dry/types/constructor/function.rb
@@ -1,6 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/equalizer"
 require "concurrent/map"
 
 module Dry
@@ -62,17 +61,17 @@ module Dry
                 ::Module.new do
                   if safe
                     module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
-                      def call(input, &block)
-                        @target.#{method}(input, &block)
-                      end
+                      def call(input, &block)              # def call(input, &block)
+                        @target.#{method}(input, &block)   #   @target.coerve(input, &block)
+                      end                                  # end
                     RUBY
                   else
                     module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
-                      def call(input, &block)
-                        @target.#{method}(input)
-                      rescue ::NoMethodError, ::TypeError, ::ArgumentError => error
-                        CoercionError.handle(error, &block)
-                      end
+                      def call(input, &block)                                        # def call(input, &block)
+                        @target.#{method}(input)                                     #   @target.coerce(input)
+                      rescue ::NoMethodError, ::TypeError, ::ArgumentError => error  # rescue ::NoMethodError, ::TypeError, ::ArgumentError => error
+                        CoercionError.handle(error, &block)                          #   CoercionError.handle(error, &block)
+                      end                                                            # end
                     RUBY
                   end
                 end
diff --git a/lib/dry/types/container.rb b/lib/dry/types/container.rb
index c566152..0b0f586 100644
--- a/lib/dry/types/container.rb
+++ b/lib/dry/types/container.rb
@@ -1,14 +1,12 @@
 # frozen_string_literal: true
 
-require "dry/container"
-
 module Dry
   module Types
     # Internal container for the built-in types
     #
     # @api private
     class Container
-      include Dry::Container::Mixin
+      include Core::Container::Mixin
     end
   end
 end
diff --git a/lib/dry/types/core.rb b/lib/dry/types/core.rb
index ad9474b..2eb09ba 100644
--- a/lib/dry/types/core.rb
+++ b/lib/dry/types/core.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/any"
-
 module Dry
   module Types
     # Primitives with {Kernel} coercion methods
@@ -60,7 +58,8 @@ module Dry
 
     # Register {KERNEL_COERCIBLE} types
     KERNEL_COERCIBLE.each do |name, primitive|
-      register("coercible.#{name}", self["nominal.#{name}"].constructor(Kernel.method(primitive.name)))
+      register("coercible.#{name}",
+               self["nominal.#{name}"].constructor(Kernel.method(primitive.name)))
     end
 
     # Register {METHOD_COERCIBLE} types
diff --git a/lib/dry/types/decorator.rb b/lib/dry/types/decorator.rb
index 39877d0..2e626be 100644
--- a/lib/dry/types/decorator.rb
+++ b/lib/dry/types/decorator.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/options"
-
 module Dry
   module Types
     # Common API for types
diff --git a/lib/dry/types/default.rb b/lib/dry/types/default.rb
index a707765..ff42309 100644
--- a/lib/dry/types/default.rb
+++ b/lib/dry/types/default.rb
@@ -1,8 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/equalizer"
-require "dry/types/decorator"
-
 module Dry
   module Types
     # Default types are useful when a missing value should be replaced by a default one
@@ -53,7 +50,7 @@ module Dry
       # @param [Object] value
       #
       # @api private
-      def initialize(type, value, **options)
+      def initialize(type, value, **)
         super
         @value = value
       end
@@ -65,8 +62,8 @@ module Dry
       # @return [Default]
       #
       # @api public
-      def constrained(*args)
-        type.constrained(*args).default(value)
+      def constrained(...)
+        type.constrained(...).default(value)
       end
 
       # @return [true]
diff --git a/lib/dry/types/enum.rb b/lib/dry/types/enum.rb
index c82da60..8caa6f6 100644
--- a/lib/dry/types/enum.rb
+++ b/lib/dry/types/enum.rb
@@ -1,8 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/equalizer"
-require "dry/types/decorator"
-
 module Dry
   module Types
     # Enum types can be used to define an enum on top of an existing type
diff --git a/lib/dry/types/errors.rb b/lib/dry/types/errors.rb
index ddc413b..be677f0 100644
--- a/lib/dry/types/errors.rb
+++ b/lib/dry/types/errors.rb
@@ -51,6 +51,7 @@ module Dry
 
       # @param [Array<CoercionError>] errors
       def initialize(errors)
+        super("")
         @errors = errors
       end
 
@@ -66,11 +67,22 @@ module Dry
     end
 
     class SchemaError < CoercionError
+      # @return [String, Symbol]
+      attr_reader :key
+
+      # @return [Object]
+      attr_reader :value
+
       # @param [String,Symbol] key
       # @param [Object] value
       # @param [String, #to_s] result
       def initialize(key, value, result)
-        super("#{value.inspect} (#{value.class}) has invalid type for :#{key} violates constraints (#{result} failed)")
+        @key = key
+        @value = value
+        super(
+          "#{value.inspect} (#{value.class}) has invalid type "\
+          "for :#{key} violates constraints (#{result} failed)"
+        )
       end
     end
 
diff --git a/lib/dry/types/extensions/maybe.rb b/lib/dry/types/extensions/maybe.rb
index 4869d95..369f9c3 100644
--- a/lib/dry/types/extensions/maybe.rb
+++ b/lib/dry/types/extensions/maybe.rb
@@ -1,8 +1,11 @@
 # frozen_string_literal: true
 
-require "dry/core/equalizer"
-require "dry/monads/maybe"
-require "dry/types/decorator"
+require "dry/monads"
+require "dry/monads/version"
+
+if Gem::Version.new(Dry::Monads::VERSION) < Gem::Version.new("1.5.0")
+  raise "dry-types requires dry-monads >= 1.5.0"
+end
 
 module Dry
   module Types
@@ -15,7 +18,7 @@ module Dry
       include Decorator
       include Builder
       include Printable
-      include ::Dry::Monads::Maybe::Mixin
+      include ::Dry::Monads[:maybe]
 
       # @param [Dry::Monads::Maybe, Object] input
       #
@@ -99,7 +102,7 @@ module Dry
     end
 
     # @api private
-    class Schema::Key
+    class Schema::Key # rubocop:disable Style/ClassAndModuleChildren
       # @api private
       def maybe
         __new__(type.maybe)
diff --git a/lib/dry/types/extensions/monads.rb b/lib/dry/types/extensions/monads.rb
index 68b51cc..97f6bd6 100644
--- a/lib/dry/types/extensions/monads.rb
+++ b/lib/dry/types/extensions/monads.rb
@@ -1,6 +1,11 @@
 # frozen_string_literal: true
 
-require "dry/monads/result"
+require "dry/monads"
+require "dry/monads/version"
+
+if Gem::Version.new(Dry::Monads::VERSION) < Gem::Version.new("1.5.0")
+  raise "dry-types requires dry-monads >= 1.5.0"
+end
 
 module Dry
   module Types
@@ -8,7 +13,7 @@ module Dry
     #
     # @api public
     class Result
-      include Dry::Monads::Result::Mixin
+      include ::Dry::Monads[:result]
 
       # Turn result into a monad
       #
diff --git a/lib/dry/types/fn_container.rb b/lib/dry/types/fn_container.rb
index 46e3d38..a36ece4 100644
--- a/lib/dry/types/fn_container.rb
+++ b/lib/dry/types/fn_container.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/container"
-
 module Dry
   module Types
     # Internal container for constructor functions used by the built-in types
diff --git a/lib/dry/types/hash.rb b/lib/dry/types/hash.rb
index 9df5083..6286e49 100644
--- a/lib/dry/types/hash.rb
+++ b/lib/dry/types/hash.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/hash/constructor"
-
 module Dry
   module Types
     # Hash types can be used to define maps and schemas
@@ -51,7 +49,7 @@ module Dry
       # @api private
       def weak(*)
         raise "Support for old hash schemas was removed, please refer to the CHANGELOG "\
-              "on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/master/CHANGELOG.md"
+              "on how to proceed with the new API https://github.com/dry-rb/dry-types/blob/main/CHANGELOG.md"
       end
       alias_method :permissive, :weak
       alias_method :strict, :weak
@@ -132,6 +130,3 @@ module Dry
     end
   end
 end
-
-require "dry/types/schema/key"
-require "dry/types/schema"
diff --git a/lib/dry/types/hash/constructor.rb b/lib/dry/types/hash/constructor.rb
index 57250ac..cc43e3b 100644
--- a/lib/dry/types/hash/constructor.rb
+++ b/lib/dry/types/hash/constructor.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/constructor"
-
 module Dry
   module Types
     # Hash type exposes additional APIs for working with schema hashes
@@ -24,8 +22,8 @@ module Dry
         # @see Dry::Types::Array#of
         #
         # @api public
-        def schema(*args)
-          type.schema(*args).constructor(fn, meta: meta)
+        def schema(...)
+          type.schema(...).constructor(fn, meta: meta)
         end
       end
     end
diff --git a/lib/dry/types/implication.rb b/lib/dry/types/implication.rb
new file mode 100644
index 0000000..d33504d
--- /dev/null
+++ b/lib/dry/types/implication.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Dry
+  module Types
+    # Implication type
+    #
+    # @api public
+    class Implication
+      include Composition
+
+      def self.operator
+        :>
+      end
+
+      # @param [Object] input
+      #
+      # @return [Object]
+      #
+      # @api private
+      def call_unsafe(input)
+        if left.try(input).success?
+          right.call_unsafe(input)
+        else
+          input
+        end
+      end
+
+      # @param [Object] input
+      #
+      # @return [Object]
+      #
+      # @api private
+      def call_safe(input, &block)
+        if left.try(input).success?
+          right.call_safe(input, &block)
+        else
+          input
+        end
+      end
+
+      # @param [Object] input
+      #
+      # @api public
+      def try(input)
+        if left.try(input).success?
+          right.try(input)
+        else
+          Result::Success.new(input)
+        end
+      end
+
+      # @param [Object] value
+      #
+      # @return [Boolean]
+      #
+      # @api private
+      def primitive?(value)
+        if left.primitive?(value)
+          right.primitive?(value)
+        else
+          true
+        end
+      end
+    end
+  end
+end
diff --git a/lib/dry/types/intersection.rb b/lib/dry/types/intersection.rb
new file mode 100644
index 0000000..96f7ba5
--- /dev/null
+++ b/lib/dry/types/intersection.rb
@@ -0,0 +1,108 @@
+# frozen_string_literal: true
+
+require "dry/core/equalizer"
+require "dry/types/options"
+require "dry/types/meta"
+
+module Dry
+  module Types
+    # Intersection type
+    #
+    # @api public
+    class Intersection
+      include Composition
+
+      def self.operator
+        :&
+      end
+
+      # @param [Object] input
+      #
+      # @return [Object]
+      #
+      # @api private
+      def call_unsafe(input)
+        merge_results(left.call_unsafe(input), right.call_unsafe(input))
+      end
+
+      # @param [Object] input
+      #
+      # @return [Object]
+      #
+      # @api private
+      def call_safe(input, &block)
+        try_sides(input, &block).input
+      end
+
+      # @param [Object] input
+      #
+      # @api public
+      def try(input)
+        try_sides(input) do |failure|
+          if block_given?
+            yield(failure)
+          else
+            failure
+          end
+        end
+      end
+
+      # @param [Object] value
+      #
+      # @return [Boolean]
+      #
+      # @api private
+      def primitive?(value)
+        left.primitive?(value) && right.primitive?(value)
+      end
+
+      private
+
+      # @api private
+      def try_sides(input, &block)
+        results = []
+
+        [left, right].each do |side|
+          result = try_side(side, input, &block)
+          return result if result.failure?
+
+          results << result
+        end
+
+        Result::Success.new(merge_results(*results.map(&:input)))
+      end
+
+      # @api private
+      def try_side(side, input)
+        failure = nil
+
+        result = side.try(input) do |f|
+          failure = f
+          yield(f)
+        end
+
+        if result.is_a?(Result)
+          result
+        elsif failure
+          Result::Failure.new(result, failure)
+        else
+          Result::Success.new(result)
+        end
+      end
+
+      # @api private
+      def merge_results(left_result, right_result)
+        case left_result
+        when ::Array
+          left_result
+            .zip(right_result)
+            .map { |lhs, rhs| merge_results(lhs, rhs) }
+        when ::Hash
+          left_result.merge(right_result)
+        else
+          left_result
+        end
+      end
+    end
+  end
+end
diff --git a/lib/dry/types/lax.rb b/lib/dry/types/lax.rb
index fc3107e..48492f6 100644
--- a/lib/dry/types/lax.rb
+++ b/lib/dry/types/lax.rb
@@ -1,8 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/deprecations"
-require "dry/types/decorator"
-
 module Dry
   module Types
     # Lax types rescue from type-related errors when constructors fail
@@ -68,7 +65,7 @@ module Dry
       end
     end
 
-    extend ::Dry::Core::Deprecations[:'dry-types']
+    extend ::Dry::Core::Deprecations[:"dry-types"]
     Safe = Lax
     deprecate_constant(:Safe)
   end
diff --git a/lib/dry/types/map.rb b/lib/dry/types/map.rb
index 790d06d..e35654d 100644
--- a/lib/dry/types/map.rb
+++ b/lib/dry/types/map.rb
@@ -14,15 +14,17 @@ module Dry
     #   # => {1 => 'right'}
     #
     #   type.('1' => 'wrong')
-    #   # Dry::Types::MapError: "1" violates constraints (type?(Integer, "1") AND gteq?(1, "1") AND lteq?(10, "1") failed)
+    #   # Dry::Types::MapError: "1" violates constraints (type?(Integer, "1")
+    #   #                                                 AND gteq?(1, "1")
+    #   #                                                 AND lteq?(10, "1") failed)
     #
     #   type.(11 => 'wrong')
     #   # Dry::Types::MapError: 11 violates constraints (lteq?(10, 11) failed)
     #
     # @api public
     class Map < Nominal
-      def initialize(_primitive, key_type: Types["any"], value_type: Types["any"], meta: EMPTY_HASH)
-        super(_primitive, key_type: key_type, value_type: value_type, meta: meta)
+      def initialize(primitive, key_type: Types["any"], value_type: Types["any"], meta: EMPTY_HASH)
+        super(primitive, key_type: key_type, value_type: value_type, meta: meta)
       end
 
       # @return [Type]
@@ -100,6 +102,8 @@ module Dry
       private
 
       # @api private
+      # rubocop:disable Metrics/PerceivedComplexity
+      # rubocop:disable Metrics/AbcSize
       def coerce(input)
         unless primitive?(input)
           return failure(
@@ -131,6 +135,8 @@ module Dry
           failure(input, MultipleError.new(failures))
         end
       end
+      # rubocop:enable Metrics/PerceivedComplexity
+      # rubocop:enable Metrics/AbcSize
     end
   end
 end
diff --git a/lib/dry/types/module.rb b/lib/dry/types/module.rb
index f15d48a..ee99e37 100644
--- a/lib/dry/types/module.rb
+++ b/lib/dry/types/module.rb
@@ -1,6 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/deprecations"
 require "dry/types/builder_methods"
 
 module Dry
@@ -8,7 +7,7 @@ module Dry
     # Export types registered in a container as module constants.
     # @example
     #   module Types
-    #     include Dry::Types(:strict, :coercible, :nominal, default: :strict)
+    #     include Dry.Types(:strict, :coercible, :nominal, default: :strict)
     #   end
     #
     #   Types.constants
@@ -26,10 +25,10 @@ module Dry
         extend(BuilderMethods)
 
         if constants.key?(:Nominal)
-          singleton_class.send(:define_method, :included) do |base|
+          singleton_class.define_method(:included) do |base|
             super(base)
             base.instance_exec(const_get(:Nominal, false)) do |nominal|
-              extend Dry::Core::Deprecations[:'dry-types']
+              extend Dry::Core::Deprecations[:"dry-types"]
               const_set(:Definition, nominal)
               deprecate_constant(:Definition, message: "Nominal")
             end
@@ -38,13 +37,16 @@ module Dry
       end
 
       # @api private
+      # rubocop:disable Metrics/AbcSize
+      # rubocop:disable Metrics/CyclomaticComplexity
+      # rubocop:disable Metrics/PerceivedComplexity
       def type_constants(*namespaces, default: Undefined, **aliases)
         if namespaces.empty? && aliases.empty? && Undefined.equal?(default)
           default_ns = :Strict
         elsif Undefined.equal?(default)
           default_ns = Undefined
         else
-          default_ns = Inflector.camelize(default).to_sym
+          default_ns = Types::Inflector.camelize(default).to_sym
         end
 
         tree = registry_tree
@@ -52,7 +54,9 @@ module Dry
         if namespaces.empty? && aliases.empty?
           modules = tree.select { |_, v| v.is_a?(::Hash) }.map(&:first)
         else
-          modules = (namespaces + aliases.keys).map { |n| Inflector.camelize(n).to_sym }
+          modules = (namespaces + aliases.keys).map { |n|
+            Types::Inflector.camelize(n).to_sym
+          }
         end
 
         tree.each_with_object({}) do |(key, value), constants|
@@ -64,13 +68,16 @@ module Dry
           constants.update(value) if key == default_ns
         end
       end
+      # rubocop:enable Metrics/AbcSize
+      # rubocop:enable Metrics/CyclomaticComplexity
+      # rubocop:enable Metrics/PerceivedComplexity
 
       # @api private
       def registry_tree
         @registry_tree ||= @registry.keys.each_with_object({}) { |key, tree|
           type = @registry[key]
           *modules, const_name = key.split(".").map { |part|
-            Inflector.camelize(part).to_sym
+            Types::Inflector.camelize(part).to_sym
           }
           next if modules.empty?
 
@@ -91,9 +98,11 @@ module Dry
           ns.to_sym unless path.empty?
         }.compact.uniq
 
-        (referenced.uniq - known).each do |name|
+        unknown = (referenced.uniq - known).first
+
+        if unknown
           raise ArgumentError,
-                "#{name.inspect} is not a known type namespace. "\
+                "#{unknown.inspect} is not a known type namespace. "\
                 "Supported options are #{known.map(&:inspect).join(", ")}"
         end
       end
diff --git a/lib/dry/types/nominal.rb b/lib/dry/types/nominal.rb
index 256ab1f..3221396 100644
--- a/lib/dry/types/nominal.rb
+++ b/lib/dry/types/nominal.rb
@@ -1,12 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/deprecations"
-require "dry/core/equalizer"
-require "dry/types/builder"
-require "dry/types/result"
-require "dry/types/options"
-require "dry/types/meta"
-
 module Dry
   module Types
     # Nominal types define a primitive class and do not apply any constructors or constraints
@@ -20,7 +13,7 @@ module Dry
       include Meta
       include Builder
       include Printable
-      include Dry::Equalizer(:primitive, :options, :meta, inspect: false, immutable: true)
+      include ::Dry::Equalizer(:primitive, :options, :meta, inspect: false, immutable: true)
 
       # @return [Class]
       attr_reader :primitive
@@ -199,12 +192,8 @@ module Dry
       end
     end
 
-    extend Dry::Core::Deprecations[:'dry-types']
+    extend ::Dry::Core::Deprecations[:"dry-types"]
     Definition = Nominal
     deprecate_constant(:Definition, message: "Nominal")
   end
 end
-
-require "dry/types/array"
-require "dry/types/hash"
-require "dry/types/map"
diff --git a/lib/dry/types/predicate_inferrer.rb b/lib/dry/types/predicate_inferrer.rb
index 3ec12c1..0cc525a 100644
--- a/lib/dry/types/predicate_inferrer.rb
+++ b/lib/dry/types/predicate_inferrer.rb
@@ -1,9 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/cache"
-require "dry/core/class_attributes"
-require "dry/types/predicate_registry"
-
 module Dry
   module Types
     # PredicateInferrer returns the list of predicates used by a type.
@@ -55,7 +51,7 @@ module Dry
         end
 
         # @api private
-        def infer_predicate(type)
+        def infer_predicate(type) # rubocop:disable Metrics/PerceivedComplexity
           pred = TYPE_TO_PREDICATE.fetch(type) do
             if type.name.nil? || self.class.infer_predicate_by_class_name.equal?(false)
               nil
@@ -175,9 +171,9 @@ module Dry
         def visit_predicate(node)
           pred, args = node
 
-          if pred.equal?(:type?)
+          if pred.equal?(:type?) || !registry.key?(pred)
             EMPTY_ARRAY
-          elsif registry.key?(pred)
+          else
             *curried, _ = args
             values = curried.map { |_, v| v }
 
@@ -186,8 +182,6 @@ module Dry
             else
               [pred => values[0]]
             end
-          else
-            EMPTY_ARRAY
           end
         end
 
diff --git a/lib/dry/types/predicate_registry.rb b/lib/dry/types/predicate_registry.rb
index 8f08780..51e6375 100644
--- a/lib/dry/types/predicate_registry.rb
+++ b/lib/dry/types/predicate_registry.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/logic/predicates"
-
 module Dry
   module Types
     # A registry with predicate objects from `Dry::Logic::Predicates`
@@ -14,20 +12,22 @@ module Dry
       # @api private
       attr_reader :has_predicate
 
+      KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?)
+      private_constant(:KERNEL_RESPOND_TO)
+
       # @api private
       def initialize(predicates = Logic::Predicates)
         @predicates = predicates
-        @has_predicate = ::Kernel.instance_method(:respond_to?).bind(@predicates)
       end
 
       # @api private
-      def [](name)
-        predicates[name]
+      def key?(name)
+        KERNEL_RESPOND_TO.bind_call(@predicates, name)
       end
 
       # @api private
-      def key?(name)
-        has_predicate.(name)
+      def [](name)
+        predicates[name]
       end
     end
   end
diff --git a/lib/dry/types/primitive_inferrer.rb b/lib/dry/types/primitive_inferrer.rb
index d631420..3ff9122 100644
--- a/lib/dry/types/primitive_inferrer.rb
+++ b/lib/dry/types/primitive_inferrer.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/cache"
-
 module Dry
   module Types
     # PrimitiveInferrer returns the list of classes matching a type.
diff --git a/lib/dry/types/printer.rb b/lib/dry/types/printer.rb
index 0cf75a3..f9706de 100644
--- a/lib/dry/types/printer.rb
+++ b/lib/dry/types/printer.rb
@@ -1,15 +1,21 @@
 # frozen_string_literal: true
 
+require "dry/types/printer/composition"
+
 module Dry
+  # rubocop:disable Metrics/AbcSize
+  # rubocop:disable Metrics/PerceivedComplexity
   module Types
     # @api private
     class Printer
       MAPPING = {
         Nominal => :visit_nominal,
         Constructor => :visit_constructor,
-        Constrained => :visit_constrained,
-        Constrained::Coercible => :visit_constrained,
-        Hash => :visit_hash,
+        [
+          Constrained,
+          Constrained::Coercible
+        ] => :visit_constrained,
+        Types::Hash => :visit_hash,
         Schema => :visit_schema,
         Schema::Key => :visit_key,
         Map => :visit_map,
@@ -17,12 +23,22 @@ module Dry
         Array::Member => :visit_array_member,
         Lax => :visit_lax,
         Enum => :visit_enum,
-        Default => :visit_default,
-        Default::Callable => :visit_default,
-        Sum => :visit_sum,
-        Sum::Constrained => :visit_sum,
+        [Default, Default::Callable] => :visit_default,
+        [
+          Sum,
+          Sum::Constrained,
+          Intersection,
+          Intersection::Constrained,
+          Implication,
+          Implication::Constrained
+        ] => :visit_composition,
         Any.class => :visit_any
-      }
+      }.flat_map { |k, v| Array(k).map { |kk| [kk, v] } }.to_h
+
+      def initialize
+        @composition_printers = {}
+        freeze
+      end
 
       def call(type)
         output = "".dup
@@ -85,106 +101,10 @@ module Dry
         end
       end
 
-      def visit_schema(schema)
-        options = schema.options.dup
-        size = schema.count
-        key_fn_str = ""
-        type_fn_str = ""
-        strict_str = ""
-
-        strict_str = "strict " if options.delete(:strict)
-
-        if key_fn = options.delete(:key_transform_fn)
-          visit_callable(key_fn) do |fn|
-            key_fn_str = "key_fn=#{fn} "
-          end
-        end
-
-        if type_fn = options.delete(:type_transform_fn)
-          visit_callable(type_fn) do |fn|
-            type_fn_str = "type_fn=#{fn} "
-          end
-        end
-
-        keys = options.delete(:keys)
-
-        visit_options(options, schema.meta) do |opts|
-          opts = "#{opts[1..-1]} " unless opts.empty?
-          schema_parameters = "#{key_fn_str}#{type_fn_str}#{strict_str}#{opts}"
-
-          header = "Schema<#{schema_parameters}keys={"
-
-          if size.zero?
-            yield "#{header}}>"
-          else
-            yield header.dup << keys.map { |key|
-              visit(key) { |type| type }
-            }.join(" ") << "}>"
-          end
-        end
-      end
-
-      def visit_map(map)
-        visit(map.key_type) do |key|
-          visit(map.value_type) do |value|
-            options = map.options.dup
-            options.delete(:key_type)
-            options.delete(:value_type)
-
-            visit_options(options) do |_opts|
-              yield "Map<#{key} => #{value}>"
-            end
-          end
-        end
-      end
-
-      def visit_key(key)
-        visit(key.type) do |type|
-          if key.required?
-            yield "#{key.name}: #{type}"
-          else
-            yield "#{key.name}?: #{type}"
-          end
-        end
-      end
-
-      def visit_sum(sum)
-        visit_sum_constructors(sum) do |constructors|
-          visit_options(sum.options, sum.meta) do |opts|
-            yield "Sum<#{constructors}#{opts}>"
-          end
-        end
-      end
-
-      def visit_sum_constructors(sum)
-        case sum.left
-        when Sum
-          visit_sum_constructors(sum.left) do |left|
-            case sum.right
-            when Sum
-              visit_sum_constructors(sum.right) do |right|
-                yield "#{left} | #{right}"
-              end
-            else
-              visit(sum.right) do |right|
-                yield "#{left} | #{right}"
-              end
-            end
-          end
-        else
-          visit(sum.left) do |left|
-            case sum.right
-            when Sum
-              visit_sum_constructors(sum.right) do |right|
-                yield "#{left} | #{right}"
-              end
-            else
-              visit(sum.right) do |right|
-                yield "#{left} | #{right}"
-              end
-            end
-          end
-        end
+      def visit_composition(composition, &block)
+        klass = composition.class
+        @composition_printers[klass] = Composition.new(self, klass)
+        @composition_printers[klass].visit(composition, &block)
       end
 
       def visit_enum(enum)
@@ -232,25 +152,6 @@ module Dry
         end
       end
 
-      def visit_hash(hash)
-        options = hash.options.dup
-        type_fn_str = ""
-
-        if type_fn = options.delete(:type_transform_fn)
-          visit_callable(type_fn) do |fn|
-            type_fn_str = "type_fn=#{fn}"
-          end
-        end
-
-        visit_options(options, hash.meta) do |opts|
-          if opts.empty? && type_fn_str.empty?
-            yield "Hash"
-          else
-            yield "Hash<#{type_fn_str}#{opts}>"
-          end
-        end
-      end
-
       def visit_callable(callable)
         fn = callable.is_a?(String) ? FnContainer[callable] : callable
 
@@ -263,9 +164,9 @@ module Dry
           if line&.zero?
             yield ".#{path}"
           elsif path
-            yield "#{path.sub(Dir.pwd + "/", EMPTY_STRING)}:#{line}"
+            yield "#{path.sub("#{Dir.pwd}/", EMPTY_STRING)}:#{line}"
           else
-            match = fn.to_s.match(/\A#<Proc:0x\h+\(&:(?<name>\w+)\)(:? \(lambda\))?>\z/)
+            match = fn.to_s.match(/\A#<Proc:0x\h+\(&:(?<name>\w+)\)(:? \(lambda\))?>\z/) # rubocop:disable Lint/MixedRegexpCaptureTypes
 
             if match
               yield ".#{match[:name]}"
@@ -286,7 +187,89 @@ module Dry
         end
       end
 
-      def visit_options(options, meta = EMPTY_HASH)
+      def visit_schema(schema)
+        options = schema.options.dup
+        size = schema.count
+        key_fn_str = ""
+        type_fn_str = ""
+        strict_str = ""
+
+        strict_str = "strict " if options.delete(:strict)
+
+        if (key_fn = options.delete(:key_transform_fn))
+          visit_callable(key_fn) do |fn|
+            key_fn_str = "key_fn=#{fn} "
+          end
+        end
+
+        if (type_fn = options.delete(:type_transform_fn))
+          visit_callable(type_fn) do |fn|
+            type_fn_str = "type_fn=#{fn} "
+          end
+        end
+
+        keys = options.delete(:keys)
+
+        visit_options(options, schema.meta) do |opts|
+          opts = "#{opts[1..]} " unless opts.empty?
+          schema_parameters = "#{key_fn_str}#{type_fn_str}#{strict_str}#{opts}"
+
+          header = "Schema<#{schema_parameters}keys={"
+
+          if size.zero?
+            yield "#{header}}>"
+          else
+            yield header.dup << keys.map { |key|
+              visit(key) { |type| type }
+            }.join(" ") << "}>"
+          end
+        end
+      end
+
+      def visit_map(map)
+        visit(map.key_type) do |key|
+          visit(map.value_type) do |value|
+            options = map.options.dup
+            options.delete(:key_type)
+            options.delete(:value_type)
+
+            visit_options(options) do |_opts|
+              yield "Map<#{key} => #{value}>"
+            end
+          end
+        end
+      end
+
+      def visit_key(key)
+        visit(key.type) do |type|
+          if key.required?
+            yield "#{key.name}: #{type}"
+          else
+            yield "#{key.name}?: #{type}"
+          end
+        end
+      end
+
+      def visit_hash(hash)
+        options = hash.options.dup
+        type_fn_str = ""
+
+        if (type_fn = options.delete(:type_transform_fn))
+          visit_callable(type_fn) do |fn|
+            type_fn_str = "type_fn=#{fn}"
+          end
+        end
+
+        visit_options(options, hash.meta) do |opts|
+          if opts.empty? && type_fn_str.empty?
+            yield "Hash"
+          else
+            yield "Hash<#{type_fn_str}#{opts}>"
+          end
+        end
+      end
+
+      def visit_options(options, meta = EMPTY_HASH) # rubocop:disable Metrics/PerceivedComplexity
         if options.empty? && meta.empty?
           yield ""
         else
@@ -310,6 +293,8 @@ module Dry
       end
     end
 
-    PRINTER = Printer.new.freeze
+    PRINTER = Printer.new
   end
+  # rubocop:enable Metrics/AbcSize
+  # rubocop:enable Metrics/PerceivedComplexity
 end
diff --git a/lib/dry/types/printer/composition.rb b/lib/dry/types/printer/composition.rb
new file mode 100644
index 0000000..82b2b55
--- /dev/null
+++ b/lib/dry/types/printer/composition.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Dry
+  module Types
+    # @api private
+    class Printer
+      # @api private
+      class Composition
+        def initialize(printer, composition_class)
+          @printer = printer
+          @composition_class = composition_class
+          freeze
+        end
+
+        def visit(composition)
+          visit_constructors(composition) do |constructors|
+            @printer.visit_options(composition.options, composition.meta) do |opts|
+              yield "#{@composition_class.composition_name}<#{constructors}#{opts}>"
+            end
+          end
+        end
+
+        private
+
+        def visit_constructors(composition)
+          visit_constructor(composition.left) do |left|
+            visit_constructor(composition.right) do |right|
+              yield "#{left} #{@composition_class.operator} #{right}"
+            end
+          end
+        end
+
+        def visit_constructor(type, &block)
+          case type
+          when @composition_class
+            visit_constructors(type, &block)
+          else
+            @printer.visit(type, &block)
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/dry/types/result.rb b/lib/dry/types/result.rb
index fbed502..b155adc 100644
--- a/lib/dry/types/result.rb
+++ b/lib/dry/types/result.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/equalizer"
-
 module Dry
   module Types
     # Result class used by {Type#try}
diff --git a/lib/dry/types/schema.rb b/lib/dry/types/schema.rb
index fa08094..1c39da3 100644
--- a/lib/dry/types/schema.rb
+++ b/lib/dry/types/schema.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/fn_container"
-
 module Dry
   module Types
     # The built-in Hash type can be defined in terms of keys and associated types
@@ -92,6 +90,8 @@ module Dry
       # @return [Object] if coercion fails and a block is given
       #
       # @api public
+      # rubocop:disable Metrics/AbcSize
+      # rubocop:disable Metrics/PerceivedComplexity
       def try(input)
         if primitive?(input)
           success = true
@@ -138,6 +138,8 @@ module Dry
           failure
         end
       end
+      # rubocop:enable Metrics/AbcSize
+      # rubocop:enable Metrics/PerceivedComplexity
 
       # @param meta [Boolean] Whether to dump the meta to the AST
       #
@@ -167,7 +169,7 @@ module Dry
       # @return [Schema]
       #
       # @api public
-      def strict(strict = true)
+      def strict(strict = true) # rubocop:disable Style/OptionalBooleanParameter
         with(strict: strict)
       end
 
@@ -368,7 +370,7 @@ module Dry
       # Try to add missing keys to the hash
       #
       # @api private
-      def resolve_missing_keys(hash, options)
+      def resolve_missing_keys(hash, options) # rubocop:disable Metrics/PerceivedComplexity
         skip_missing = options.fetch(:skip_missing, false)
         resolve_defaults = options.fetch(:resolve_defaults, true)
 
diff --git a/lib/dry/types/schema/key.rb b/lib/dry/types/schema/key.rb
index a3e4f22..3d14450 100644
--- a/lib/dry/types/schema/key.rb
+++ b/lib/dry/types/schema/key.rb
@@ -1,8 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/core/equalizer"
-require "dry/core/deprecations"
-
 module Dry
   module Types
     # Schema is a hash with explicit member types defined
@@ -15,7 +12,7 @@ module Dry
       #
       # @see Dry::Types::Schema
       class Key
-        extend ::Dry::Core::Deprecations[:'dry-types']
+        extend ::Dry::Core::Deprecations[:"dry-types"]
         include Type
         include Dry::Equalizer(:name, :type, :options, inspect: false, immutable: true)
         include Decorator
diff --git a/lib/dry/types/sum.rb b/lib/dry/types/sum.rb
index 22f6857..2dd60c3 100644
--- a/lib/dry/types/sum.rb
+++ b/lib/dry/types/sum.rb
@@ -1,71 +1,15 @@
 # frozen_string_literal: true
 
-require "dry/core/equalizer"
-require "dry/types/options"
-require "dry/types/meta"
-
 module Dry
   module Types
     # Sum type
     #
     # @api public
     class Sum
-      include Type
-      include Builder
-      include Options
-      include Meta
-      include Printable
-      include Dry::Equalizer(:left, :right, :options, :meta, inspect: false, immutable: true)
-
-      # @return [Type]
-      attr_reader :left
-
-      # @return [Type]
-      attr_reader :right
-
-      # @api private
-      class Constrained < Sum
-        # @return [Dry::Logic::Operations::Or]
-        def rule
-          left.rule | right.rule
-        end
-
-        # @return [true]
-        def constrained?
-          true
-        end
-      end
-
-      # @param [Type] left
-      # @param [Type] right
-      # @param [Hash] options
-      #
-      # @api private
-      def initialize(left, right, **options)
-        super
-        @left, @right = left, right
-        freeze
-      end
+      include Composition
 
-      # @return [String]
-      #
-      # @api public
-      def name
-        [left, right].map(&:name).join(" | ")
-      end
-
-      # @return [false]
-      #
-      # @api public
-      def default?
-        false
-      end
-
-      # @return [false]
-      #
-      # @api public
-      def constrained?
-        false
+      def self.operator
+        :|
       end
 
       # @return [Boolean]
@@ -108,26 +52,6 @@ module Dry
         end
       end
 
-      # @api private
-      def success(input)
-        if left.valid?(input)
-          left.success(input)
-        elsif right.valid?(input)
-          right.success(input)
-        else
-          raise ArgumentError, "Invalid success value '#{input}' for #{inspect}"
-        end
-      end
-
-      # @api private
-      def failure(input, _error = nil)
-        if !left.valid?(input)
-          left.failure(input, left.try(input).error)
-        else
-          right.failure(input, right.try(input).error)
-        end
-      end
-
       # @param [Object] value
       #
       # @return [Boolean]
@@ -153,13 +77,6 @@ module Dry
         end
       end
 
-      # @see Nominal#to_ast
-      #
-      # @api public
-      def to_ast(meta: true)
-        [:sum, [left.to_ast(meta: meta), right.to_ast(meta: meta), meta ? self.meta : EMPTY_HASH]]
-      end
-
       # @param [Hash] options
       #
       # @return [Constrained,Sum]
@@ -174,15 +91,6 @@ module Dry
           super
         end
       end
-
-      # Wrap the type with a proc
-      #
-      # @return [Proc]
-      #
-      # @api public
-      def to_proc
-        proc { |value| self.(value) }
-      end
     end
   end
 end
diff --git a/lib/dry/types/type.rb b/lib/dry/types/type.rb
index e9feb78..cf24be6 100644
--- a/lib/dry/types/type.rb
+++ b/lib/dry/types/type.rb
@@ -1,14 +1,12 @@
 # frozen_string_literal: true
 
-require "dry/core/deprecations"
-
 module Dry
   module Types
     # Common Type module denoting an object is a Type
     #
     # @api public
     module Type
-      extend ::Dry::Core::Deprecations[:'dry-types']
+      extend ::Dry::Core::Deprecations[:"dry-types"]
 
       deprecate(:safe, :lax)
 
diff --git a/lib/dry/types/version.rb b/lib/dry/types/version.rb
index 2bdfa93..05c533e 100644
--- a/lib/dry/types/version.rb
+++ b/lib/dry/types/version.rb
@@ -2,6 +2,6 @@
 
 module Dry
   module Types
-    VERSION = "1.5.1"
+    VERSION = "1.7.1"
   end
 end
diff --git a/project.yml b/project.yml
index ba85075..2716946 100644
--- a/project.yml
+++ b/project.yml
@@ -1,4 +1,5 @@
 name: dry-types
+custom_ci: true
 codacy_id: f2d71613195f4da993acb9ac9d6ea336
 gemspec:
   authors: ["Piotr Solnica"]
@@ -9,10 +10,9 @@ gemspec:
     - rake
     - rspec
     - yard
-    - [dry-monads, "~> 1.0"]
   runtime_dependencies:
     - [concurrent-ruby, "~> 1.0"]
-    - [dry-container, "~> 0.3"]
-    - [dry-core, "~> 0.5", ">= 0.5"]
-    - [dry-inflector, "~> 0.1", ">= 0.1.2"]
-    - [dry-logic, "~> 1.0", ">= 1.0.2"]
+    - [zeitwerk, "~> 2.6"]
+    - [dry-core, "~> 1.0"]
+    - [dry-inflector, "~> 1.0"]
+    - [dry-logic, "~> 1.4"]
diff --git a/spec/dry/types/array_spec.rb b/spec/dry/types/array_spec.rb
index de0a3d5..21bc06a 100644
--- a/spec/dry/types/array_spec.rb
+++ b/spec/dry/types/array_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe Dry::Types::Array do
 
         it "an invalid type with a block" do
           expect(
-            array.try("X") { |x| "error: " + x.to_s }
+            array.try("X") { |x| "error: #{x}" }
           ).to eql("error: X is not an array")
         end
       end
diff --git a/spec/dry/types/builder_spec.rb b/spec/dry/types/builder_spec.rb
index e9c1660..4b3235b 100644
--- a/spec/dry/types/builder_spec.rb
+++ b/spec/dry/types/builder_spec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/builder"
-
 RSpec.describe Dry::Types::Builder do
   let(:base) { Dry::Types["string"].constrained(min_size: 4) }
 
diff --git a/spec/dry/types/compiler_spec.rb b/spec/dry/types/compiler_spec.rb
index 78f7d6e..10097f3 100644
--- a/spec/dry/types/compiler_spec.rb
+++ b/spec/dry/types/compiler_spec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/compiler"
-
 RSpec.describe Dry::Types::Compiler, "#call" do
   subject(:compiler) { Dry::Types::Compiler.new(Dry::Types) }
 
@@ -263,8 +261,8 @@ RSpec.describe Dry::Types::Compiler, "#call" do
       :constrained, [[:nominal, [Integer, {}]],
                      [:or,
                       [
-                        [:predicate, [:lt?, [[:num, 5], [:input, Undefined]]]],
-                        [:predicate, [:gt?, [[:num, 18], [:input, Undefined]]]]
+                        [:predicate, [:lt?, [[:num, 5], [:input, undefined]]]],
+                        [:predicate, [:gt?, [[:num, 18], [:input, undefined]]]]
                       ]], {}]
     ]
 
@@ -324,10 +322,10 @@ RSpec.describe Dry::Types::Compiler, "#call" do
       :map, [
         [:constrained,
          [[:nominal, [String, {}]],
-          [:predicate, [:type?, [[:type, String], [:input, Undefined]]]]]],
+          [:predicate, [:type?, [[:type, String], [:input, undefined]]]]]],
         [:constrained,
          [[:nominal, [Integer, {}]],
-          [:predicate, [:type?, [[:type, Integer], [:input, Undefined]]]]]],
+          [:predicate, [:type?, [[:type, Integer], [:input, undefined]]]]]],
         abc: 123, foo: "bar"
       ]
     ])
diff --git a/spec/dry/types/constrained_spec.rb b/spec/dry/types/constrained_spec.rb
index 92c21d9..50e25da 100644
--- a/spec/dry/types/constrained_spec.rb
+++ b/spec/dry/types/constrained_spec.rb
@@ -118,7 +118,7 @@ RSpec.describe Dry::Types::Constrained do
   context "with a wrapping constructor" do
     subject(:type) do
       Dry::Types["coercible.integer"].constructor { |input, t|
-        t.(input + "0") + 10
+        t.("#{input}0") + 10
       }.constrained(gt: 300)
     end
 
diff --git a/spec/dry/types/constructor_spec.rb b/spec/dry/types/constructor_spec.rb
index 8fec9aa..85ca541 100644
--- a/spec/dry/types/constructor_spec.rb
+++ b/spec/dry/types/constructor_spec.rb
@@ -179,7 +179,7 @@ RSpec.describe Dry::Types::Constructor do
     context "wrapping" do
       context "simple case of wrapping contructor" do
         let(:type) do
-          super().constructor { |input, type| type.(input + 1) + "8" }
+          super().constructor { |input, type| "#{type.(input + 1)}8" }
         end
 
         it "wraps" do
diff --git a/spec/dry/types/default_spec.rb b/spec/dry/types/default_spec.rb
index cd73a4e..fed2c4f 100644
--- a/spec/dry/types/default_spec.rb
+++ b/spec/dry/types/default_spec.rb
@@ -10,6 +10,10 @@ RSpec.describe Dry::Types::Builder, "#default" do
       expect(type[]).to eql("foo")
     end
 
+    it "returns default value when Undefined is passed" do
+      expect(type[undefined]).to eql("foo")
+    end
+
     it "aliases #[] as #call" do
       expect(type.call).to eql("foo")
     end
@@ -165,7 +169,7 @@ RSpec.describe Dry::Types::Builder, "#default" do
     end
 
     it "returns true if value is Undefined" do
-      expect(type.valid?(Undefined)).to eq true
+      expect(type.valid?(undefined)).to eq true
     end
 
     it "returns true if no value is passed" do
@@ -175,7 +179,7 @@ RSpec.describe Dry::Types::Builder, "#default" do
 
   context "with a constructor" do
     describe "returning Undefined" do
-      let(:non_empty_string) { Dry::Types["nominal.string"].constructor { |str| str.empty? ? Undefined : str } }
+      let(:non_empty_string) { Dry::Types["nominal.string"].constructor { |str| str.empty? ? undefined : str } }
       subject(:type) { non_empty_string.default("empty") }
 
       it "returns default value on empty input" do
diff --git a/spec/dry/types/implication_spec.rb b/spec/dry/types/implication_spec.rb
new file mode 100644
index 0000000..5170949
--- /dev/null
+++ b/spec/dry/types/implication_spec.rb
@@ -0,0 +1,230 @@
+# frozen_string_literal: true
+
+RSpec.describe Dry::Types::Implication do
+  let(:t) { Dry.Types }
+
+  let(:role_id_schema) { t.Hash(id: t::Strict::String) }
+  let(:role_title_schema) { t.Hash(title: t::Strict::String) }
+  let(:role_schema) { role_id_schema > role_title_schema }
+
+  describe "common nominal behavior" do
+    subject(:type) { role_schema }
+
+    it_behaves_like "Dry::Types::Nominal#meta"
+    it_behaves_like "Dry::Types::Nominal without primitive"
+    it_behaves_like "a composable constructor"
+
+    it "is frozen" do
+      expect(type).to be_frozen
+    end
+  end
+
+  describe "#[]" do
+    it "works with two pass-through types" do
+      type = t::Nominal::Hash > t.Hash(foo: t::Nominal::Integer)
+
+      expect(type[{foo: ""}]).to eq({foo: ""})
+      expect(type[{foo: 312}]).to eq({foo: 312})
+    end
+
+    it "works with two strict types" do
+      type = t::Strict::Hash > t.Hash(foo: t::Strict::Integer)
+
+      expect(type[{foo: 312}]).to eq({foo: 312})
+
+      expect { type[{foo: "312"}] }.to raise_error(Dry::Types::CoercionError)
+    end
+
+    it "is aliased as #call" do
+      type = t::Nominal::Hash > t.Hash(foo: t::Nominal::Integer)
+
+      expect(type.call({foo: ""})).to eq({foo: ""})
+      expect(type.call({foo: 312})).to eq({foo: 312})
+    end
+
+    it "works with two constructor & constrained types" do
+      left = t.Array(t::Strict::Hash)
+      right = t.Array(t.Hash(foo: t::Nominal::Integer))
+
+      type = left > right
+
+      expect(type[[{foo: 312}]]).to eql([{foo: 312}])
+    end
+
+    it "works with two complex types with constraints" do
+      type =
+        t
+          .Array(t.Array(t::Coercible::String.constrained(min_size: 5)).constrained(size: 2))
+          .constrained(min_size: 1) >
+        t
+          .Array(t.Array(t::Coercible::String.constrained(format: /foo/)).constrained(size: 2))
+          .constrained(min_size: 2)
+
+      expect(type.([%w[foofoo barfoo], %w[bazfoo fooqux]])).to eql(
+        [%w[foofoo barfoo], %w[bazfoo fooqux]]
+      )
+
+      expect { type[[["hello there", "my friend"]]] }.to raise_error(Dry::Types::ConstraintError, /min_size\?\(2/)
+
+      expect { type[[%w[hello there], ["my good", "friend"]]] }.to raise_error(Dry::Types::ConstraintError, %r{/foo/})
+    end
+  end
+
+  describe "#try" do
+    subject(:type) { role_schema }
+
+    it "returns success when value passed" do
+      expect(type.try({id: "id", title: "title"})).to be_success
+    end
+
+    it "returns failure when value did not pass" do
+      expect(type.try({id: "id"})).to be_failure
+    end
+  end
+
+  describe "#success" do
+    subject(:type) { role_schema }
+
+    it "returns success when value passed" do
+      expect(type.success({id: "id", title: "title"})).to be_success
+    end
+
+    it "raises ArgumentError when non of the types have a valid input" do
+      expect { type.success({id: "id"}) }.to raise_error(ArgumentError, /Invalid success value '{:id=>"id"}' /)
+    end
+  end
+
+  describe "#failure" do
+    subject(:type) { t::Strict::Integer > t::Strict::String }
+
+    it "returns failure when invalid value is passed" do
+      expect(type.failure(1)).to be_failure
+    end
+  end
+
+  describe "#===" do
+    subject(:type) { role_schema }
+
+    it "returns boolean" do
+      expect(type.===({id: "id", title: "title"})).to eql(true)
+      expect(type.===({id: "id"})).to eql(false)
+    end
+
+    context "in case statement" do
+      let(:value) do
+        case {id: "id", title: "title"}
+        when type
+          "accepted"
+        else
+          "invalid"
+        end
+      end
+
+      it "returns correct value" do
+        expect(value).to eql("accepted")
+      end
+    end
+  end
+
+  describe "#default" do
+    it "returns a default value implication type" do
+      type = (t::Nominal::Nil > t::Nominal::Nil).default("foo")
+
+      expect(type.call).to eql("foo")
+    end
+  end
+
+  describe "#constructor" do
+    let(:type) do
+      (t::Nominal::String > t::Nominal::Nil).constructor do |input|
+        input ? "#{input} world" : input
+      end
+    end
+
+    it "returns the correct value" do
+      expect(type.call("hello")).to eql("hello world")
+      expect(type.call(nil)).to eql(nil)
+      expect(type.call(10)).to eql("10 world")
+    end
+
+    it "returns if value is valid" do
+      expect(type.valid?("hello")).to eql(true)
+      expect(type.valid?(nil)).to eql(true)
+      expect(type.valid?(10)).to eql(true)
+    end
+  end
+
+  describe "#rule" do
+    let(:type) { t::Strict::Integer > t::Strict::String }
+
+    it "returns a rule" do
+      rule = type.rule
+
+      expect(rule.(true)).to be_success
+      expect(rule.(1)).to be_failure
+    end
+  end
+
+  describe "#to_s" do
+    context "shallow implication" do
+      let(:type) { t::Nominal::String > t::Nominal::Integer }
+
+      it "returns string representation of the type" do
+        expect(type.to_s).to eql("#<Dry::Types[Implication<Nominal<String> > Nominal<Integer>>]>")
+      end
+    end
+
+    context "constrained" do
+      let(:type) { t::Nominal::String.constrained(format: /foo/) > t::Nominal::String.constrained(min_size: 4) }
+
+      it "returns string representation of the type" do
+        expect(type.to_s).to eql(
+          "#<Dry::Types[Implication<" \
+            "Constrained<Nominal<String> rule=[format?(/foo/)]> > "\
+            "Constrained<Nominal<String> rule=[min_size?(4)]>>]>"
+        )
+      end
+    end
+
+    context "implication tree" do
+      let(:type) { t::Nominal::String > (t::Nominal::Integer > (t::Nominal::Date > t::Nominal::Time)) }
+
+      it "returns string representation of the type" do
+        expect(type.to_s).to eql(
+          "#<Dry::Types[Implication<" \
+            "Nominal<String> > " \
+            "Nominal<Integer> > " \
+            "Nominal<Date> > " \
+            "Nominal<Time>" \
+            ">]>"
+        )
+      end
+    end
+  end
+
+  context "with map type" do
+    let(:map_type) { t::Nominal::Hash.map(t::Nominal::Symbol, t::Nominal::String) }
+
+    let(:schema_type) { t.Hash(foo: t::Strict::String) }
+
+    subject(:type) { map_type > schema_type }
+
+    it "rejects invalid input" do
+      expect(type.valid?({foo: 1, bar: 1})).to be false
+      expect { type[{foo: 1, bar: 1}] }.to raise_error(Dry::Types::SchemaError)
+    end
+  end
+
+  describe "#meta" do
+    context "optional types" do
+      let(:meta) { {foo: :bar} }
+
+      subject(:type) { t::Nominal::String.optional }
+
+      it "uses meta from the right branch" do
+        expect(type.meta(meta).meta).to eql(meta)
+        expect(type.meta(meta).right.meta).to eql(meta)
+      end
+    end
+  end
+end
diff --git a/spec/dry/types/intersection_spec.rb b/spec/dry/types/intersection_spec.rb
new file mode 100644
index 0000000..10ceba2
--- /dev/null
+++ b/spec/dry/types/intersection_spec.rb
@@ -0,0 +1,245 @@
+# frozen_string_literal: true
+
+RSpec.describe Dry::Types::Intersection do
+  let(:t) { Dry.Types }
+
+  let(:callable_type) { t.Interface(:call) }
+  let(:procable_type) { t.Interface(:to_proc) }
+  let(:function_type) { callable_type & procable_type }
+
+  describe "common nominal behavior" do
+    subject(:type) { function_type }
+
+    it_behaves_like "Dry::Types::Nominal#meta"
+    it_behaves_like "Dry::Types::Nominal without primitive"
+    it_behaves_like "a composable constructor"
+
+    it "is frozen" do
+      expect(type).to be_frozen
+    end
+  end
+
+  it_behaves_like "a constrained type" do
+    let(:type) { function_type }
+
+    it_behaves_like "a composable constructor"
+  end
+
+  describe "#[]" do
+    it "works with two pass-through types" do
+      type = t::Nominal::Hash & t.Hash(foo: t::Nominal::Integer)
+
+      expect(type[{foo: ""}]).to eq({foo: ""})
+      expect(type[{foo: 312}]).to eq({foo: 312})
+    end
+
+    it "works with two strict types" do
+      type = t::Strict::Hash & t.Hash(foo: t::Strict::Integer)
+
+      expect(type[{foo: 312}]).to eq({foo: 312})
+
+      expect { type[312] }.to raise_error(Dry::Types::CoercionError)
+    end
+
+    it "is aliased as #call" do
+      type = t::Nominal::Hash & t.Hash(foo: t::Nominal::Integer)
+
+      expect(type.call({foo: ""})).to eq({foo: ""})
+      expect(type.call({foo: 312})).to eq({foo: 312})
+    end
+
+    it "works with two constructor & constrained types" do
+      left = t.Array(t::Strict::Hash)
+      right = t.Array(t.Hash(foo: t::Nominal::Integer))
+
+      type = left & right
+
+      expect(type[[{foo: 312}]]).to eql([{foo: 312}])
+    end
+
+    it "works with two complex types with constraints" do
+      type =
+        t
+          .Array(t.Array(t::Coercible::String.constrained(min_size: 5)).constrained(size: 2))
+          .constrained(min_size: 1) &
+        t
+          .Array(t.Array(t::Coercible::String.constrained(format: /foo/)).constrained(size: 2))
+          .constrained(min_size: 2)
+
+      expect(type.([%w[foofoo barfoo], %w[bazfoo fooqux]])).to eql(
+        [%w[foofoo barfoo], %w[bazfoo fooqux]]
+      )
+
+      expect { type[:oops] }.to raise_error(Dry::Types::ConstraintError, /:oops/)
+
+      expect { type[[]] }.to raise_error(Dry::Types::ConstraintError, /\[\]/)
+
+      expect { type.([%i[foo]]) }.to raise_error(Dry::Types::ConstraintError, /\[:foo\]/)
+
+      expect { type.([[1], [2]]) }.to raise_error(Dry::Types::ConstraintError, /2, \[1\]/)
+
+      expect { type.([%w[foofoo barfoo], %w[bazfoo foo]]) }.to raise_error(
+        Dry::Types::ConstraintError,
+        /min_size\?\(5, "foo"\)/
+      )
+    end
+  end
+
+  describe "#try" do
+    subject(:type) { function_type }
+
+    it "returns success when value passed" do
+      expect(type.try(-> {})).to be_success
+    end
+
+    it "returns failure when value did not pass" do
+      expect(type.try(:foo)).to be_failure
+    end
+  end
+
+  describe "#success" do
+    subject(:type) { function_type }
+
+    it "returns success when value passed" do
+      expect(type.success(-> {})).to be_success
+    end
+
+    it "raises ArgumentError when non of the types have a valid input" do
+      expect { type.success("foo") }.to raise_error(ArgumentError, /Invalid success value 'foo' /)
+    end
+  end
+
+  describe "#failure" do
+    subject(:type) { Dry::Types["integer"] & Dry::Types["string"] }
+
+    it "returns failure when invalid value is passed" do
+      expect(type.failure(true)).to be_failure
+    end
+  end
+
+  describe "#===" do
+    subject(:type) { function_type }
+
+    it "returns boolean" do
+      expect(type.===(-> {})).to eql(true)
+      expect(type.===(nil)).to eql(false) # rubocop:disable Style/NilComparison
+    end
+
+    context "in case statement" do
+      let(:value) do
+        case -> {}
+        when type
+          "accepted"
+        else
+          "invalid"
+        end
+      end
+
+      it "returns correct value" do
+        expect(value).to eql("accepted")
+      end
+    end
+  end
+
+  describe "#default" do
+    it "returns a default value intersection type" do
+      type = (t::Nominal::Nil & t::Nominal::Nil).default("foo")
+
+      expect(type.call).to eql("foo")
+    end
+  end
+
+  describe "#constructor" do
+    let(:type) do
+      (t::Nominal::String & t::Nominal::Nil).constructor do |input|
+        input ? "#{input} world" : input
+      end
+    end
+
+    it "returns the correct value" do
+      expect(type.call("hello")).to eql("hello world")
+      expect(type.call(nil)).to eql(nil)
+      expect(type.call(10)).to eql("10 world")
+    end
+
+    it "returns if value is valid" do
+      expect(type.valid?("hello")).to eql(true)
+      expect(type.valid?(nil)).to eql(true)
+      expect(type.valid?(10)).to eql(true)
+    end
+  end
+
+  describe "#rule" do
+    let(:type) { function_type }
+
+    it "returns a rule" do
+      rule = type.rule
+
+      expect(rule.(-> {})).to be_success
+      expect(rule.(nil)).to be_failure
+    end
+  end
+
+  describe "#to_s" do
+    context "shallow intersection" do
+      let(:type) { t::Nominal::String & t::Nominal::Integer }
+
+      it "returns string representation of the type" do
+        expect(type.to_s).to eql("#<Dry::Types[Intersection<Nominal<String> & Nominal<Integer>>]>")
+      end
+    end
+
+    context "constrained" do
+      let(:type) { t::Nominal::String.constrained(format: /foo/) & t::Nominal::String.constrained(min_size: 4) }
+
+      it "returns string representation of the type" do
+        expect(type.to_s).to eql(
+          "#<Dry::Types[Intersection<" \
+            "Constrained<Nominal<String> rule=[format?(/foo/)]> & "\
+            "Constrained<Nominal<String> rule=[min_size?(4)]>>]>"
+        )
+      end
+    end
+
+    context "intersection tree" do
+      let(:type) { t::Nominal::String & t::Nominal::Integer & t::Nominal::Date & t::Nominal::Time }
+
+      it "returns string representation of the type" do
+        expect(type.to_s).to eql(
+          "#<Dry::Types[Intersection<" \
+            "Nominal<String> & " \
+            "Nominal<Integer> & " \
+            "Nominal<Date> & " \
+            "Nominal<Time>" \
+            ">]>"
+        )
+      end
+    end
+  end
+
+  context "with map type" do
+    let(:map_type) { t::Nominal::Hash.map(t::Nominal::Symbol, t::Nominal::String) }
+
+    let(:schema_type) { t.Hash(foo: t::Strict::String) }
+
+    subject(:type) { map_type & schema_type }
+
+    it "rejects invalid input" do
+      expect(type.valid?({foo: 1, bar: 1})).to be false
+      expect { type[{foo: 1, bar: 1}] }.to raise_error(Dry::Types::SchemaError)
+    end
+  end
+
+  describe "#meta" do
+    context "optional types" do
+      let(:meta) { {foo: :bar} }
+
+      subject(:type) { Dry::Types["string"].optional }
+
+      it "uses meta from the right branch" do
+        expect(type.meta(meta).meta).to eql(meta)
+        expect(type.meta(meta).right.meta).to eql(meta)
+      end
+    end
+  end
+end
diff --git a/spec/dry/types/predicate_inferrer_spec.rb b/spec/dry/types/predicate_inferrer_spec.rb
index 84597ea..d2ffa86 100644
--- a/spec/dry/types/predicate_inferrer_spec.rb
+++ b/spec/dry/types/predicate_inferrer_spec.rb
@@ -1,8 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/predicate_inferrer"
-require "dry/types/predicate_registry"
-
 RSpec.describe Dry::Types::PredicateInferrer, "#[]" do
   subject(:inferrer) do
     Dry::Types::PredicateInferrer.new(Dry::Types::PredicateRegistry.new)
@@ -173,7 +170,7 @@ RSpec.describe Dry::Types::PredicateInferrer, "#[]" do
       end
 
       it "should be removed once 2.0 is released" do
-        if Dry::Types::VERSION.start_with?('2.')
+        if Dry::Types::VERSION.start_with?("2.")
           raise "Remove infer_predicate_by_class_name"
         end
       end
diff --git a/spec/dry/types/predicate_registry_spec.rb b/spec/dry/types/predicate_registry_spec.rb
index 4ff2f29..0a78331 100644
--- a/spec/dry/types/predicate_registry_spec.rb
+++ b/spec/dry/types/predicate_registry_spec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/predicate_registry"
-
 RSpec.describe Dry::Types::PredicateRegistry do
   subject(:predicate_registry) { Dry::Types::PredicateRegistry.new }
 
diff --git a/spec/dry/types/primitive_inferrer_spec.rb b/spec/dry/types/primitive_inferrer_spec.rb
index f8b7f0e..16e3a53 100644
--- a/spec/dry/types/primitive_inferrer_spec.rb
+++ b/spec/dry/types/primitive_inferrer_spec.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require "dry/types/primitive_inferrer"
-
 RSpec.describe Dry::Types::PrimitiveInferrer, "#[]" do
   subject(:inferrer) do
     Dry::Types::PrimitiveInferrer.new
diff --git a/spec/dry/types/schema_spec.rb b/spec/dry/types/schema_spec.rb
index 28e3e72..c97aea5 100644
--- a/spec/dry/types/schema_spec.rb
+++ b/spec/dry/types/schema_spec.rb
@@ -267,9 +267,12 @@ RSpec.describe Dry::Types::Schema do
       expect {
         hash.call(name: :Jane, age: "oops", active: true, phone: [])
       }.to raise_error(
-        Dry::Types::SchemaError,
-        '"oops" (String) has invalid type for :age violates '\
-        'constraints (type?(Integer, "oops") failed)'
+        an_instance_of(Dry::Types::SchemaError).and(having_attributes(
+                                                      message: '"oops" (String) has invalid type for :age violates '\
+                                                               'constraints (type?(Integer, "oops") failed)',
+                                                      key: :age,
+                                                      value: "oops"
+                                                    ))
       )
     end
 
@@ -277,9 +280,12 @@ RSpec.describe Dry::Types::Schema do
       expect {
         hash.schema(age: "coercible.integer").call(name: :Jane, age: nil, active: true, phone: [])
       }.to raise_error(
-        Dry::Types::SchemaError,
-        "nil (NilClass) has invalid type for :age violates constraints"\
-        " (can't convert nil into Integer failed)"
+        an_instance_of(Dry::Types::SchemaError).and(having_attributes(
+                                                      message: "nil (NilClass) has invalid type for :age violates constraints"\
+                                                               " (can't convert nil into Integer failed)",
+                                                      key: :age,
+                                                      value: nil
+                                                    ))
       )
     end
 
@@ -287,9 +293,12 @@ RSpec.describe Dry::Types::Schema do
       expect {
         hash.schema(age: "coercible.integer").call(name: :Jane, age: "oops", active: true, phone: [])
       }.to raise_error(
-        Dry::Types::SchemaError,
-        '"oops" (String) has invalid type for :age violates constraints'\
-        ' (invalid value for Integer(): "oops" failed)'
+        an_instance_of(Dry::Types::SchemaError).and(having_attributes(
+                                                      message: '"oops" (String) has invalid type for :age violates constraints'\
+                                                               ' (invalid value for Integer(): "oops" failed)',
+                                                      key: :age,
+                                                      value: "oops"
+                                                    ))
       )
     end
 
@@ -542,7 +551,7 @@ RSpec.describe Dry::Types::Schema do
 
     it "preserves key transformations from the caller schema" do
       schema1 = Dry::Types["hash"].schema(foo: "string").with_key_transform(&:to_sym)
-      transf2 = ->(key) { (key + "_").to_sym }
+      transf2 = ->(key) { :"#{key}_" }
       schema2 = Dry::Types["hash"].schema(bar: "string").with_key_transform(transf2)
 
       schema = schema1.merge(schema2)
diff --git a/spec/dry/types/sum_spec.rb b/spec/dry/types/sum_spec.rb
index b2a3f39..648755d 100644
--- a/spec/dry/types/sum_spec.rb
+++ b/spec/dry/types/sum_spec.rb
@@ -111,8 +111,7 @@ RSpec.describe Dry::Types::Sum do
 
       expect { type.([%i[foo]]) }.to raise_error(Dry::Types::ConstraintError, /\[:foo\]/)
 
-      expect { type.([[1], [2]]) }.to raise_error(Dry::Types::ConstraintError, /[1]/)
-      expect { type.([[1], [2]]) }.to raise_error(Dry::Types::ConstraintError, /[2]/)
+      expect { type.([[1], [2]]) }.to raise_error(Dry::Types::ConstraintError, /2, \[1\]/)
     end
   end
 
@@ -155,7 +154,7 @@ RSpec.describe Dry::Types::Sum do
 
     it "returns boolean" do
       expect(type.===("hello")).to eql(true)
-      expect(type.===(nil)).to eql(false)
+      expect(type.===(nil)).to eql(false) # rubocop:disable Style/NilComparison
     end
 
     context "in case statement" do
@@ -194,7 +193,7 @@ RSpec.describe Dry::Types::Sum do
   end
 
   describe "#constructor" do
-    let(:type) { (Dry::Types["nominal.string"] | Dry::Types["nominal.nil"]).constructor { |input| input ? input.to_s + " world" : input } }
+    let(:type) { (Dry::Types["nominal.string"] | Dry::Types["nominal.nil"]).constructor { |input| input ? "#{input} world" : input } }
 
     it "returns the correct value" do
       expect(type.call("hello")).to eql("hello world")
@@ -258,6 +257,21 @@ RSpec.describe Dry::Types::Sum do
       end
     end
 
+    context "constrained" do
+      let(:type) do
+        Dry::Types["nominal.string"].constrained(format: /foo/) |
+          Dry::Types["nominal.string"].constrained(min_size: 4)
+      end
+
+      it "returns string representation of the type" do
+        expect(type.to_s).to eql(
+          "#<Dry::Types[Sum<" \
+            "Constrained<Nominal<String> rule=[format?(/foo/)]> | "\
+            "Constrained<Nominal<String> rule=[min_size?(4)]>>]>"
+        )
+      end
+    end
+
     context "sum tree" do
       let(:type) do
         Dry::Types["nominal.string"] | Dry::Types["nominal.integer"] |
diff --git a/spec/dry/types/to_ast_spec.rb b/spec/dry/types/to_ast_spec.rb
index d2ad3dd..ee29a8e 100644
--- a/spec/dry/types/to_ast_spec.rb
+++ b/spec/dry/types/to_ast_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe Dry::Types, "#to_ast" do
       expect(type.to_ast).to eql(
         [:constrained, [
           [:nominal, [Integer, {}]],
-          [:predicate, [:type?, [[:type, Integer], [:input, Undefined]]]]
+          [:predicate, [:type?, [[:type, Integer], [:input, undefined]]]]
         ]]
       )
     end
@@ -65,7 +65,7 @@ RSpec.describe Dry::Types, "#to_ast" do
       expect(type_with_meta.to_ast).to eql(
         [:constrained, [
           [:nominal, [Integer, key: :value]],
-          [:predicate, [:type?, [[:type, Integer], [:input, Undefined]]]]
+          [:predicate, [:type?, [[:type, Integer], [:input, undefined]]]]
         ]]
       )
     end
@@ -127,12 +127,12 @@ RSpec.describe Dry::Types, "#to_ast" do
                 [
                   [
                     :predicate,
-                    [:type?, [[:type, String], [:input, Undefined]]]
+                    [:type?, [[:type, String], [:input, undefined]]]
                   ],
                   [
                     :predicate,
                     [:included_in?,
-                     [[:list, %w[draft published archived]], [:input, Undefined]]]
+                     [[:list, %w[draft published archived]], [:input, undefined]]]
                   ]
                 ]
               ]
diff --git a/spec/dry/types_spec.rb b/spec/dry/types_spec.rb
index 56121d7..67d1f68 100644
--- a/spec/dry/types_spec.rb
+++ b/spec/dry/types_spec.rb
@@ -1,6 +1,15 @@
 # frozen_string_literal: true
 
 RSpec.describe Dry::Types do
+  describe ".loader" do
+    it "can eagerly load this library" do
+      Dry::Types.loader.eager_load
+    ensure
+      Dry::Types.loader.unload
+      Dry::Types.loader.setup
+    end
+  end
+
   describe ".register" do
     it "registers a new type constructor" do
       module Test
@@ -64,6 +73,8 @@ RSpec.describe Dry::Types do
       constructed = Dry::Types["integer"].or_nil
 
       expect(constructed.("123")).to be_nil
+    ensure
+      Dry::Types::Builder.remove_method(:or_nil)
     end
 
     it "has support for arguments" do
@@ -71,6 +82,8 @@ RSpec.describe Dry::Types do
       constructed = Dry::Types["integer"].or(300)
 
       expect(constructed.("123")).to eql(300)
+    ensure
+      Dry::Types::Builder.remove_method(:or)
     end
   end
 end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 16a5153..dc3d312 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -13,12 +13,9 @@ require "dry-types"
 begin
   require "pry-byebug"
 rescue LoadError; end
-Dir[Pathname(__dir__).join("shared/*.rb")].each(&method(:require))
+Dir[Pathname(__dir__).join("shared/*.rb")].sort.each(&method(:require))
 require "dry/types/spec/types"
 
-Undefined = Dry::Core::Constants::Undefined
-
-require "dry/core/deprecations"
 Dry::Core::Deprecations.set_logger!(SPEC_ROOT.join("../log/deprecations.log"))
 
 RSpec.configure do |config|
@@ -33,6 +30,12 @@ RSpec.configure do |config|
   config.before { stub_const("Test", Module.new) }
 
   config.order = "random"
+
+  config.include Module.new {
+    extend RSpec::SharedContext
+
+    let(:undefined) { Dry::Core::Constants::Undefined }
+  }
 end
 
 srand RSpec.configuration.seed

More details

Full run details

Historical runs