From 26a3b17f0f2743d5967dc7696595f58defccce97 Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Sat, 10 Oct 2026 01:31:36 -0700 Subject: [PATCH] Preserve the type of recorded condition values ValueRecorder.record now uses a type parameter for its argument and return value, preserving the recorded expression's static type without changing the recording logic. Regression coverage exercises properties and public fields across then, expect, and explicit assertions in when. Additional cases check that failing conditions still record the receiver, operands, and result for both string and null values. Fixes #2343 --- .../spockframework/runtime/ValueRecorder.java | 2 +- .../smoke/StaticTypeChecking.groovy | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/spock-core/src/main/java/org/spockframework/runtime/ValueRecorder.java b/spock-core/src/main/java/org/spockframework/runtime/ValueRecorder.java index fbd4d04636..456f470b9b 100644 --- a/spock-core/src/main/java/org/spockframework/runtime/ValueRecorder.java +++ b/spock-core/src/main/java/org/spockframework/runtime/ValueRecorder.java @@ -42,7 +42,7 @@ public ValueRecorder reset() { * Records and returns the specified value. Hence an expression can be replaced * with record(expression) without impacting evaluation of the expression. */ - public Object record(int index, Object value) { + public T record(int index, T value) { realizeNas(index + 1, null); values.set(index, value); diff --git a/spock-specs/src/test/groovy/org/spockframework/smoke/StaticTypeChecking.groovy b/spock-specs/src/test/groovy/org/spockframework/smoke/StaticTypeChecking.groovy index bb7af932c9..6d7cff5eaa 100644 --- a/spock-specs/src/test/groovy/org/spockframework/smoke/StaticTypeChecking.groovy +++ b/spock-specs/src/test/groovy/org/spockframework/smoke/StaticTypeChecking.groovy @@ -17,9 +17,75 @@ package org.spockframework.smoke import org.spockframework.EmbeddedSpecification +import org.spockframework.runtime.ConditionNotSatisfiedError + +import spock.lang.Issue +import spock.lang.Specification class StaticTypeChecking extends EmbeddedSpecification { + @Issue("https://github.com/spockframework/spock/issues/2343") + def "preserves receiver types in conditions"(String visibility, String condition) { + when: + runner.runWithImports(""" + @groovy.transform.TypeChecked + class Example extends Specification { + $visibility String myField + + def feature() { + $condition + } + } + """) + + then: + noExceptionThrown() + + where: + [visibility, condition] << [["", "public"], [""" + when: + this.myField = 'value' + + then: + this.myField == 'value' + """, """ + expect: + this.myField == null + """, """ + when: + this.myField = 'value' + assert this.myField == 'value' + + then: + true + """]].combinations() + } + + @Issue("https://github.com/spockframework/spock/issues/2343") + def "records values in failing type checked conditions"(String value) { + when: + runner.runWithImports(""" + @groovy.transform.TypeChecked + class Example extends Specification { + String myField = $value + + def feature() { + expect: + this.myField != $value + } + } + """) + + then: + def error = thrown(ConditionNotSatisfiedError) + def values = error.condition.values + values[0] instanceof Specification + values[1] == values[0].myField + values[2..3] == [values[1], false] + + where: + value << ["'value'", "null"] + } def "correctly spelled setup compiles successfully"() { when: