diff --git a/core/lib/Drupal/Core/Access/AccessResult.php b/core/lib/Drupal/Core/Access/AccessResult.php
index c87b80f..fa1fada 100644
--- a/core/lib/Drupal/Core/Access/AccessResult.php
+++ b/core/lib/Drupal/Core/Access/AccessResult.php
@@ -410,49 +410,40 @@ public function allowIfHasPermission(AccountInterface $account, $permission) {
    * {@inheritdoc}
    */
   public function orIf(AccessResultInterface $other) {
-    // If this AccessResult already is forbidden, then that already is the
-    // conclusion. We can completely disregard $other.
-    if ($this->isForbidden()) {
-      return $this;
+    if ($this->isForbidden() || $other->isForbidden()) {
+      $result = static::forbidden();
+    }
+    elseif ($this->isAllowed() || $other->isAllowed()) {
+      $result = static::allowed();
     }
-    // Otherwise, we make this AccessResult forbidden if the other is, or
-    // allowed if the other is, and we merge in the cacheability metadata if the
-    // other access result also has cacheability metadata.
     else {
-      if ($other->isForbidden()) {
-        $this->forbid();
-      }
-      else if ($other->isAllowed()) {
-        $this->allow();
-      }
-      $this->mergeCacheabilityMetadata($other);
-      return $this;
+      $result = static::create();
     }
+    $result->mergeCacheabilityMetadata($this);
+    if (!$this->isForbidden()) {
+      $result->mergeCacheabilityMetadata($other);
+    }
+    return $result;
   }
 
   /**
    * {@inheritdoc}
    */
   public function andIf(AccessResultInterface $other) {
-    // If this AccessResult already is forbidden or is merely not explicitly
-    // allowed, then that already is the conclusion. We can completely disregard
-    // $other.
-    if ($this->isForbidden() || !$this->isAllowed()) {
-      return $this;
+    if ($this->isAllowed() && $other->isAllowed()) {
+      $result = static::allowed();
+    }
+    elseif (!$this->isAllowed() && !$this->isForbidden() && !$other->isAllowed() && !$other->isForbidden()) {
+      $result = static::create();
     }
-    // Otherwise, we make this AccessResult forbidden if the other is, or not
-    // explicitly allowed if the other isn't, and we merge in the cacheability
-    // metadata if the other access result also has cacheability metadata.
     else {
-      if ($other->isForbidden()) {
-        $this->forbid();
-      }
-      else if (!$other->isAllowed()) {
-        $this->resetAccess();
-      }
-      $this->mergeCacheabilityMetadata($other);
-      return $this;
+      $result = static::forbidden();
     }
+    $result->mergeCacheabilityMetadata($this);
+    if ($this->isAllowed()) {
+      $result->mergeCacheabilityMetadata($other);
+    }
+    return $result;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Access/AccessResultInterface.php b/core/lib/Drupal/Core/Access/AccessResultInterface.php
index 5e6f5bc..e44467f 100644
--- a/core/lib/Drupal/Core/Access/AccessResultInterface.php
+++ b/core/lib/Drupal/Core/Access/AccessResultInterface.php
@@ -42,6 +42,9 @@ public function isAllowed();
   /**
    * Checks whether this access result indicates access is explicitly forbidden.
    *
+   * This is a kill switch -- both orIf and andIf will result in isForbidden()
+   * if either results are isForbidden().
+   *
    * @return bool
    */
   public function isForbidden();
@@ -51,28 +54,29 @@ public function isForbidden();
    *
    * When OR-ing two access results, the result is:
    * - isForbidden() in either ⇒ isForbidden()
-   * - isAllowed() in either ⇒ isAllowed()
-   * - neither isForbidden() nor isAllowed() => !isAllowed() && !isForbidden()
+   * - otherwise, isAllowed() in either ⇒ isAllowed()
+   * - otherwise, both are !isForbidden() && !isAllowed() ⇒
+   * !isAllowed() && !isForbidden().
    *
    * @param \Drupal\Core\Access\AccessResultInterface $other
    *   The other access result to OR this one with.
    *
-   * @return $this
+   * @return \Drupal\Core\Access\AccessResultInterface
    */
   public function orIf(AccessResultInterface $other);
 
   /**
    * Combine this access result with another using AND.
    *
-   * When OR-ing two access results, the result is:
-   * - isForbidden() in either ⇒ isForbidden()
+   * When AND-ing two access results, the result is:
    * - isAllowed() in both ⇒ isAllowed()
-   * - neither isForbidden() nor isAllowed() => !isAllowed() && !isForbidden()
+   * - both are !isForbidden() && !isAllowed() ⇒ !isAllowed() && !isForbidden()
+   * - isForbidden in every other case.
    *
    * @param \Drupal\Core\Access\AccessResultInterface $other
    *   The other access result to AND this one with.
    *
-   * @return $this
+   * @return \Drupal\Core\Access\AccessResultInterface
    */
   public function andIf(AccessResultInterface $other);
 
diff --git a/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php b/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php
index 67d25d5..c8f660b 100644
--- a/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/AccessResultTest.php
@@ -201,36 +201,31 @@ public function testAndIf() {
       ->method($this->anything());
 
     // ALLOW && ALLOW === ALLOW.
-    $access = clone $allowed;
-    $access->andIf($allowed);
+    $access = $allowed->andIf($allowed);
     $this->assertTrue($access->isAllowed());
     $this->assertFalse($access->isForbidden());
     $this->assertDefaultCacheability($access);
 
     // ALLOW && DENY === DENY.
-    $access = clone $allowed;
-    $access->andIf($no_opinion);
+    $access = $allowed->andIf($no_opinion);
     $this->assertFalse($access->isAllowed());
-    $this->assertFalse($access->isForbidden());
+    $this->assertTrue($access->isForbidden());
     $this->assertDefaultCacheability($access);
 
     // ALLOW && KILL === KILL.
-    $access = clone $allowed;
-    $access->andIf($forbidden);
+    $access = $allowed->andIf($forbidden);
     $this->assertFalse($access->isAllowed());
     $this->assertTrue($access->isForbidden());
     $this->assertDefaultCacheability($access);
 
     // DENY && * === DENY.
-    $access = clone $no_opinion;
-    $access->andIf($unused_access_result_due_to_lazy_evaluation);
+    $access = $no_opinion->andIf($unused_access_result_due_to_lazy_evaluation);
     $this->assertFalse($access->isAllowed());
     $this->assertFalse($access->isForbidden());
     $this->assertDefaultCacheability($access);
 
     // KILL && * === KILL.
-    $access = clone $forbidden;
-    $access->andIf($unused_access_result_due_to_lazy_evaluation);
+    $access = $forbidden->andIf($unused_access_result_due_to_lazy_evaluation);
     $this->assertFalse($access->isAllowed());
     $this->assertTrue($access->isForbidden());
     $this->assertDefaultCacheability($access);
@@ -248,50 +243,43 @@ public function testOrIf() {
       ->method($this->anything());
 
     // ALLOW || ALLOW === ALLOW.
-    $access = clone $allowed;
-    $access->orIf($allowed);
+    $access = $allowed->orIf($allowed);
     $this->assertTrue($access->isAllowed());
     $this->assertFalse($access->isForbidden());
     $this->assertDefaultCacheability($access);
 
     // ALLOW || DENY === ALLOW.
-    $access = clone $allowed;
-    $access->orIf($no_opinion);
+    $access = $allowed->orIf($no_opinion);
     $this->assertTrue($access->isAllowed());
     $this->assertFalse($access->isForbidden());
     $this->assertDefaultCacheability($access);
 
     // ALLOW || KILL === KILL.
-    $access = clone $allowed;
-    $access->orIf($forbidden);
+    $access = $allowed->orIf($forbidden);
     $this->assertFalse($access->isAllowed());
     $this->assertTrue($access->isForbidden());
     $this->assertDefaultCacheability($access);
 
     // DENY || DENY === DENY.
-    $access = clone $no_opinion;
-    $access->orIf($no_opinion);
+    $access = $no_opinion->orIf($no_opinion);
     $this->assertFalse($access->isAllowed());
     $this->assertFalse($access->isForbidden());
     $this->assertDefaultCacheability($access);
 
     // DENY || ALLOW === ALLOW.
-    $access = clone $no_opinion;
-    $access->orIf($allowed);
+    $access = $no_opinion->orIf($allowed);
     $this->assertTrue($access->isAllowed());
     $this->assertFalse($access->isForbidden());
     $this->assertDefaultCacheability($access);
 
     // DENY || KILL === KILL.
-    $access = clone $no_opinion;
-    $access->orIf($forbidden);
+    $access = $no_opinion->orIf($forbidden);
     $this->assertFalse($access->isAllowed());
     $this->assertTrue($access->isForbidden());
     $this->assertDefaultCacheability($access);
 
     // KILL || * === KILL.
-    $access = clone $forbidden;
-    $access->orIf($unused_access_result_due_to_lazy_evaluation);
+    $access = $forbidden->orIf($unused_access_result_due_to_lazy_evaluation);
     $this->assertFalse($access->isAllowed());
     $this->assertTrue($access->isForbidden());
     $this->assertDefaultCacheability($access);
