diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3b1fabfd1..00d3b2fc4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,12 @@
 CHANGELOG
 =========
 
+7.3
+---
+
+ * Add support for displaying nested options in DebugCommand
+ * Add support for strings as data for the `MoneyType`
+
 7.2
 ---
 
diff --git a/Console/Descriptor/Descriptor.php b/Console/Descriptor/Descriptor.php
index c910acdf4..91778f819 100644
--- a/Console/Descriptor/Descriptor.php
+++ b/Console/Descriptor/Descriptor.php
@@ -118,6 +118,7 @@ protected function getOptionDefinition(OptionsResolver $optionsResolver, string
             'allowedValues' => 'getAllowedValues',
             'normalizers' => 'getNormalizers',
             'deprecation' => 'getDeprecation',
+            'nestedOptions' => 'getNestedOptions',
         ];
 
         foreach ($map as $key => $method) {
diff --git a/Console/Descriptor/JsonDescriptor.php b/Console/Descriptor/JsonDescriptor.php
index 1f5c7bfa5..1eca762b7 100644
--- a/Console/Descriptor/JsonDescriptor.php
+++ b/Console/Descriptor/JsonDescriptor.php
@@ -62,6 +62,12 @@ protected function describeResolvedFormType(ResolvedFormTypeInterface $resolvedF
     }
 
     protected function describeOption(OptionsResolver $optionsResolver, array $options): void
+    {
+        $data = $this->getOptionDescription($optionsResolver, $options);
+        $this->writeData($data, $options);
+    }
+
+    private function getOptionDescription(OptionsResolver $optionsResolver, array $options): array
     {
         $definition = $this->getOptionDefinition($optionsResolver, $options['option']);
 
@@ -90,7 +96,17 @@ protected function describeOption(OptionsResolver $optionsResolver, array $optio
         }
         $data['has_normalizer'] = isset($definition['normalizers']);
 
-        $this->writeData($data, $options);
+        if ($data['has_nested_options'] = isset($definition['nestedOptions'])) {
+            $nestedResolver = new OptionsResolver();
+            foreach ($definition['nestedOptions'] as $nestedOption) {
+                $nestedOption($nestedResolver, $optionsResolver);
+            }
+            foreach ($nestedResolver->getDefinedOptions() as $option) {
+                $data['nested_options'][$option] = $this->getOptionDescription($nestedResolver, ['option' => $option]);
+            }
+        }
+
+        return $data;
     }
 
     private function writeData(array $data, array $options): void
diff --git a/Console/Descriptor/TextDescriptor.php b/Console/Descriptor/TextDescriptor.php
index 630b87253..e12b34262 100644
--- a/Console/Descriptor/TextDescriptor.php
+++ b/Console/Descriptor/TextDescriptor.php
@@ -118,12 +118,19 @@ protected function describeOption(OptionsResolver $optionsResolver, array $optio
             'Allowed types' => 'allowedTypes',
             'Allowed values' => 'allowedValues',
             'Normalizers' => 'normalizers',
+            'Nested Options' => 'nestedOptions',
         ];
         $rows = [];
         foreach ($map as $label => $name) {
             $value = \array_key_exists($name, $definition) ? $dump($definition[$name]) : '-';
             if ('default' === $name && isset($definition['lazy'])) {
                 $value = "Value: $value\n\nClosure(s): ".$dump($definition['lazy']);
+            } elseif ('nestedOptions' === $name && isset($definition['nestedOptions'])) {
+                $nestedResolver = new OptionsResolver();
+                foreach ($definition['nestedOptions'] as $nestedOption) {
+                    $nestedOption($nestedResolver, $optionsResolver);
+                }
+                $value = $dump($nestedResolver->getDefinedOptions());
             }
 
             $rows[] = ["<info>$label</info>", $value];
diff --git a/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php b/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php
index b03f8da44..e9c9cb9e5 100644
--- a/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php
+++ b/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php
@@ -70,7 +70,7 @@ public function reverseTransform(mixed $value): int|float|null
         if (null !== $value) {
             $value = (string) ($value * $this->divisor);
 
-            if ('float' === $this->input) {
+            if ('integer' !== $this->input) {
                 return (float) $value;
             }
 
diff --git a/Extension/Core/Type/MoneyType.php b/Extension/Core/Type/MoneyType.php
index 2657b03af..38dbfc038 100644
--- a/Extension/Core/Type/MoneyType.php
+++ b/Extension/Core/Type/MoneyType.php
@@ -14,6 +14,7 @@
 use Symfony\Component\Form\AbstractType;
 use Symfony\Component\Form\Exception\LogicException;
 use Symfony\Component\Form\Extension\Core\DataTransformer\MoneyToLocalizedStringTransformer;
+use Symfony\Component\Form\Extension\Core\DataTransformer\StringToFloatTransformer;
 use Symfony\Component\Form\FormBuilderInterface;
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\Form\FormView;
@@ -38,6 +39,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
                 $options['input'],
             ))
         ;
+
+        if ('string' === $options['input']) {
+            $builder->addModelTransformer(new StringToFloatTransformer($options['scale']));
+        }
     }
 
     public function buildView(FormView $view, FormInterface $form, array $options): void
@@ -77,7 +82,7 @@ public function configureOptions(OptionsResolver $resolver): void
 
         $resolver->setAllowedTypes('html5', 'bool');
 
-        $resolver->setAllowedValues('input', ['float', 'integer']);
+        $resolver->setAllowedValues('input', ['float', 'integer', 'string']);
 
         $resolver->setNormalizer('grouping', static function (Options $options, $value) {
             if ($value && $options['html5']) {
diff --git a/Tests/Command/DebugCommandTest.php b/Tests/Command/DebugCommandTest.php
index 4537099c2..cac92addb 100644
--- a/Tests/Command/DebugCommandTest.php
+++ b/Tests/Command/DebugCommandTest.php
@@ -179,6 +179,8 @@ class:%s
                      }       %s
                    ]         %s
  ---------------- -----------%s
+  Nested Options   -         %s
+ ---------------- -----------%s
 
 TXT
             , $tester->getDisplay(true));
diff --git a/Tests/Console/Descriptor/AbstractDescriptorTestCase.php b/Tests/Console/Descriptor/AbstractDescriptorTestCase.php
index 8a99c205c..456b433ee 100644
--- a/Tests/Console/Descriptor/AbstractDescriptorTestCase.php
+++ b/Tests/Console/Descriptor/AbstractDescriptorTestCase.php
@@ -130,6 +130,11 @@ public static function getDescribeOptionTestData()
         $options['option'] = 'bar';
         $options['show_deprecated'] = true;
         yield [$resolvedType->getOptionsResolver(), $options, 'deprecated_option'];
+
+        $resolvedType = new ResolvedFormType(new FooType(), [], $parent);
+        $options['type'] = $resolvedType->getInnerType();
+        $options['option'] = 'baz';
+        yield [$resolvedType->getOptionsResolver(), $options, 'nested_option'];
     }
 
     abstract protected function getDescriptor();
@@ -172,5 +177,9 @@ public function configureOptions(OptionsResolver $resolver): void
         $resolver->setAllowedTypes('foo', 'string');
         $resolver->setAllowedValues('foo', ['bar', 'baz']);
         $resolver->setNormalizer('foo', fn (Options $options, $value) => (string) $value);
+        $resolver->setOptions('baz', function (OptionsResolver $baz) {
+            $baz->setRequired('foo');
+            $baz->setDefaults(['foo' => true, 'bar' => true]);
+        });
     }
 }
diff --git a/Tests/Extension/Core/Type/ChoiceTypeTest.php b/Tests/Extension/Core/Type/ChoiceTypeTest.php
index 28810bbc7..977a8707c 100644
--- a/Tests/Extension/Core/Type/ChoiceTypeTest.php
+++ b/Tests/Extension/Core/Type/ChoiceTypeTest.php
@@ -878,7 +878,7 @@ public function testSubmitSingleExpandedRequired()
 
         $this->assertSame('b', $form->getData());
         $this->assertSame('b', $form->getViewData());
-        $this->assertEmpty($form->getExtraData());
+        $this->assertSame([], $form->getExtraData());
         $this->assertTrue($form->isSynchronized());
 
         $this->assertFalse($form[0]->getData());
@@ -906,7 +906,7 @@ public function testSubmitSingleExpandedRequiredInvalidChoice()
 
         $this->assertNull($form->getData());
         $this->assertSame('foobar', $form->getViewData());
-        $this->assertEmpty($form->getExtraData());
+        $this->assertSame([], $form->getExtraData());
         $this->assertFalse($form->isSynchronized());
 
         $this->assertFalse($form[0]->getData());
@@ -934,7 +934,7 @@ public function testSubmitSingleExpandedNonRequired()
 
         $this->assertSame('b', $form->getData());
         $this->assertSame('b', $form->getViewData());
-        $this->assertEmpty($form->getExtraData());
+        $this->assertSame([], $form->getExtraData());
         $this->assertTrue($form->isSynchronized());
 
         $this->assertFalse($form['placeholder']->getData());
@@ -964,7 +964,7 @@ public function testSubmitSingleExpandedNonRequiredInvalidChoice()
 
         $this->assertNull($form->getData());
         $this->assertSame('foobar', $form->getViewData());
-        $this->assertEmpty($form->getExtraData());
+        $this->assertSame([], $form->getExtraData());
         $this->assertFalse($form->isSynchronized());
 
         $this->assertFalse($form[0]->getData());
@@ -1348,7 +1348,7 @@ public function testSubmitMultipleExpanded()
 
         $this->assertSame(['a', 'c'], $form->getData());
         $this->assertSame(['a', 'c'], $form->getViewData());
-        $this->assertEmpty($form->getExtraData());
+        $this->assertSame([], $form->getExtraData());
         $this->assertTrue($form->isSynchronized());
 
         $this->assertTrue($form[0]->getData());
@@ -1375,7 +1375,7 @@ public function testSubmitMultipleExpandedInvalidScalarChoice()
 
         $this->assertNull($form->getData());
         $this->assertSame('foobar', $form->getViewData());
-        $this->assertEmpty($form->getExtraData());
+        $this->assertSame([], $form->getExtraData());
         $this->assertFalse($form->isSynchronized());
 
         $this->assertFalse($form[0]->getData());
@@ -1402,7 +1402,7 @@ public function testSubmitMultipleExpandedInvalidArrayChoice()
 
         $this->assertSame(['a'], $form->getData());
         $this->assertSame(['a'], $form->getViewData());
-        $this->assertEmpty($form->getExtraData());
+        $this->assertSame([], $form->getExtraData());
         $this->assertFalse($form->isValid());
 
         $this->assertTrue($form[0]->getData());
diff --git a/Tests/Extension/Core/Type/ColorTypeTest.php b/Tests/Extension/Core/Type/ColorTypeTest.php
index 52382cea2..5cfab193e 100644
--- a/Tests/Extension/Core/Type/ColorTypeTest.php
+++ b/Tests/Extension/Core/Type/ColorTypeTest.php
@@ -13,6 +13,7 @@
 
 use Symfony\Component\Form\Extension\Core\Type\ColorType;
 use Symfony\Component\Form\FormError;
+use Symfony\Component\Form\FormErrorIterator;
 
 final class ColorTypeTest extends BaseTypeTestCase
 {
@@ -30,7 +31,8 @@ public function testValidationShouldPass(bool $html5, ?string $submittedValue)
 
         $form->submit($submittedValue);
 
-        $this->assertEmpty($form->getErrors());
+        $this->assertInstanceOf(FormErrorIterator::class, $form->getErrors());
+        $this->assertCount(0, $form->getErrors());
     }
 
     public static function validationShouldPassProvider(): array
diff --git a/Tests/Extension/Core/Type/MoneyTypeTest.php b/Tests/Extension/Core/Type/MoneyTypeTest.php
index f9112ffca..aa0d6c249 100644
--- a/Tests/Extension/Core/Type/MoneyTypeTest.php
+++ b/Tests/Extension/Core/Type/MoneyTypeTest.php
@@ -11,6 +11,7 @@
 
 namespace Symfony\Component\Form\Tests\Extension\Core\Type;
 
+use Symfony\Component\Form\Exception\TransformationFailedException;
 use Symfony\Component\Form\Extension\Core\Type\MoneyType;
 use Symfony\Component\Intl\Util\IntlTestHelper;
 
@@ -146,4 +147,54 @@ public function testIntegerInputWithoutDivisor()
 
         $this->assertSame(1234567, $form->getData());
     }
+
+    public function testDefaultFormattingWithScaleAndStringInput()
+    {
+        $form = $this->factory->create(static::TESTED_TYPE, null, ['scale' => 2, 'input' => 'string']);
+        $form->setData('12345.67890');
+
+        $this->assertSame('12345.68', $form->createView()->vars['value']);
+    }
+
+    public function testStringInputWithFloatData()
+    {
+        $this->expectException(TransformationFailedException::class);
+        $this->expectExceptionMessage('Expected a numeric string.');
+
+        $this->factory->create(static::TESTED_TYPE, 12345.6789, [
+            'input' => 'string',
+            'scale' => 2,
+        ]);
+    }
+
+    public function testStringInputWithIntData()
+    {
+        $this->expectException(TransformationFailedException::class);
+        $this->expectExceptionMessage('Expected a numeric string.');
+
+        $this->factory->create(static::TESTED_TYPE, 12345, [
+            'input' => 'string',
+            'scale' => 2,
+        ]);
+    }
+
+    public function testSubmitStringInputWithDefaultScale()
+    {
+        $form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'string']);
+        $form->submit('1.234');
+
+        $this->assertSame('1.23', $form->getData());
+        $this->assertSame(1.23, $form->getNormData());
+        $this->assertSame('1.23', $form->getViewData());
+    }
+
+    public function testSubmitStringInputWithScale()
+    {
+        $form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'string', 'scale' => 3]);
+        $form->submit('1.234');
+
+        $this->assertSame('1.234', $form->getData());
+        $this->assertSame(1.234, $form->getNormData());
+        $this->assertSame('1.234', $form->getViewData());
+    }
 }
diff --git a/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php b/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php
index 6a6a8be9c..48cbf743e 100644
--- a/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php
+++ b/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php
@@ -16,6 +16,7 @@
 use Symfony\Component\Form\Extension\Core\DataMapper\DataMapper;
 use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener;
 use Symfony\Component\Form\FormBuilder;
+use Symfony\Component\Form\FormErrorIterator;
 use Symfony\Component\Form\FormEvent;
 use Symfony\Component\Form\FormFactoryBuilder;
 use Symfony\Component\Form\FormFactoryInterface;
@@ -65,7 +66,8 @@ public function testArrayCsrfToken()
         $validation = new CsrfValidationListener('csrf', $this->tokenManager, 'unknown', 'Invalid.');
         $validation->preSubmit($event);
 
-        $this->assertNotEmpty($this->form->getErrors());
+        $this->assertInstanceOf(FormErrorIterator::class, $this->form->getErrors());
+        $this->assertGreaterThan(0, \count($this->form->getErrors()));
     }
 
     public function testMaxPostSizeExceeded()
@@ -74,7 +76,8 @@ public function testMaxPostSizeExceeded()
         $validation = new CsrfValidationListener('csrf', $this->tokenManager, 'unknown', 'Error message', null, null, new ServerParamsPostMaxSizeExceeded());
 
         $validation->preSubmit($event);
-        $this->assertEmpty($this->form->getErrors());
+        $this->assertInstanceOf(FormErrorIterator::class, $this->form->getErrors());
+        $this->assertCount(0, $this->form->getErrors());
     }
 }
 
diff --git a/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php b/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php
index 14595e8cf..9841ac9fc 100644
--- a/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php
+++ b/Tests/Extension/Validator/Constraints/FormValidatorFunctionalTest.php
@@ -88,7 +88,7 @@ public function testFieldConstraintsInvalidateFormIfFieldIsSubmitted()
     public function testNonCompositeConstraintValidatedOnce()
     {
         $form = $this->formFactory->create(TextType::class, null, [
-            'constraints' => [new NotBlank(['groups' => ['foo', 'bar']])],
+            'constraints' => [new NotBlank(groups: ['foo', 'bar'])],
             'validation_groups' => ['foo', 'bar'],
         ]);
         $form->submit('');
@@ -105,12 +105,8 @@ public function testCompositeConstraintValidatedInEachGroup()
         $form = $this->formFactory->create(FormType::class, null, [
             'constraints' => [
                 new Collection([
-                    'field1' => new NotBlank([
-                        'groups' => ['field1'],
-                    ]),
-                    'field2' => new NotBlank([
-                        'groups' => ['field2'],
-                    ]),
+                    'field1' => new NotBlank(groups: ['field1']),
+                    'field2' => new NotBlank(groups: ['field2']),
                 ]),
             ],
             'validation_groups' => ['field1', 'field2'],
@@ -136,12 +132,8 @@ public function testCompositeConstraintValidatedInSequence()
         $form = $this->formFactory->create(FormType::class, null, [
             'constraints' => [
                 new Collection([
-                    'field1' => new NotBlank([
-                        'groups' => ['field1'],
-                    ]),
-                    'field2' => new NotBlank([
-                        'groups' => ['field2'],
-                    ]),
+                    'field1' => new NotBlank(groups: ['field1']),
+                    'field2' => new NotBlank(groups: ['field2']),
                 ]),
             ],
             'validation_groups' => new GroupSequence(['field1', 'field2']),
@@ -167,10 +159,10 @@ public function testFieldsValidateInSequence()
             'validation_groups' => new GroupSequence(['group1', 'group2']),
         ])
             ->add('foo', TextType::class, [
-                'constraints' => [new Length(['min' => 10, 'groups' => ['group1']])],
+                'constraints' => [new Length(min: 10, groups: ['group1'])],
             ])
             ->add('bar', TextType::class, [
-                'constraints' => [new NotBlank(['groups' => ['group2']])],
+                'constraints' => [new NotBlank(groups: ['group2'])],
             ])
         ;
 
@@ -188,13 +180,13 @@ public function testFieldsValidateInSequenceWithNestedGroupsArray()
             'validation_groups' => new GroupSequence([['group1', 'group2'], 'group3']),
         ])
             ->add('foo', TextType::class, [
-                'constraints' => [new Length(['min' => 10, 'groups' => ['group1']])],
+                'constraints' => [new Length(min: 10, groups: ['group1'])],
             ])
             ->add('bar', TextType::class, [
-                'constraints' => [new Length(['min' => 10, 'groups' => ['group2']])],
+                'constraints' => [new Length(min: 10, groups: ['group2'])],
             ])
             ->add('baz', TextType::class, [
-                'constraints' => [new NotBlank(['groups' => ['group3']])],
+                'constraints' => [new NotBlank(groups: ['group3'])],
             ])
         ;
 
@@ -214,13 +206,11 @@ public function testConstraintsInDifferentGroupsOnSingleField()
         ])
             ->add('foo', TextType::class, [
                 'constraints' => [
-                    new NotBlank([
-                        'groups' => ['group1'],
-                    ]),
-                    new Length([
-                        'groups' => ['group2'],
-                        'max' => 3,
-                    ]),
+                    new NotBlank(groups: ['group1']),
+                    new Length(
+                        groups: ['group2'],
+                        max: 3,
+                    ),
                 ],
             ]);
         $form->submit([
@@ -242,13 +232,11 @@ public function testConstraintsInDifferentGroupsOnSingleFieldWithAdditionalField
             ->add('bar')
             ->add('foo', TextType::class, [
                 'constraints' => [
-                    new NotBlank([
-                        'groups' => ['group1'],
-                    ]),
-                    new Length([
-                        'groups' => ['group2'],
-                        'max' => 3,
-                    ]),
+                    new NotBlank(groups: ['group1']),
+                    new Length(
+                        groups: ['group2'],
+                        max: 3,
+                    ),
                 ],
             ]);
         $form->submit([
@@ -268,11 +256,11 @@ public function testCascadeValidationToChildFormsUsingPropertyPaths()
             'validation_groups' => ['group1', 'group2'],
         ])
             ->add('field1', null, [
-                'constraints' => [new NotBlank(['groups' => 'group1'])],
+                'constraints' => [new NotBlank(groups: ['group1'])],
                 'property_path' => '[foo]',
             ])
             ->add('field2', null, [
-                'constraints' => [new NotBlank(['groups' => 'group2'])],
+                'constraints' => [new NotBlank(groups: ['group2'])],
                 'property_path' => '[bar]',
             ])
         ;
@@ -359,11 +347,11 @@ public function testCascadeValidationToChildFormsUsingPropertyPathsValidatedInSe
             'validation_groups' => new GroupSequence(['group1', 'group2']),
         ])
             ->add('field1', null, [
-                'constraints' => [new NotBlank(['groups' => 'group1'])],
+                'constraints' => [new NotBlank(groups: ['group1'])],
                 'property_path' => '[foo]',
             ])
             ->add('field2', null, [
-                'constraints' => [new NotBlank(['groups' => 'group2'])],
+                'constraints' => [new NotBlank(groups: ['group2'])],
                 'property_path' => '[bar]',
             ])
         ;
@@ -384,9 +372,7 @@ public function testContextIsPopulatedWithFormBeingValidated()
     {
         $form = $this->formFactory->create(FormType::class)
             ->add('field1', null, [
-                'constraints' => [new Expression([
-                    'expression' => '!this.getParent().get("field2").getData()',
-                ])],
+                'constraints' => [new Expression(expression: '!this.getParent().get("field2").getData()')],
             ])
             ->add('field2')
         ;
@@ -407,10 +393,10 @@ public function testContextIsPopulatedWithFormBeingValidatedUsingGroupSequence()
             'validation_groups' => new GroupSequence(['group1']),
         ])
             ->add('field1', null, [
-                'constraints' => [new Expression([
-                    'expression' => '!this.getParent().get("field2").getData()',
-                    'groups' => ['group1'],
-                ])],
+                'constraints' => [new Expression(
+                    expression: '!this.getParent().get("field2").getData()',
+                    groups: ['group1'],
+                )],
             ])
             ->add('field2')
         ;
diff --git a/Tests/Extension/Validator/Constraints/FormValidatorTest.php b/Tests/Extension/Validator/Constraints/FormValidatorTest.php
index 86b53ac3a..b438c0d8f 100644
--- a/Tests/Extension/Validator/Constraints/FormValidatorTest.php
+++ b/Tests/Extension/Validator/Constraints/FormValidatorTest.php
@@ -70,9 +70,9 @@ public function testValidate()
     public function testValidateConstraints()
     {
         $object = new \stdClass();
-        $constraint1 = new NotNull(['groups' => ['group1', 'group2']]);
-        $constraint2 = new NotBlank(['groups' => 'group2']);
-        $constraint3 = new Length(['groups' => 'group2', 'min' => 3]);
+        $constraint1 = new NotNull(groups: ['group1', 'group2']);
+        $constraint2 = new NotBlank(groups: ['group2']);
+        $constraint3 = new Length(groups: ['group2'], min: 3);
 
         $options = [
             'validation_groups' => ['group1', 'group2'],
@@ -156,8 +156,8 @@ public function testMissingConstraintIndex()
     public function testValidateConstraintsOptionEvenIfNoValidConstraint()
     {
         $object = new \stdClass();
-        $constraint1 = new NotNull(['groups' => ['group1', 'group2']]);
-        $constraint2 = new NotBlank(['groups' => 'group2']);
+        $constraint1 = new NotNull(groups: ['group1', 'group2']);
+        $constraint2 = new NotBlank(groups: ['group2']);
 
         $parent = $this->getBuilder('parent', null)
             ->setCompound(true)
@@ -684,7 +684,7 @@ public function getValidationGroups(FormInterface $form)
     public function testCauseForNotAllowedExtraFieldsIsTheFormConstraint()
     {
         $form = $this
-            ->getBuilder('form', null, ['constraints' => [new NotBlank(['groups' => ['foo']])]])
+            ->getBuilder('form', null, ['constraints' => [new NotBlank(groups: ['foo'])]])
             ->setCompound(true)
             ->setDataMapper(new DataMapper())
             ->getForm();
diff --git a/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php b/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php
index a1d1a3840..2dec87b5c 100644
--- a/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php
+++ b/Tests/Extension/Validator/Type/FormTypeValidatorExtensionTest.php
@@ -84,8 +84,8 @@ public function testGroupSequenceWithConstraintsOption()
             ->create(FormTypeTest::TESTED_TYPE, null, ['validation_groups' => new GroupSequence(['First', 'Second'])])
             ->add('field', TextTypeTest::TESTED_TYPE, [
                 'constraints' => [
-                    new Length(['min' => 10, 'groups' => ['First']]),
-                    new NotBlank(['groups' => ['Second']]),
+                    new Length(min: 10, groups: ['First']),
+                    new NotBlank(groups: ['Second']),
                 ],
             ])
         ;
@@ -102,7 +102,7 @@ public function testManyFieldsGroupSequenceWithConstraintsOption()
     {
         $formMetadata = new ClassMetadata(Form::class);
         $authorMetadata = (new ClassMetadata(Author::class))
-            ->addPropertyConstraint('firstName', new NotBlank(['groups' => 'Second']))
+            ->addPropertyConstraint('firstName', new NotBlank(groups: ['Second']))
         ;
         $metadataFactory = $this->createMock(MetadataFactoryInterface::class);
         $metadataFactory->expects($this->any())
@@ -131,12 +131,12 @@ public function testManyFieldsGroupSequenceWithConstraintsOption()
             ->add('firstName', TextTypeTest::TESTED_TYPE)
             ->add('lastName', TextTypeTest::TESTED_TYPE, [
                 'constraints' => [
-                    new Length(['min' => 10, 'groups' => ['First']]),
+                    new Length(min: 10, groups: ['First']),
                 ],
             ])
             ->add('australian', TextTypeTest::TESTED_TYPE, [
                 'constraints' => [
-                    new NotBlank(['groups' => ['Second']]),
+                    new NotBlank(groups: ['Second']),
                 ],
             ])
         ;
diff --git a/Tests/Extension/Validator/ValidatorTypeGuesserTest.php b/Tests/Extension/Validator/ValidatorTypeGuesserTest.php
index c561cd76f..1d0e0f872 100644
--- a/Tests/Extension/Validator/ValidatorTypeGuesserTest.php
+++ b/Tests/Extension/Validator/ValidatorTypeGuesserTest.php
@@ -96,8 +96,8 @@ public static function guessRequiredProvider()
             [new NotNull(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)],
             [new NotBlank(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)],
             [new IsTrue(), new ValueGuess(true, Guess::HIGH_CONFIDENCE)],
-            [new Length(['min' => 10, 'max' => 10]), new ValueGuess(false, Guess::LOW_CONFIDENCE)],
-            [new Range(['min' => 1, 'max' => 20]), new ValueGuess(false, Guess::LOW_CONFIDENCE)],
+            [new Length(min: 10, max: 10), new ValueGuess(false, Guess::LOW_CONFIDENCE)],
+            [new Range(min: 1, max: 20), new ValueGuess(false, Guess::LOW_CONFIDENCE)],
         ];
     }
 
@@ -122,7 +122,7 @@ public function testGuessRequiredReturnsFalseForUnmappedProperties()
 
     public function testGuessMaxLengthForConstraintWithMaxValue()
     {
-        $constraint = new Length(['max' => '2']);
+        $constraint = new Length(max: '2');
 
         $result = $this->guesser->guessMaxLengthForConstraint($constraint);
         $this->assertInstanceOf(ValueGuess::class, $result);
@@ -132,7 +132,7 @@ public function testGuessMaxLengthForConstraintWithMaxValue()
 
     public function testGuessMaxLengthForConstraintWithMinValue()
     {
-        $constraint = new Length(['min' => '2']);
+        $constraint = new Length(min: '2');
 
         $result = $this->guesser->guessMaxLengthForConstraint($constraint);
         $this->assertNull($result);
@@ -141,7 +141,7 @@ public function testGuessMaxLengthForConstraintWithMinValue()
     public function testGuessMimeTypesForConstraintWithMimeTypesValue()
     {
         $mimeTypes = ['image/png', 'image/jpeg'];
-        $constraint = new File(['mimeTypes' => $mimeTypes]);
+        $constraint = new File(mimeTypes: $mimeTypes);
         $typeGuess = $this->guesser->guessTypeForConstraint($constraint);
         $this->assertInstanceOf(TypeGuess::class, $typeGuess);
         $this->assertArrayHasKey('attr', $typeGuess->getOptions());
@@ -159,7 +159,7 @@ public function testGuessMimeTypesForConstraintWithoutMimeTypesValue()
 
     public function testGuessMimeTypesForConstraintWithMimeTypesStringValue()
     {
-        $constraint = new File(['mimeTypes' => 'image/*']);
+        $constraint = new File(mimeTypes: 'image/*');
         $typeGuess = $this->guesser->guessTypeForConstraint($constraint);
         $this->assertInstanceOf(TypeGuess::class, $typeGuess);
         $this->assertArrayHasKey('attr', $typeGuess->getOptions());
@@ -169,7 +169,7 @@ public function testGuessMimeTypesForConstraintWithMimeTypesStringValue()
 
     public function testGuessMimeTypesForConstraintWithMimeTypesEmptyStringValue()
     {
-        $constraint = new File(['mimeTypes' => '']);
+        $constraint = new File(mimeTypes: '');
         $typeGuess = $this->guesser->guessTypeForConstraint($constraint);
         $this->assertInstanceOf(TypeGuess::class, $typeGuess);
         $this->assertArrayNotHasKey('attr', $typeGuess->getOptions());
diff --git a/Tests/Fixtures/Descriptor/default_option_with_normalizer.json b/Tests/Fixtures/Descriptor/default_option_with_normalizer.json
index 0ac903a95..25b7c4525 100644
--- a/Tests/Fixtures/Descriptor/default_option_with_normalizer.json
+++ b/Tests/Fixtures/Descriptor/default_option_with_normalizer.json
@@ -7,5 +7,6 @@
         "bool",
         "string"
     ],
-    "has_normalizer": true
+    "has_normalizer": true,
+    "has_nested_options": false
 }
diff --git a/Tests/Fixtures/Descriptor/default_option_with_normalizer.txt b/Tests/Fixtures/Descriptor/default_option_with_normalizer.txt
index 0f6f1f40e..cd41f47a8 100644
--- a/Tests/Fixtures/Descriptor/default_option_with_normalizer.txt
+++ b/Tests/Fixtures/Descriptor/default_option_with_normalizer.txt
@@ -25,4 +25,6 @@ Symfony\Component\Form\Extension\Core\Type\ChoiceType (choice_translation_domain
                      }       %s
                    ]         %s
  ---------------- -----------%s
+  Nested Options   -         %s
+ ---------------- -----------%s
 
diff --git a/Tests/Fixtures/Descriptor/deprecated_option.json b/Tests/Fixtures/Descriptor/deprecated_option.json
index b3b0cf3ec..5c88933f9 100644
--- a/Tests/Fixtures/Descriptor/deprecated_option.json
+++ b/Tests/Fixtures/Descriptor/deprecated_option.json
@@ -2,5 +2,6 @@
     "deprecated": true,
     "deprecation_message": "The option \"bar\" is deprecated.",
     "required": false,
-    "has_normalizer": false
+    "has_normalizer": false,
+    "has_nested_options": false
 }
diff --git a/Tests/Fixtures/Descriptor/deprecated_option.txt b/Tests/Fixtures/Descriptor/deprecated_option.txt
index 31ef796f9..f5da39c70 100644
--- a/Tests/Fixtures/Descriptor/deprecated_option.txt
+++ b/Tests/Fixtures/Descriptor/deprecated_option.txt
@@ -21,4 +21,6 @@ Symfony\Component\Form\Tests\Console\Descriptor\FooType (bar)
   Allowed values        -                                  
  --------------------- ----------------------------------- 
   Normalizers           -                                  
- --------------------- -----------------------------------
+ --------------------- ----------------------------------- 
+  Nested Options        -                                  
+ --------------------- ----------------------------------- 
diff --git a/Tests/Fixtures/Descriptor/nested_option.json b/Tests/Fixtures/Descriptor/nested_option.json
new file mode 100644
index 000000000..a6fa2a93a
--- /dev/null
+++ b/Tests/Fixtures/Descriptor/nested_option.json
@@ -0,0 +1,23 @@
+{
+    "required": false,
+    "default": [],
+    "is_lazy": false,
+    "has_normalizer": false,
+    "has_nested_options": true,
+    "nested_options": {
+        "foo": {
+            "required": true,
+            "default": true,
+            "is_lazy": false,
+            "has_normalizer": false,
+            "has_nested_options": false
+        },
+        "bar": {
+            "required": false,
+            "default": true,
+            "is_lazy": false,
+            "has_normalizer": false,
+            "has_nested_options": false
+        }
+    }
+}
diff --git a/Tests/Fixtures/Descriptor/nested_option.txt b/Tests/Fixtures/Descriptor/nested_option.txt
new file mode 100644
index 000000000..0f698abfc
--- /dev/null
+++ b/Tests/Fixtures/Descriptor/nested_option.txt
@@ -0,0 +1,21 @@
+Symfony\Component\Form\Tests\Console\Descriptor\FooType (baz)
+=============================================================
+
+ ---------------- ---------- 
+  Info             -         
+ ---------------- ---------- 
+  Required         false     
+ ---------------- ---------- 
+  Default          []        
+ ---------------- ---------- 
+  Allowed types    -         
+ ---------------- ---------- 
+  Allowed values   -         
+ ---------------- ---------- 
+  Normalizers      -         
+ ---------------- ---------- 
+  Nested Options   [         
+                     "foo",  
+                     "bar"   
+                   ]         
+ ---------------- ---------- 
diff --git a/Tests/Fixtures/Descriptor/overridden_option_with_default_closures.json b/Tests/Fixtures/Descriptor/overridden_option_with_default_closures.json
index c41e377ac..bc79122e1 100644
--- a/Tests/Fixtures/Descriptor/overridden_option_with_default_closures.json
+++ b/Tests/Fixtures/Descriptor/overridden_option_with_default_closures.json
@@ -2,5 +2,6 @@
     "required": false,
     "default": null,
     "is_lazy": true,
-    "has_normalizer": false
+    "has_normalizer": false,
+    "has_nested_options": false
 }
diff --git a/Tests/Fixtures/Descriptor/overridden_option_with_default_closures.txt b/Tests/Fixtures/Descriptor/overridden_option_with_default_closures.txt
index cedca25a6..8bf9cb8c8 100644
--- a/Tests/Fixtures/Descriptor/overridden_option_with_default_closures.txt
+++ b/Tests/Fixtures/Descriptor/overridden_option_with_default_closures.txt
@@ -23,6 +23,8 @@ Symfony\Component\Form\Tests\Console\Descriptor\FooType (empty_data)
  ---------------- ----------------------%s 
   Allowed values   -                    %s 
  ---------------- ----------------------%s 
-  Normalizers      -                    %s
+  Normalizers      -                    %s 
+ ---------------- ----------------------%s 
+  Nested Options   -                    %s 
  ---------------- ----------------------%s 
 
diff --git a/Tests/Fixtures/Descriptor/required_option_with_allowed_values.json b/Tests/Fixtures/Descriptor/required_option_with_allowed_values.json
index 126933c6b..5aad5becb 100644
--- a/Tests/Fixtures/Descriptor/required_option_with_allowed_values.json
+++ b/Tests/Fixtures/Descriptor/required_option_with_allowed_values.json
@@ -7,5 +7,6 @@
         "bar",
         "baz"
     ],
-    "has_normalizer": true
+    "has_normalizer": true,
+    "has_nested_options": false
 }
diff --git a/Tests/Fixtures/Descriptor/required_option_with_allowed_values.txt b/Tests/Fixtures/Descriptor/required_option_with_allowed_values.txt
index fd2f8f3f1..a8371243b 100644
--- a/Tests/Fixtures/Descriptor/required_option_with_allowed_values.txt
+++ b/Tests/Fixtures/Descriptor/required_option_with_allowed_values.txt
@@ -27,4 +27,5 @@ Symfony\Component\Form\Tests\Console\Descriptor\FooType (foo)
                      }       %s
                    ]         %s
  ---------------- -----------%s
-
+  Nested Options   -         %s
+ ---------------- -----------%s
diff --git a/Tests/FormBuilderTest.php b/Tests/FormBuilderTest.php
index 023cf77d6..9234d8775 100644
--- a/Tests/FormBuilderTest.php
+++ b/Tests/FormBuilderTest.php
@@ -173,8 +173,8 @@ public function testGetFormConfigErasesReferences()
         $children = $reflClass->getProperty('children');
         $unresolvedChildren = $reflClass->getProperty('unresolvedChildren');
 
-        $this->assertEmpty($children->getValue($config));
-        $this->assertEmpty($unresolvedChildren->getValue($config));
+        $this->assertSame([], $children->getValue($config));
+        $this->assertSame([], $unresolvedChildren->getValue($config));
     }
 
     public function testGetButtonBuilderBeforeExplicitlyResolvingAllChildren()
diff --git a/composer.json b/composer.json
index 40c021d91..f4403ba74 100644
--- a/composer.json
+++ b/composer.json
@@ -19,7 +19,7 @@
         "php": ">=8.2",
         "symfony/deprecation-contracts": "^2.5|^3",
         "symfony/event-dispatcher": "^6.4|^7.0",
-        "symfony/options-resolver": "^6.4|^7.0",
+        "symfony/options-resolver": "^7.3",
         "symfony/polyfill-ctype": "~1.8",
         "symfony/polyfill-intl-icu": "^1.21",
         "symfony/polyfill-mbstring": "~1.0",