Skip to content

Commit d917915

Browse files
committed
tests(hot-reload): characterize structural edits to non-struct CEs
Adds service-level diff tests pinning that adding a step to async/seq/inner functions is classified differently from task. Unlike task (a struct resumable state machine, where adding a do! is StateMachineShapeChange), these lower to closures, so the same structural edit is not a state machine shape change and (for async and inner functions) is allowed with full capabilities. Documents where F# already matches C# and isolates the remaining gap to struct resumable computation expressions.
1 parent 08f98ae commit d917915

1 file changed

Lines changed: 101 additions & 0 deletions

File tree

‎tests/FSharp.Compiler.Service.Tests/HotReload/TypedTreeDiffTests.fs‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,3 +2103,104 @@ type Container<'T>(value: 'T) =
21032103
rude.Kind = RudeEditKind.NotSupportedByRuntime
21042104
&& rude.Message.Contains "GenericAddFieldToExistingType"
21052105
)
2106+
2107+
// --- Parity characterization: structural edits to NON-struct-state-machine CEs ---
2108+
// task {} lowers to a struct resumable state machine, so adding/removing a step is a
2109+
// StateMachineShapeChange rude edit (see the task tests above). async/seq/inner functions do
2110+
// NOT use struct resumable code, so the same structural edit should classify differently (and,
2111+
// we expect, more permissively, matching C#). These tests pin that distinction. The positive
2112+
// (Empty rude edits) assertions encode the expected C#-parity outcome; if F# is stricter than
2113+
// predicted, the failure documents exactly where.
2114+
2115+
[<Fact>]
2116+
let ``async let-bang addition is not a struct state machine rude edit`` () =
2117+
use harness = new DiffTestHarness()
2118+
let baseline_source = """
2119+
module Library
2120+
let runAsync () =
2121+
async {
2122+
let! value = async { return 1 }
2123+
return value + 1
2124+
}
2125+
"""
2126+
let updated_source = """
2127+
module Library
2128+
let runAsync () =
2129+
async {
2130+
let! value = async { return 1 }
2131+
let! extra = async { return 2 }
2132+
return value + extra
2133+
}
2134+
"""
2135+
harness.Rewrite(baseline_source)
2136+
let baseline = harness.Compile()
2137+
harness.Rewrite(updated_source)
2138+
let updated = harness.Compile()
2139+
2140+
let result = harness.DiffWith allCapabilities baseline updated
2141+
2142+
// async lowers to continuation closures, not a struct resumable state machine: adding a
2143+
// let! adds a continuation lambda (allowed with NewTypeDefinition/AddMethodToExistingType)
2144+
// and edits the prior continuation body. Unlike task, this should NOT be rude.
2145+
Assert.DoesNotContain(result.RudeEdits, fun rude -> rude.Kind = RudeEditKind.StateMachineShapeChange)
2146+
Assert.Empty(result.RudeEdits)
2147+
2148+
[<Fact>]
2149+
let ``seq yield addition is not a struct state machine rude edit`` () =
2150+
use harness = new DiffTestHarness()
2151+
let baseline_source = """
2152+
module Library
2153+
let numbers () =
2154+
seq {
2155+
yield 1
2156+
yield 2
2157+
}
2158+
"""
2159+
let updated_source = """
2160+
module Library
2161+
let numbers () =
2162+
seq {
2163+
yield 1
2164+
yield 2
2165+
yield 3
2166+
}
2167+
"""
2168+
harness.Rewrite(baseline_source)
2169+
let baseline = harness.Compile()
2170+
harness.Rewrite(updated_source)
2171+
let updated = harness.Compile()
2172+
2173+
let result = harness.DiffWith allCapabilities baseline updated
2174+
2175+
// seq {} lowers via the sequence-expression -> generated IEnumerable object path
2176+
// (ConvertSequenceExprToObject), not resumable struct code. Adding a yield must not be a
2177+
// StateMachineShapeChange; characterizing whether it lands as an allowed body update or a
2178+
// synthesized-declaration change is the point of this test.
2179+
Assert.DoesNotContain(result.RudeEdits, fun rude -> rude.Kind = RudeEditKind.StateMachineShapeChange)
2180+
2181+
[<Fact>]
2182+
let ``adding an inner function is not a struct state machine rude edit`` () =
2183+
use harness = new DiffTestHarness()
2184+
let baseline_source = """
2185+
module Library
2186+
let compute (x: int) =
2187+
x + 1
2188+
"""
2189+
let updated_source = """
2190+
module Library
2191+
let compute (x: int) =
2192+
let helper y = y * 2
2193+
helper x + 1
2194+
"""
2195+
harness.Rewrite(baseline_source)
2196+
let baseline = harness.Compile()
2197+
harness.Rewrite(updated_source)
2198+
let updated = harness.Compile()
2199+
2200+
let result = harness.DiffWith allCapabilities baseline updated
2201+
2202+
// An inner function lowers to a closure/local method, the same family as a lambda: adding
2203+
// one should be an additive (allowed) edit with full capabilities, never a state machine
2204+
// shape change.
2205+
Assert.DoesNotContain(result.RudeEdits, fun rude -> rude.Kind = RudeEditKind.StateMachineShapeChange)
2206+
Assert.Empty(result.RudeEdits)

0 commit comments

Comments
 (0)