close
Skip to content

View config: reject shape-mismatched merges, define empty-array semantics, strip nulls from appended members - #80571

Merged
jorgefilipecosta merged 4 commits into
WordPress:trunkfrom
jorgefilipecosta:fix/view-config-merge-shape-mismatch
Jul 23, 2026
Merged

View config: reject shape-mismatched merges, define empty-array semantics, strip nulls from appended members#80571
jorgefilipecosta merged 4 commits into
WordPress:trunkfrom
jorgefilipecosta:fix/view-config-merge-shape-mismatch

Conversation

@jorgefilipecosta

@jorgefilipecosta jorgefilipecosta commented Jul 22, 2026

Copy link
Copy Markdown
Member

What?

Follow up to #80319. Core PR: WordPress/wordpress-develop#12644.

Fixes three silent data-loss defects in Gutenberg_View_Config_Data's merge engine:

  • An associative patch value over a list (or a non-empty list over an associative value) discarded the whole current value. It is now rejected with _doing_it_wrong() and the current value is kept.
  • An empty array under merge() wiped associative values but no-oped on lists. It is now a no-op for both shapes — clear a list with replace() and an empty list, reset a key with null.
  • A list member appended by merge() kept nested nulls that every other write path drops. Appended members now go through strip_nulls().

How?

  • merge_properties(): a patch value only merges into a current value of the same shape. Non-empty mismatches warn and keep the current value; an empty array merges nothing. Empty arrays stay exempt from the guards, so an associative patch still creates new nested maps.
  • merge_list_by_identity(): appended members go through strip_nulls().
  • Docblocks document the rule.

Testing

Verify unit tests are passing:

npm run test:unit:php:base -- --filter Tests_View_Config_Data

One new test per fix; the inline PR comments show each assertion's result without the fix.

AI usage disclosure: issues found via AI-assisted code review; fix, tests, and description drafted with AI assistance (Claude Code) and reviewed by me.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: jorgefilipecosta <jorgefilipecosta@git.wordpress.org>
Co-authored-by: oandregal <oandregal@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@jorgefilipecosta jorgefilipecosta left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-fix test results: each new test was run against trunk's class (source change reverted, tests kept) to confirm it fails without this PR. Per-assertion output inline.

1
);

$this->assertSame( $before, self::read_config( $data ) );

@jorgefilipecosta jorgefilipecosta Jul 22, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Result of this assertion without the fix:

array(
	'view_list' => array(
		'published' => array( 'title' => 'Live' ),
	),
)

The default all view is gone, view_list became a slug-keyed map instead of a list, with no notice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the fix ensures that numeric indexed arrays aren't updated with associative arrays. I cannot think of an use case where this would be valid behavior. It's true that lists can items of different types (e.g., scalar vs. arrays, such as the form.fields list), but all of them will still have numeric indexes. This is a good prevention measure 👍

1
);

$this->assertSame( $before, self::read_config( $data ) );

@jorgefilipecosta jorgefilipecosta Jul 22, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Result of this assertion without the fix:

array(
	'default_view' => array(
		'sort' => array( 'title', 'asc' ),
	),
)

The sort map (field/direction) was replaced by the bare list, with no notice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the opposite of the above. Again, I can't think of uses cases where this would be a good behavior, and it's a good preventive measure.

1
);

$this->assertSame( $before, self::read_config( $data ) );

@jorgefilipecosta jorgefilipecosta Jul 22, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Result of this assertion without the fix:

array(
	'default_view' => array(
		'filters' => array(
			array(
				'field'    => 'author',
				'operator' => 'isAny',
			),
		),
		'sort'    => array(),
	),
)

The same empty-array patch left filters untouched but emptied sort: one input, two different outcomes depending on the current shape.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

1
);

$this->assertSame(

@jorgefilipecosta jorgefilipecosta Jul 22, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Result of this assertion without the fix:

array(
	'view_list' => array(
		array(
			'slug'  => 'all',
			'title' => 'All items',
		),
		array(
			'slug' => 'mine',
			'view' => array( 'filters' => null ),
		),
	),
)

The appended member kept the literal 'filters' => null instead of dropping it the way every other write path does.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@jorgefilipecosta jorgefilipecosta added the [Type] Bug An existing feature does not function as intended label Jul 22, 2026
@jorgefilipecosta
jorgefilipecosta force-pushed the fix/view-config-merge-shape-mismatch branch from 0187b8f to 9c36b12 Compare July 23, 2026 10:00
@oandregal oandregal added the Backport to WP 7.1 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta label Jul 23, 2026
…p, strip nulls from appended members

merge_properties() classified patch values by shape but let a mismatch
fall through: an associative patch value landing on a list (or a list
landing on an associative value) silently discarded the current value
before merging into nothing, and an empty array — classified as a list —
wiped associative values while no-oping on lists. An unmatched list
member was also appended verbatim, storing nested nulls that every other
write path consumes.

A non-empty shape mismatch is now reported with _doing_it_wrong() and
leaves the current value unchanged, an empty array under merge() is a
documented no-op (clearing stays with replace() and null), and appended
list members have their nulls stripped like every other path with no
existing leaf to delete.
…neric message

The $method parameter threaded through merge_properties() and
merge_list_by_identity() existed only to name the public method in the
_doing_it_wrong() notice. A single shape-agnostic message keeps the
notice actionable without the extra plumbing.
A non-empty list patch value applied with replace() bypassed the new
shape guard and silently swapped out an associative current value, while
the associative-over-list direction was already rejected. The guard now
runs before the replace() early return, covering both modes; an empty
array stays exempt so replace() with an empty list still clears a list.
@jorgefilipecosta
jorgefilipecosta force-pushed the fix/view-config-merge-shape-mismatch branch from b180c42 to d6dc2aa Compare July 23, 2026 16:51
@jorgefilipecosta
jorgefilipecosta merged commit f69c158 into WordPress:trunk Jul 23, 2026
38 of 41 checks passed
@github-actions github-actions Bot added this to the Gutenberg 23.7 milestone Jul 23, 2026
@t-hamano

Copy link
Copy Markdown
Contributor

This PR was submitted from a forked repository, so it seems that automatic cherry-picking is not working. If you have the bandwidth, I would appreciate it if you could submit a backport PR directly to wp/7.1.

@t-hamano

Copy link
Copy Markdown
Contributor

This PR was backported to 7.1 by #80829.

@t-hamano t-hamano added Backported to WP Core Pull request that has been successfully merged into WP Core and removed Backport to WP 7.1 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta labels Jul 29, 2026
@github-actions

Copy link
Copy Markdown

There was a conflict while trying to cherry-pick the commit to the wp/7.1 branch. Please resolve the conflict manually and create a PR to the wp/7.1 branch.

PRs to wp/7.1 are similar to PRs to trunk, but you should base your PR on the wp/7.1 branch instead of trunk.

# Checkout the wp/7.1 branch instead of trunk.
git checkout wp/7.1

# Create a new branch for your PR.
git checkout -b my-branch

# Cherry-pick the commit.
git cherry-pick f69c15847fb9f8cbff9fd32b97e0f02640f6e345

# Check which files have conflicts.
git status

# Resolve the conflict...
# Add the resolved files to the staging area.
git status
git add .
git cherry-pick --continue

# Push the branch to the repository
git push origin my-branch

# Create a PR and set the base to the wp/7.1 branch.
# See https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-base-branch-of-a-pull-request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backported to WP Core Pull request that has been successfully merged into WP Core [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants