diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index ec97db2..3870138 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -13,8 +13,10 @@
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Render\MarkupInterface;
 use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Cache\CacheableDependencyInterface;
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\StorageException;
+use Drupal\Core\Render\AttachmentsInterface;
 use Drupal\Core\Render\RenderableInterface;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Theme\ThemeSettings;
@@ -399,6 +401,20 @@ function theme_get_setting($setting_name, $theme = NULL) {
  *   https://www.drupal.org/node/2575065
  */
 function theme_render_and_autoescape($arg) {
+  // Bubbles argument's cacheability & attachment metadata if necessary.
+  if (!($arg instanceof RenderableInterface) && ($arg instanceof CacheableDependencyInterface || $arg instanceof AttachmentsInterface)) {
+    $arg_bubbleable = [];
+    if ($arg instanceof CacheableDependencyInterface) {
+      $arg_bubbleable['#cache']['contexts'] = $arg->getCacheContexts();
+      $arg_bubbleable['#cache']['tags'] = $arg->getCacheTags();
+      $arg_bubbleable['#cache']['max-age'] = $arg->getCacheMaxAge();
+    }
+    if ($arg instanceof AttachmentsInterface) {
+      $arg_bubbleable['#attached'] = $arg->getAttachments();
+    }
+    \Drupal::service('renderer')->render($arg_bubbleable);
+  }
+
   if ($arg instanceof MarkupInterface) {
     return (string) $arg;
   }
diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php
index e204ba9..059d1b5 100644
--- a/core/lib/Drupal/Core/Template/TwigExtension.php
+++ b/core/lib/Drupal/Core/Template/TwigExtension.php
@@ -4,7 +4,9 @@
 
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Render\MarkupInterface;
+use Drupal\Core\Cache\CacheableDependencyInterface;
 use Drupal\Core\Datetime\DateFormatterInterface;
+use Drupal\Core\Render\AttachmentsInterface;
 use Drupal\Core\Render\RenderableInterface;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Routing\UrlGeneratorInterface;
@@ -410,6 +412,8 @@ public function escapeFilter(\Twig_Environment $env, $arg, $strategy = 'html', $
       return NULL;
     }
 
+    $this->bubbleArgMetadata($arg);
+
     // Keep Twig_Markup objects intact to support autoescaping.
     if ($autoescape && ($arg instanceof \Twig_Markup || $arg instanceof MarkupInterface)) {
       return $arg;
@@ -463,6 +467,42 @@ public function escapeFilter(\Twig_Environment $env, $arg, $strategy = 'html', $
   }
 
   /**
+   * Bubbles Twig template argument's cacheability & attachment metadata.
+   *
+   * For example: a generated link or generated URL object is passed as a Twig
+   * template argument, and its bubbleable metadata must be bubbled.
+   *
+   * @see \Drupal\Core\GeneratedLink
+   * @see \Drupal\Core\GeneratedUrl
+   *
+   * @param mixed $arg
+   *   A Twig template argument that is about to be printed.
+   *
+   * @see \Drupal\Core\Theme\ThemeManager::render()
+   * @see \Drupal\Core\Render\RendererInterface::render()
+   */
+  protected function bubbleArgMetadata($arg) {
+    // If it's a renderable, then it'll be up to the generated render array it
+    // returns to contain the necessary cacheability & attachment metadata. If
+    // it doesn't implement CacheableDependencyInterface or AttachmentsInterface
+    // then there is nothing to do here.
+    if ($arg instanceof RenderableInterface || !($arg instanceof CacheableDependencyInterface || $arg instanceof AttachmentsInterface)) {
+      return;
+    }
+
+    $arg_bubbleable = [];
+    if ($arg instanceof CacheableDependencyInterface) {
+      $arg_bubbleable['#cache']['contexts'] = $arg->getCacheContexts();
+      $arg_bubbleable['#cache']['tags'] = $arg->getCacheTags();
+      $arg_bubbleable['#cache']['max-age'] = $arg->getCacheMaxAge();
+    }
+    if ($arg instanceof AttachmentsInterface) {
+      $arg_bubbleable['#attached'] = $arg->getAttachments();
+    }
+    $this->renderer->render($arg_bubbleable);
+  }
+
+  /**
    * Wrapper around render() for twig printed output.
    *
    * If an object is passed which does not implement __toString(),
@@ -504,6 +544,7 @@ public function renderVar($arg) {
     }
 
     if (is_object($arg)) {
+      $this->bubbleArgMetadata($arg);
       if ($arg instanceof RenderableInterface) {
         $arg = $arg->toRenderable();
       }
diff --git a/core/tests/Drupal/KernelTests/Core/Theme/ThemeRenderAndAutoescapeTest.php b/core/tests/Drupal/KernelTests/Core/Theme/ThemeRenderAndAutoescapeTest.php
index cbbb75a..cc65ffe 100644
--- a/core/tests/Drupal/KernelTests/Core/Theme/ThemeRenderAndAutoescapeTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Theme/ThemeRenderAndAutoescapeTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\KernelTests\Core\Theme;
 
 use Drupal\Component\Utility\Html;
+use Drupal\Core\GeneratedLink;
 use Drupal\Core\Link;
 use Drupal\Core\Render\RenderContext;
 use Drupal\Core\Render\Markup;
@@ -87,6 +88,26 @@ public function testThemeEscapeAndRenderNotPrintable() {
     theme_render_and_autoescape(new NonPrintable());
   }
 
+  /**
+   * Ensure cache metadata is bubbled when using theme_render_and_autoescape().
+   */
+  public function testBubblingMetadata() {
+    $link = new GeneratedLink();
+    $link->setGeneratedLink('<a href="example.com"></a>');
+    $link->addCacheTags(['foo']);
+
+    $context = new RenderContext();
+    // Use a closure here since we need to render with a render context.
+    $theme_render_and_autoescape = function () use ($link) {
+      return theme_render_and_autoescape($link);
+    };
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+    $output = $renderer->executeInRenderContext($context, $theme_render_and_autoescape);
+    $this->assertEquals('<a href="example.com"></a>', $output);
+    $this->assertEquals(['foo'], $context->pop()->getCacheTags());
+  }
+
 }
 
 class NonPrintable { }
diff --git a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php
index cb09022..de1ba20 100644
--- a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php
+++ b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\Tests\Core\Template;
 
+use Drupal\Core\GeneratedLink;
 use Drupal\Core\Render\RenderableInterface;
 use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Template\Loader\StringLoader;
 use Drupal\Core\Template\TwigEnvironment;
@@ -240,6 +242,45 @@ public function providerTestRenderVar() {
     return $data;
   }
 
+  /**
+   * @covers ::escapeFilter
+   */
+  public function testEscapeWithGeneratedLink() {
+    $renderer = $this->prophesize(RendererInterface::class);
+    $twig = new \Twig_Environment(NULL, [
+        'debug' => TRUE,
+        'cache' => FALSE,
+        'autoescape' => 'html',
+        'optimizations' => 0,
+      ]
+    );
+
+    $twig_extension = new TwigExtension($renderer->reveal());
+    $twig->addExtension($twig_extension->setUrlGenerator($this->prophesize(UrlGeneratorInterface::class)->reveal()));
+    $link = new GeneratedLink();
+    $link->setGeneratedLink('<a href="example.com"></a>');
+    $link->addCacheTags(['foo']);
+
+    $result = $twig_extension->escapeFilter($twig, $link, 'html', NULL, TRUE);
+    $renderer->render(["#cache" => ["contexts" => [], "tags" => ["foo"], "max-age" => -1], "#attached" => []])->shouldHaveBeenCalled();
+    $this->assertEquals('<a href="example.com"></a>', $result);
+  }
+
+  /**
+   * @covers ::renderVar
+   */
+  public function testRenderVarWithGeneratedLink() {
+    $renderer = $this->prophesize(RendererInterface::class);
+    $twig_extension = new TwigExtension($renderer->reveal());
+    $link = new GeneratedLink();
+    $link->setGeneratedLink('<a href="example.com"></a>');
+    $link->addCacheTags(['foo']);
+
+    $result = $twig_extension->renderVar($link);
+    $renderer->render(["#cache" => ["contexts" => [], "tags" => ["foo"], "max-age" => -1], "#attached" => []])->shouldHaveBeenCalled();
+    $this->assertEquals('<a href="example.com"></a>', $result);
+  }
+
 }
 
 class TwigExtensionTestString {
