diff --git a/conformance/results/mypy/specialtypes_promotions.toml b/conformance/results/mypy/specialtypes_promotions.toml
index 864bc73e..04499e86 100644
--- a/conformance/results/mypy/specialtypes_promotions.toml
+++ b/conformance/results/mypy/specialtypes_promotions.toml
@@ -1,7 +1,12 @@
-conformant = "Pass"
+conformant = "Partial"
+notes = """
+Does not narrow from float to int after isinstance() check
+"""
 output = """
-specialtypes_promotions.py:13: error: "float" has no attribute "numerator"  [attr-defined]
+specialtypes_promotions.py:17: error: "float" has no attribute "numerator"  [attr-defined]
+specialtypes_promotions.py:33: error: Incompatible return value type (got "complex", expected "float")  [return-value]
 """
-conformance_automated = "Pass"
+conformance_automated = "Fail"
 errors_diff = """
+Line 26: Expected 1 errors
 """
diff --git a/conformance/results/mypy/version.toml b/conformance/results/mypy/version.toml
index 268bd31a..dc9b55ed 100644
--- a/conformance/results/mypy/version.toml
+++ b/conformance/results/mypy/version.toml
@@ -1,2 +1,2 @@
 version = "mypy 1.10.0"
-test_duration = 1.4
+test_duration = 1.5
diff --git a/conformance/results/pyre/specialtypes_promotions.toml b/conformance/results/pyre/specialtypes_promotions.toml
index fb9055fb..f17ad77f 100644
--- a/conformance/results/pyre/specialtypes_promotions.toml
+++ b/conformance/results/pyre/specialtypes_promotions.toml
@@ -1,10 +1,13 @@
 conformant = "Partial"
 notes = """
 Does not reject use of attribute that is compatible only with float.
+Does not narrow from float to int after isinstance() check
 """
 output = """
+specialtypes_promotions.py:33:8 Incompatible return type [7]: Expected `float` but got `complex`.
 """
 conformance_automated = "Fail"
 errors_diff = """
-Line 13: Expected 1 errors
+Line 17: Expected 1 errors
+Line 26: Expected 1 errors
 """
diff --git a/conformance/results/pyre/version.toml b/conformance/results/pyre/version.toml
index 57ed5696..1fec89de 100644
--- a/conformance/results/pyre/version.toml
+++ b/conformance/results/pyre/version.toml
@@ -1,2 +1,2 @@
 version = "pyre 0.9.21"
-test_duration = 3.4
+test_duration = 2.9
diff --git a/conformance/results/pyright/specialtypes_promotions.toml b/conformance/results/pyright/specialtypes_promotions.toml
index 20daf3a3..560cb119 100644
--- a/conformance/results/pyright/specialtypes_promotions.toml
+++ b/conformance/results/pyright/specialtypes_promotions.toml
@@ -1,7 +1,11 @@
 conformant = "Pass"
 output = """
-specialtypes_promotions.py:13:7 - error: Cannot access attribute "numerator" for class "float"
+specialtypes_promotions.py:17:7 - error: Cannot access attribute "numerator" for class "float"
   Attribute "numerator" is unknown (reportAttributeAccessIssue)
+specialtypes_promotions.py:26:16 - error: Expression of type "Literal['x']" is incompatible with return type "int"
+  "Literal['x']" is incompatible with "int" (reportReturnType)
+specialtypes_promotions.py:33:16 - error: Expression of type "complex" is incompatible with return type "float"
+  "complex" is incompatible with "float" (reportReturnType)
 """
 conformance_automated = "Pass"
 errors_diff = """
diff --git a/conformance/results/pyright/version.toml b/conformance/results/pyright/version.toml
index 6302edbf..789c07a2 100644
--- a/conformance/results/pyright/version.toml
+++ b/conformance/results/pyright/version.toml
@@ -1,2 +1,2 @@
 version = "pyright 1.1.364"
-test_duration = 1.4
+test_duration = 1.6
diff --git a/conformance/results/pytype/specialtypes_promotions.toml b/conformance/results/pytype/specialtypes_promotions.toml
index dc20943f..3d6ee073 100644
--- a/conformance/results/pytype/specialtypes_promotions.toml
+++ b/conformance/results/pytype/specialtypes_promotions.toml
@@ -1,7 +1,12 @@
-conformant = "Pass"
+conformant = "Partial"
+notes = """
+Does not narrow from float to int after isinstance() check
+"""
 output = """
-File "specialtypes_promotions.py", line 13, in func1: No attribute 'numerator' on float [attribute-error]
+File "specialtypes_promotions.py", line 17, in func1: No attribute 'numerator' on float [attribute-error]
+File "specialtypes_promotions.py", line 33, in func2: bad return type [bad-return-type]
 """
-conformance_automated = "Pass"
+conformance_automated = "Fail"
 errors_diff = """
