Repository: matplotlib/matplotlib (Python)

Issue:
[Bug]: Regression on `tight_layout` when manually adding axes for colorbars
### Bug summary

When creating manual floating axes to fill with colorbars, previous to version 3.5 a call to `fig.tight_layout()` on a figure with manually located axes would simply raise a warning that the figure contains axes incompatbile with tight layout. 

Starting in 3.5 we get the attribute error documented below. 

### Code for reproduction

```python
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
matplotlib.__version__
# > '3.5.0'

def add_cax(ax):
    ax_rect = ax.get_position()
    cax = fig.add_axes(
        [ax_rect.x1 + 0.005, ax_rect.y0, 0.015, ax_rect.height]
    )
    return cax

fig, axes = plt.subplots(1, 2)
pts = axes[1].scatter([0, 1], [0, 1], c=[1, 5])
cax = add_cax(axes[1])
fig.colorbar(pts, cax=cax) 
fig.tight_layout()
```


### Actual outcome

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/var/folders/3c/nbn0xyrs4mldy84gltty_pt00000gn/T/ipykernel_87887/4094170185.py in <module>
      3 cax = add_cax(axes[1])
      4 fig.colorbar(pts, cax=cax, use_gridspec=True)
----> 5 fig.tight_layout()

~/.venv/envs/rms-2.0.0/lib/python3.9/site-packages/matplotlib/figure.py in tight_layout(self, pad, h_pad, w_pad, rect)
   3181         from .tight_layout import (
   3182             get_subplotspec_list, get_tight_layout_figure)
-> 3183         subplotspec_list = get_subplotspec_list(self.axes)
   3184         if None in subplotspec_list:
   3185             _api.warn_external("This figure includes Axes that are not "

~/.venv/envs/rms-2.0.0/lib/python3.9/site-packages/matplotlib/tight_layout.py in get_subplotspec_list(axes_list, grid_spec)
    232 
    233         if hasattr(axes_or_locator, "get_subplotspec"):
--> 234             subplotspec = axes_or_locator.get_subplotspec()
    235             subplotspec = subplotspec.get_topmost_subplotspec()
    236             gs = subplotspec.get_gridspec()

~/.venv/envs/rms-2.0.0/lib/python3.9/site-packages/matplotlib/colorbar.py in get_subplotspec(self)
    281         ss = getattr(self._cbar.ax, 'get_subplotspec', None)
    282         if ss is None:
--> 283             ss = self._orig_locator.get_subplotspec()
    284         else:
    285             ss = ss()

AttributeError: 'NoneType' object has no attribute 'get_subplotspec'

### Expected outcome

Expect a warning about tight layout used with incompatible axes

### Additional information

Fails under Matplotlib 3.5

Works (warns) under < 3.4

### Operating system

MacOS, Windows, Ubuntu

### Matplotlib Version

3.5.0

### Matplotlib Backend

'module://matplotlib_inline.backend_inline'

### Python version

3.9.7

### Jupyter version

3.2.2

### Installation

conda


Discussion:
Thanks for the report.  This shouldn't be too hard to fix, look for it in 3.5.1
Sorry this was broken - #21751 should fix it...



Patch:
diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py
index dc7d43b5f80e..68530e0695aa 100644
--- a/lib/matplotlib/colorbar.py
+++ b/lib/matplotlib/colorbar.py
@@ -280,6 +280,8 @@ def get_subplotspec(self):
         # make tight_layout happy..
         ss = getattr(self._cbar.ax, 'get_subplotspec', None)
         if ss is None:
+            if self._orig_locator is None:
+                return None
             ss = self._orig_locator.get_subplotspec()
         else:
             ss = ss()
diff --git a/lib/matplotlib/tight_layout.py b/lib/matplotlib/tight_layout.py
index 1b4b80b2e0e8..81465f9b5db6 100644
--- a/lib/matplotlib/tight_layout.py
+++ b/lib/matplotlib/tight_layout.py
@@ -228,13 +228,14 @@ def get_subplotspec_list(axes_list, grid_spec=None):
 
         if hasattr(axes_or_locator, "get_subplotspec"):
             subplotspec = axes_or_locator.get_subplotspec()
-            subplotspec = subplotspec.get_topmost_subplotspec()
-            gs = subplotspec.get_gridspec()
-            if grid_spec is not None:
-                if gs != grid_spec:
+            if subplotspec is not None:
+                subplotspec = subplotspec.get_topmost_subplotspec()
+                gs = subplotspec.get_gridspec()
+                if grid_spec is not None:
+                    if gs != grid_spec:
+                        subplotspec = None
+                elif gs.locally_modified_subplot_params():
                     subplotspec = None
-            elif gs.locally_modified_subplot_params():
-                subplotspec = None
         else:
             subplotspec = None
 


Test patch:
diff --git a/lib/matplotlib/tests/test_tightlayout.py b/lib/matplotlib/tests/test_tightlayout.py
index 23d363b5083b..e9b01b160da1 100644
--- a/lib/matplotlib/tests/test_tightlayout.py
+++ b/lib/matplotlib/tests/test_tightlayout.py
@@ -329,3 +329,16 @@ def __init__(self, *args, **kwargs):
     monkeypatch.setattr(mpl.backend_bases.RendererBase, "__init__", __init__)
     fig, ax = plt.subplots()
     fig.tight_layout()
+
+
+def test_manual_colorbar():
+    # This should warn, but not raise
+    fig, axes = plt.subplots(1, 2)
+    pts = axes[1].scatter([0, 1], [0, 1], c=[1, 5])
+    ax_rect = axes[1].get_position()
+    cax = fig.add_axes(
+        [ax_rect.x1 + 0.005, ax_rect.y0, 0.015, ax_rect.height]
+    )
+    fig.colorbar(pts, cax=cax)
+    with pytest.warns(UserWarning, match="This figure includes Axes"):
+        fig.tight_layout()