OptimizedPhpArrayDumperTest.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  1. <?php
  2. /**
  3. * @file
  4. * Contains \Drupal\Tests\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumperTest.
  5. */
  6. namespace Drupal\Tests\Component\DependencyInjection\Dumper {
  7. use Drupal\Component\Utility\Crypt;
  8. use PHPUnit\Framework\TestCase;
  9. use Symfony\Component\DependencyInjection\Definition;
  10. use Symfony\Component\DependencyInjection\Reference;
  11. use Symfony\Component\DependencyInjection\Parameter;
  12. use Symfony\Component\ExpressionLanguage\Expression;
  13. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
  14. use Symfony\Component\DependencyInjection\ContainerInterface;
  15. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  16. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  17. /**
  18. * @coversDefaultClass \Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper
  19. * @group DependencyInjection
  20. */
  21. class OptimizedPhpArrayDumperTest extends TestCase {
  22. /**
  23. * The container builder instance.
  24. *
  25. * @var \Symfony\Component\DependencyInjection\ContainerBuilder
  26. */
  27. protected $containerBuilder;
  28. /**
  29. * The definition for the container to build in tests.
  30. *
  31. * @var array
  32. */
  33. protected $containerDefinition;
  34. /**
  35. * Whether the dumper uses the machine-optimized format or not.
  36. *
  37. * @var bool
  38. */
  39. protected $machineFormat = TRUE;
  40. /**
  41. * Stores the dumper class to use.
  42. *
  43. * @var string
  44. */
  45. protected $dumperClass = '\Drupal\Component\DependencyInjection\Dumper\OptimizedPhpArrayDumper';
  46. /**
  47. * The dumper instance.
  48. *
  49. * @var \Symfony\Component\DependencyInjection\Dumper\DumperInterface
  50. */
  51. protected $dumper;
  52. /**
  53. * {@inheritdoc}
  54. */
  55. protected function setUp() {
  56. // Setup a mock container builder.
  57. $this->containerBuilder = $this->prophesize('\Symfony\Component\DependencyInjection\ContainerBuilder');
  58. $this->containerBuilder->getAliases()->willReturn([]);
  59. $this->containerBuilder->getParameterBag()->willReturn(new ParameterBag());
  60. $this->containerBuilder->getDefinitions()->willReturn(NULL);
  61. $this->containerBuilder->isCompiled()->willReturn(TRUE);
  62. $definition = [];
  63. $definition['aliases'] = [];
  64. $definition['parameters'] = [];
  65. $definition['services'] = [];
  66. $definition['frozen'] = TRUE;
  67. $definition['machine_format'] = $this->machineFormat;
  68. $this->containerDefinition = $definition;
  69. // Create the dumper.
  70. $this->dumper = new $this->dumperClass($this->containerBuilder->reveal());
  71. }
  72. /**
  73. * Tests that an empty container works properly.
  74. *
  75. * @covers ::dump
  76. * @covers ::getArray
  77. * @covers ::supportsMachineFormat
  78. */
  79. public function testDumpForEmptyContainer() {
  80. $serialized_definition = $this->dumper->dump();
  81. $this->assertEquals(serialize($this->containerDefinition), $serialized_definition);
  82. }
  83. /**
  84. * Tests that alias processing works properly.
  85. *
  86. * @covers ::getAliases
  87. *
  88. * @dataProvider getAliasesDataProvider
  89. */
  90. public function testGetAliases($aliases, $definition_aliases) {
  91. $this->containerDefinition['aliases'] = $definition_aliases;
  92. $this->containerBuilder->getAliases()->willReturn($aliases);
  93. $this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
  94. }
  95. /**
  96. * Data provider for testGetAliases().
  97. *
  98. * @return array[]
  99. * Returns data-set elements with:
  100. * - aliases as returned by ContainerBuilder.
  101. * - aliases as expected in the container definition.
  102. */
  103. public function getAliasesDataProvider() {
  104. return [
  105. [[], []],
  106. [
  107. ['foo' => 'foo.alias'],
  108. ['foo' => 'foo.alias'],
  109. ],
  110. [
  111. ['foo' => 'foo.alias', 'foo.alias' => 'foo.alias.alias'],
  112. ['foo' => 'foo.alias.alias', 'foo.alias' => 'foo.alias.alias'],
  113. ],
  114. ];
  115. }
  116. /**
  117. * Tests that parameter processing works properly.
  118. *
  119. * @covers ::getParameters
  120. * @covers ::prepareParameters
  121. * @covers ::escape
  122. * @covers ::dumpValue
  123. * @covers ::getReferenceCall
  124. *
  125. * @dataProvider getParametersDataProvider
  126. */
  127. public function testGetParameters($parameters, $definition_parameters, $is_frozen) {
  128. $this->containerDefinition['parameters'] = $definition_parameters;
  129. $this->containerDefinition['frozen'] = $is_frozen;
  130. $parameter_bag = new ParameterBag($parameters);
  131. $this->containerBuilder->getParameterBag()->willReturn($parameter_bag);
  132. $this->containerBuilder->isCompiled()->willReturn($is_frozen);
  133. if (isset($parameters['reference'])) {
  134. $definition = new Definition('\stdClass');
  135. $this->containerBuilder->getDefinition('referenced_service')->willReturn($definition);
  136. }
  137. $this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
  138. }
  139. /**
  140. * Data provider for testGetParameters().
  141. *
  142. * @return array[]
  143. * Returns data-set elements with:
  144. * - parameters as returned by ContainerBuilder.
  145. * - parameters as expected in the container definition.
  146. * - frozen value
  147. */
  148. public function getParametersDataProvider() {
  149. return [
  150. [[], [], TRUE],
  151. [
  152. ['foo' => 'value_foo'],
  153. ['foo' => 'value_foo'],
  154. TRUE,
  155. ],
  156. [
  157. ['foo' => ['llama' => 'yes']],
  158. ['foo' => ['llama' => 'yes']],
  159. TRUE,
  160. ],
  161. [
  162. ['foo' => '%llama%', 'llama' => 'yes'],
  163. ['foo' => '%%llama%%', 'llama' => 'yes'],
  164. TRUE,
  165. ],
  166. [
  167. ['foo' => '%llama%', 'llama' => 'yes'],
  168. ['foo' => '%llama%', 'llama' => 'yes'],
  169. FALSE,
  170. ],
  171. [
  172. ['reference' => new Reference('referenced_service')],
  173. ['reference' => $this->getServiceCall('referenced_service')],
  174. TRUE,
  175. ],
  176. ];
  177. }
  178. /**
  179. * Tests that service processing works properly.
  180. *
  181. * @covers ::getServiceDefinitions
  182. * @covers ::getServiceDefinition
  183. * @covers ::dumpMethodCalls
  184. * @covers ::dumpCollection
  185. * @covers ::dumpCallable
  186. * @covers ::dumpValue
  187. * @covers ::getPrivateServiceCall
  188. * @covers ::getReferenceCall
  189. * @covers ::getServiceCall
  190. * @covers ::getParameterCall
  191. *
  192. * @dataProvider getDefinitionsDataProvider
  193. *
  194. * @group legacy
  195. */
  196. public function testGetServiceDefinitions($services, $definition_services) {
  197. $this->containerDefinition['services'] = $definition_services;
  198. $this->containerBuilder->getDefinitions()->willReturn($services);
  199. $bar_definition = new Definition('\stdClass');
  200. $this->containerBuilder->getDefinition('bar')->willReturn($bar_definition);
  201. $private_definition = new Definition('\stdClass');
  202. $private_definition->setPublic(FALSE);
  203. $this->containerBuilder->getDefinition('private_definition')->willReturn($private_definition);
  204. $this->assertEquals($this->containerDefinition, $this->dumper->getArray(), 'Expected definition matches dump.');
  205. }
  206. /**
  207. * Data provider for testGetServiceDefinitions().
  208. *
  209. * @return array[]
  210. * Returns data-set elements with:
  211. * - parameters as returned by ContainerBuilder.
  212. * - parameters as expected in the container definition.
  213. * - frozen value
  214. */
  215. public function getDefinitionsDataProvider() {
  216. $base_service_definition = [
  217. 'class' => '\stdClass',
  218. 'public' => TRUE,
  219. 'file' => FALSE,
  220. 'synthetic' => FALSE,
  221. 'lazy' => FALSE,
  222. 'arguments' => [],
  223. 'arguments_count' => 0,
  224. 'properties' => [],
  225. 'calls' => [],
  226. 'shared' => TRUE,
  227. 'factory' => FALSE,
  228. 'configurator' => FALSE,
  229. ];
  230. // Test basic flags.
  231. $service_definitions[] = [] + $base_service_definition;
  232. $service_definitions[] = [
  233. 'public' => FALSE,
  234. ] + $base_service_definition;
  235. $service_definitions[] = [
  236. 'file' => 'test_include.php',
  237. ] + $base_service_definition;
  238. $service_definitions[] = [
  239. 'synthetic' => TRUE,
  240. ] + $base_service_definition;
  241. $service_definitions[] = [
  242. 'shared' => FALSE,
  243. ] + $base_service_definition;
  244. $service_definitions[] = [
  245. 'lazy' => TRUE,
  246. ] + $base_service_definition;
  247. // Test a basic public Reference.
  248. $service_definitions[] = [
  249. 'arguments' => ['foo', new Reference('bar')],
  250. 'arguments_count' => 2,
  251. 'arguments_expected' => $this->getCollection(['foo', $this->getServiceCall('bar')]),
  252. ] + $base_service_definition;
  253. // Test a public reference that should not throw an Exception.
  254. $reference = new Reference('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE);
  255. $service_definitions[] = [
  256. 'arguments' => [$reference],
  257. 'arguments_count' => 1,
  258. 'arguments_expected' => $this->getCollection([$this->getServiceCall('bar', ContainerInterface::NULL_ON_INVALID_REFERENCE)]),
  259. ] + $base_service_definition;
  260. // Test a private shared service, denoted by having a Reference.
  261. $private_definition = [
  262. 'class' => '\stdClass',
  263. 'public' => FALSE,
  264. 'arguments_count' => 0,
  265. ];
  266. $service_definitions[] = [
  267. 'arguments' => ['foo', new Reference('private_definition')],
  268. 'arguments_count' => 2,
  269. 'arguments_expected' => $this->getCollection([
  270. 'foo',
  271. $this->getPrivateServiceCall('private_definition', $private_definition, TRUE),
  272. ]),
  273. ] + $base_service_definition;
  274. // Test a private non-shared service, denoted by having a Definition.
  275. $private_definition_object = new Definition('\stdClass');
  276. $private_definition_object->setPublic(FALSE);
  277. $service_definitions[] = [
  278. 'arguments' => ['foo', $private_definition_object],
  279. 'arguments_count' => 2,
  280. 'arguments_expected' => $this->getCollection([
  281. 'foo',
  282. $this->getPrivateServiceCall(NULL, $private_definition),
  283. ]),
  284. ] + $base_service_definition;
  285. // Test a deep collection without a reference.
  286. $service_definitions[] = [
  287. 'arguments' => [[['foo']]],
  288. 'arguments_count' => 1,
  289. ] + $base_service_definition;
  290. // Test a deep collection with a reference to resolve.
  291. $service_definitions[] = [
  292. 'arguments' => [[new Reference('bar')]],
  293. 'arguments_count' => 1,
  294. 'arguments_expected' => $this->getCollection([$this->getCollection([$this->getServiceCall('bar')])]),
  295. ] + $base_service_definition;
  296. // Test a collection with a variable to resolve.
  297. $service_definitions[] = [
  298. 'arguments' => [new Parameter('llama_parameter')],
  299. 'arguments_count' => 1,
  300. 'arguments_expected' => $this->getCollection([$this->getParameterCall('llama_parameter')]),
  301. ] + $base_service_definition;
  302. // Test objects that have _serviceId property.
  303. $drupal_service = new \stdClass();
  304. $drupal_service->_serviceId = 'bar';
  305. $service_definitions[] = [
  306. 'arguments' => [$drupal_service],
  307. 'arguments_count' => 1,
  308. 'arguments_expected' => $this->getCollection([$this->getServiceCall('bar')]),
  309. ] + $base_service_definition;
  310. // Test getMethodCalls.
  311. $calls = [
  312. ['method', $this->getCollection([])],
  313. ['method2', $this->getCollection([])],
  314. ];
  315. $service_definitions[] = [
  316. 'calls' => $calls,
  317. ] + $base_service_definition;
  318. $service_definitions[] = [
  319. 'shared' => FALSE,
  320. ] + $base_service_definition;
  321. // Test factory.
  322. $service_definitions[] = [
  323. 'factory' => [new Reference('bar'), 'factoryMethod'],
  324. 'factory_expected' => [$this->getServiceCall('bar'), 'factoryMethod'],
  325. ] + $base_service_definition;
  326. // Test invalid factory - needed to test deep dumpValue().
  327. $service_definitions[] = [
  328. 'factory' => [['foo', 'llama'], 'factoryMethod'],
  329. ] + $base_service_definition;
  330. // Test properties.
  331. $service_definitions[] = [
  332. 'properties' => ['_value' => 'llama'],
  333. ] + $base_service_definition;
  334. // Test configurator.
  335. $service_definitions[] = [
  336. 'configurator' => [new Reference('bar'), 'configureService'],
  337. 'configurator_expected' => [$this->getServiceCall('bar'), 'configureService'],
  338. ] + $base_service_definition;
  339. $services_provided = [];
  340. $services_provided[] = [
  341. [],
  342. [],
  343. ];
  344. foreach ($service_definitions as $service_definition) {
  345. $definition = $this->prophesize('\Symfony\Component\DependencyInjection\Definition');
  346. $definition->getClass()->willReturn($service_definition['class']);
  347. $definition->isPublic()->willReturn($service_definition['public']);
  348. $definition->getFile()->willReturn($service_definition['file']);
  349. $definition->isSynthetic()->willReturn($service_definition['synthetic']);
  350. $definition->isLazy()->willReturn($service_definition['lazy']);
  351. $definition->getArguments()->willReturn($service_definition['arguments']);
  352. $definition->getProperties()->willReturn($service_definition['properties']);
  353. $definition->getMethodCalls()->willReturn($service_definition['calls']);
  354. $definition->isShared()->willReturn($service_definition['shared']);
  355. $definition->getDecoratedService()->willReturn(NULL);
  356. $definition->getFactory()->willReturn($service_definition['factory']);
  357. $definition->getConfigurator()->willReturn($service_definition['configurator']);
  358. // Preserve order.
  359. $filtered_service_definition = [];
  360. foreach ($base_service_definition as $key => $value) {
  361. $filtered_service_definition[$key] = $service_definition[$key];
  362. unset($service_definition[$key]);
  363. if ($key == 'class' || $key == 'arguments_count') {
  364. continue;
  365. }
  366. if ($filtered_service_definition[$key] === $base_service_definition[$key]) {
  367. unset($filtered_service_definition[$key]);
  368. }
  369. }
  370. // Add remaining properties.
  371. $filtered_service_definition += $service_definition;
  372. // Allow to set _expected values.
  373. foreach (['arguments', 'factory', 'configurator'] as $key) {
  374. $expected = $key . '_expected';
  375. if (isset($filtered_service_definition[$expected])) {
  376. $filtered_service_definition[$key] = $filtered_service_definition[$expected];
  377. unset($filtered_service_definition[$expected]);
  378. }
  379. }
  380. if (isset($filtered_service_definition['public']) && $filtered_service_definition['public'] === FALSE) {
  381. $services_provided[] = [
  382. ['foo_service' => $definition->reveal()],
  383. [],
  384. ];
  385. continue;
  386. }
  387. $services_provided[] = [
  388. ['foo_service' => $definition->reveal()],
  389. ['foo_service' => $this->serializeDefinition($filtered_service_definition)],
  390. ];
  391. }
  392. return $services_provided;
  393. }
  394. /**
  395. * Helper function to serialize a definition.
  396. *
  397. * Used to override serialization.
  398. */
  399. protected function serializeDefinition(array $service_definition) {
  400. return serialize($service_definition);
  401. }
  402. /**
  403. * Helper function to return a service definition.
  404. */
  405. protected function getServiceCall($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) {
  406. return (object) [
  407. 'type' => 'service',
  408. 'id' => $id,
  409. 'invalidBehavior' => $invalid_behavior,
  410. ];
  411. }
  412. /**
  413. * Tests that references to aliases work correctly.
  414. *
  415. * @covers ::getReferenceCall
  416. *
  417. * @dataProvider publicPrivateDataProvider
  418. *
  419. * @group legacy
  420. */
  421. public function testGetServiceDefinitionWithReferenceToAlias($public) {
  422. $bar_definition = new Definition('\stdClass');
  423. $bar_definition_php_array = [
  424. 'class' => '\stdClass',
  425. ];
  426. if (!$public) {
  427. $bar_definition->setPublic(FALSE);
  428. $bar_definition_php_array['public'] = FALSE;
  429. }
  430. $bar_definition_php_array['arguments_count'] = 0;
  431. $services['bar'] = $bar_definition;
  432. $aliases['bar.alias'] = 'bar';
  433. $foo = new Definition('\stdClass');
  434. $foo->addArgument(new Reference('bar.alias'));
  435. $services['foo'] = $foo;
  436. $this->containerBuilder->getAliases()->willReturn($aliases);
  437. $this->containerBuilder->getDefinitions()->willReturn($services);
  438. $this->containerBuilder->getDefinition('bar')->willReturn($bar_definition);
  439. $dump = $this->dumper->getArray();
  440. if ($public) {
  441. $service_definition = $this->getServiceCall('bar');
  442. }
  443. else {
  444. $service_definition = $this->getPrivateServiceCall('bar', $bar_definition_php_array, TRUE);
  445. }
  446. $data = [
  447. 'class' => '\stdClass',
  448. 'arguments' => $this->getCollection([
  449. $service_definition,
  450. ]),
  451. 'arguments_count' => 1,
  452. ];
  453. $this->assertEquals($this->serializeDefinition($data), $dump['services']['foo'], 'Expected definition matches dump.');
  454. }
  455. public function publicPrivateDataProvider() {
  456. return [
  457. [TRUE],
  458. [FALSE],
  459. ];
  460. }
  461. /**
  462. * Tests that getDecoratedService() is unsupported.
  463. *
  464. * Tests that the correct InvalidArgumentException is thrown for
  465. * getDecoratedService().
  466. *
  467. * @covers ::getServiceDefinition
  468. *
  469. * @group legacy
  470. */
  471. public function testGetServiceDefinitionForDecoratedService() {
  472. $bar_definition = new Definition('\stdClass');
  473. $bar_definition->setDecoratedService(new Reference('foo'));
  474. $services['bar'] = $bar_definition;
  475. $this->containerBuilder->getDefinitions()->willReturn($services);
  476. if (method_exists($this, 'expectException')) {
  477. $this->expectException(InvalidArgumentException::class);
  478. }
  479. else {
  480. $this->setExpectedException(InvalidArgumentException::class);
  481. }
  482. $this->dumper->getArray();
  483. }
  484. /**
  485. * Tests that the correct RuntimeException is thrown for expressions.
  486. *
  487. * @covers ::dumpValue
  488. */
  489. public function testGetServiceDefinitionForExpression() {
  490. $expression = new Expression();
  491. $bar_definition = new Definition('\stdClass');
  492. $bar_definition->addArgument($expression);
  493. $services['bar'] = $bar_definition;
  494. $this->containerBuilder->getDefinitions()->willReturn($services);
  495. if (method_exists($this, 'expectException')) {
  496. $this->expectException(RuntimeException::class);
  497. }
  498. else {
  499. $this->setExpectedException(RuntimeException::class);
  500. }
  501. $this->dumper->getArray();
  502. }
  503. /**
  504. * Tests that the correct RuntimeException is thrown for dumping an object.
  505. *
  506. * @covers ::dumpValue
  507. */
  508. public function testGetServiceDefinitionForObject() {
  509. $service = new \stdClass();
  510. $bar_definition = new Definition('\stdClass');
  511. $bar_definition->addArgument($service);
  512. $services['bar'] = $bar_definition;
  513. $this->containerBuilder->getDefinitions()->willReturn($services);
  514. if (method_exists($this, 'expectException')) {
  515. $this->expectException(RuntimeException::class);
  516. }
  517. else {
  518. $this->setExpectedException(RuntimeException::class);
  519. }
  520. $this->dumper->getArray();
  521. }
  522. /**
  523. * Tests that the correct RuntimeException is thrown for dumping a resource.
  524. *
  525. * @covers ::dumpValue
  526. */
  527. public function testGetServiceDefinitionForResource() {
  528. $resource = fopen('php://memory', 'r');
  529. $bar_definition = new Definition('\stdClass');
  530. $bar_definition->addArgument($resource);
  531. $services['bar'] = $bar_definition;
  532. $this->containerBuilder->getDefinitions()->willReturn($services);
  533. if (method_exists($this, 'expectException')) {
  534. $this->expectException(RuntimeException::class);
  535. }
  536. else {
  537. $this->setExpectedException(RuntimeException::class);
  538. }
  539. $this->dumper->getArray();
  540. }
  541. /**
  542. * Helper function to return a private service definition.
  543. */
  544. protected function getPrivateServiceCall($id, $service_definition, $shared = FALSE) {
  545. if (!$id) {
  546. $hash = Crypt::hashBase64(serialize($service_definition));
  547. $id = 'private__' . $hash;
  548. }
  549. return (object) [
  550. 'type' => 'private_service',
  551. 'id' => $id,
  552. 'value' => $service_definition,
  553. 'shared' => $shared,
  554. ];
  555. }
  556. /**
  557. * Helper function to return a machine-optimized collection.
  558. */
  559. protected function getCollection($collection, $resolve = TRUE) {
  560. return (object) [
  561. 'type' => 'collection',
  562. 'value' => $collection,
  563. 'resolve' => $resolve,
  564. ];
  565. }
  566. /**
  567. * Helper function to return a parameter definition.
  568. */
  569. protected function getParameterCall($name) {
  570. return (object) [
  571. 'type' => 'parameter',
  572. 'name' => $name,
  573. ];
  574. }
  575. }
  576. }
  577. /**
  578. * As Drupal Core does not ship with ExpressionLanguage component we need to
  579. * define a dummy, else it cannot be tested.
  580. */
  581. namespace Symfony\Component\ExpressionLanguage {
  582. if (!class_exists('\Symfony\Component\ExpressionLanguage\Expression')) {
  583. /**
  584. * Dummy class to ensure non-existent Symfony component can be tested.
  585. */
  586. class Expression {
  587. /**
  588. * Gets the string representation of the expression.
  589. */
  590. public function __toString() {
  591. return 'dummy_expression';
  592. }
  593. }
  594. }
  595. }