+Line 26: Expected 1 errors
 """
diff --git a/conformance/results/pytype/version.toml b/conformance/results/pytype/version.toml
index 68b8ac60..bbfb29cd 100644
--- a/conformance/results/pytype/version.toml
+++ b/conformance/results/pytype/version.toml
@@ -1,2 +1,2 @@
 version = "pytype 2024.04.11"
-test_duration = 30.1
+test_duration = 49.8
diff --git a/conformance/results/results.html b/conformance/results/results.html
index 9693b2e8..bc90bed9 100644
--- a/conformance/results/results.html
+++ b/conformance/results/results.html
@@ -159,16 +159,16 @@ <h3>Python Type System Conformance Test Results</h3>
         <div class="table_container"><table><tbody>
 <tr><th class="col1">&nbsp;</th>
 <th class='tc-header'><div class='tc-name'>mypy 1.10.0</div>
-<div class='tc-time'>1.4sec</div>
+<div class='tc-time'>1.5sec</div>
 </th>
 <th class='tc-header'><div class='tc-name'>pyright 1.1.364</div>
-<div class='tc-time'>1.4sec</div>
+<div class='tc-time'>1.6sec</div>
 </th>
 <th class='tc-header'><div class='tc-name'>pyre 0.9.21</div>
-<div class='tc-time'>3.4sec</div>
+<div class='tc-time'>2.9sec</div>
 </th>
 <th class='tc-header'><div class='tc-name'>pytype 2024.04.11</div>
-<div class='tc-time'>30.1sec</div>
+<div class='tc-time'>49.8sec</div>
 </th>
 </tr>
 <tr><th class="column" colspan="5">
diff --git a/conformance/tests/specialtypes_promotions.py b/conformance/tests/specialtypes_promotions.py
index fc51e133..0216337d 100644
--- a/conformance/tests/specialtypes_promotions.py
+++ b/conformance/tests/specialtypes_promotions.py
@@ -2,15 +2,36 @@
 Tests "type promotions" for float and complex when they appear in annotations.
 """
 
+from typing import assert_type
+
 # Specification: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
 
-v1: float = 1
-v2: complex = 1.2
-v2 = 1
+v1: int = 1
+v2: float = 1
+v3: float = v1
+v4: complex = 1.2
+v4 = 1
+
+
+def func1(f: float) -> int:
+    f.numerator  # E: attribute exists on int but not float
 
+    if isinstance(f, float):
+        f.hex()  # OK (attribute exists on float but not int)
+        return 1
+    else:
+        assert_type(f, int)
+        # Make sure type checkers don't treat this branch as unreachable
+        # and skip checking it.
+        return "x"  # E
 
-def func1(f: float):
-    f.numerator  # E
 
-    if not isinstance(f, float):
-        f.numerator  # OK
+def func2(x: int) -> float:
+    if x == 0:
+        return 1
+    elif x == 1:
+        return 1j  # E
+    elif x > 10:
+        return x
+    else:
+        return 1.0
diff --git a/docs/spec/special-types.rst b/docs/spec/special-types.rst
index b94222e6..b0339ae0 100644
--- a/docs/spec/special-types.rst
+++ b/docs/spec/special-types.rst
@@ -113,11 +113,25 @@ Special cases for ``float`` and ``complex``
 
 Python's numeric types ``complex``, ``float`` and ``int`` are not
 subtypes of each other, but to support common use cases, the type
-system contains a straightforward shortcut:
-when an argument is annotated as having
-type ``float``, an argument of type ``int`` is acceptable; similar,
-for an argument annotated as having type ``complex``, arguments of
-type ``float`` or ``int`` are acceptable.
+system contains a special case.
+
+When a reference to the built-in type ``float`` appears in a :term:`type expression`,
+it is interpreted as if it were a union of the built-in types ``float`` and ``int``.
+Similarly, when a reference to the type ``complex`` appears, it is interpreted as
+a union of the built-in types ``complex``, ``float`` and ``int``.
+These implicit unions behave exactly like the corresponding explicit union types,
+but type checkers may choose to display them differently in user-visible output
+for clarity.
+
+Type checkers should support narrowing the type of a variable to exactly ``float``
+or ``int``, without the implicit union, through a call to ``isinstance()``::
+
+  def f(x: float) -> None:
+      reveal_type(x)  # float | int, but type checkers may display just "float"
+      if isinstance(x, float):
+          reveal_type(x)  # float
+      else:
+          reveal_type(x)  # int
 
 .. _`type-brackets`: