From 8113ffc6f4d9f4e0e3d759f095b7bf5e93a56b5c Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Sun, 7 Jun 2026 20:32:34 -0500 Subject: [PATCH 1/2] fix: pool ng-content attributes into the const pool (v22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Angular 22.0.0 (rc.3, angular/angular@2891f7e) moves the `attrs` argument of `ɵɵprojection` out of the inline call and into the shared const pool, emitting `ɵɵprojection(slot, idx, _cN)` instead of an inline array. Mirror that in const_collection by routing projection attrs through `pool.get_const_literal(.., true)`, exactly as element attrs and the projectionDef/ngContentSelectors consts already do. This phase runs after generate_projection_def, so the projection attrs land at the next `_cN`, matching the goldens. Bump the conformance angular submodule rc.2 -> v22.0.0 so the suite guards this emit. Conformance stays at 1264/1264 (100%); update the three ng-content integration snapshots to the pooled form. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/oxc_angular_compiler/angular | 2 +- .../src/pipeline/phases/const_collection.rs | 10 +++++++++- ...integration_test__ng_content_with_bound_select.snap | 3 ++- ...ntegration_test__ng_content_with_ng_project_as.snap | 3 ++- ...t__ng_content_with_ng_project_as_attr_selector.snap | 3 ++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/oxc_angular_compiler/angular b/crates/oxc_angular_compiler/angular index ebd698b90..1cb0524f8 160000 --- a/crates/oxc_angular_compiler/angular +++ b/crates/oxc_angular_compiler/angular @@ -1 +1 @@ -Subproject commit ebd698b90f097bafb6a9696bc4f14f9ef1a7da05 +Subproject commit 1cb0524f82ebcc06642ceebd0b96a19bba883b2e diff --git a/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs b/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs index db58bfbbb..0e1fba504 100644 --- a/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs +++ b/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs @@ -768,7 +768,15 @@ pub fn collect_element_consts(job: &mut ComponentCompilationJob<'_>) { if let Some(attrs) = all_element_attrs.get(xref) { if !attrs.is_empty() { let attr_array = serialize_attributes_to_array_expr(allocator, attrs); - projection_attrs.insert(*xref, attr_array); + // Angular v22 (rc.3+) pools projection attributes into the + // shared const pool (`_cN`) instead of emitting them inline, + // matching `getConstLiteral(attrArray, true)` in Angular's + // const_collection.ts (angular/angular@2891f7e). This phase + // runs after `generate_projection_def`, so the projectionDef + // and ngContentSelectors consts are pooled first and the + // projection attrs land at the next `_cN`, as in the goldens. + let pooled = job.pool.get_const_literal(attr_array, true); + projection_attrs.insert(*xref, pooled); } } } diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_bound_select.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_bound_select.snap index ddaa731f2..49e913b64 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_bound_select.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_bound_select.snap @@ -3,9 +3,10 @@ source: crates/oxc_angular_compiler/tests/integration_test.rs expression: js --- const _c0 = ["*"]; +const _c1 = ["[select]","'[slot=expanded-content]'"]; function TestComponent_Template(rf,ctx) { if ((rf & 1)) { i0.ɵɵprojectionDef(); - i0.ɵɵprojection(0,0,["[select]","'[slot=expanded-content]'"]); + i0.ɵɵprojection(0,0,_c1); } } diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as.snap index 965876452..ca2c57572 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as.snap @@ -3,9 +3,10 @@ source: crates/oxc_angular_compiler/tests/integration_test.rs expression: js --- const _c0 = ["*"]; +const _c1 = ["ngProjectAs","bit-label",5,["bit-label"]]; function TestComponent_Template(rf,ctx) { if ((rf & 1)) { i0.ɵɵprojectionDef(); - i0.ɵɵprojection(0,0,["ngProjectAs","bit-label",5,["bit-label"]]); + i0.ɵɵprojection(0,0,_c1); } } diff --git a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as_attr_selector.snap b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as_attr_selector.snap index 470db9dad..055836b7f 100644 --- a/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as_attr_selector.snap +++ b/crates/oxc_angular_compiler/tests/snapshots/integration_test__ng_content_with_ng_project_as_attr_selector.snap @@ -3,9 +3,10 @@ source: crates/oxc_angular_compiler/tests/integration_test.rs expression: js --- const _c0 = ["*"]; +const _c1 = ["ngProjectAs","[card-content]",5,["","card-content",""]]; function TestComponent_Template(rf,ctx) { if ((rf & 1)) { i0.ɵɵprojectionDef(); - i0.ɵɵprojection(0,0,["ngProjectAs","[card-content]",5,["","card-content",""]]); + i0.ɵɵprojection(0,0,_c1); } } From 06cdb171fd5b480af139e4684597b56401f6fcfe Mon Sep 17 00:00:00 2001 From: "openai-code-agent[bot]" <242516109+Codex@users.noreply.github.com> Date: Mon, 8 Jun 2026 02:05:44 +0000 Subject: [PATCH 2/2] fix: reuse pooled projection attrs const per xref Co-authored-by: brandonroberts <42211+brandonroberts@users.noreply.github.com> --- .../src/pipeline/phases/const_collection.rs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs b/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs index 0e1fba504..72d2919b6 100644 --- a/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs +++ b/crates/oxc_angular_compiler/src/pipeline/phases/const_collection.rs @@ -760,7 +760,7 @@ pub fn collect_element_consts(job: &mut ComponentCompilationJob<'_>) { // Second pass (2b): Assign const indices in collected order // This is where we actually call add_const, matching Angular's getConstIndex call order let mut element_const_indices: FxHashMap = FxHashMap::default(); - let mut projection_attrs: FxHashMap> = FxHashMap::default(); + let mut projection_attrs: FxHashMap> = FxHashMap::default(); for xref_item in &xrefs_to_assign { match xref_item { @@ -776,7 +776,13 @@ pub fn collect_element_consts(job: &mut ComponentCompilationJob<'_>) { // and ngContentSelectors consts are pooled first and the // projection attrs land at the next `_cN`, as in the goldens. let pooled = job.pool.get_const_literal(attr_array, true); - projection_attrs.insert(*xref, pooled); + let pooled_name = match pooled { + OutputExpression::ReadVar(rv) => rv.name.clone(), + _ => unreachable!( + "ConstantPool::get_const_literal must return OutputExpression::ReadVar" + ), + }; + projection_attrs.insert(*xref, pooled_name); } } } @@ -802,8 +808,17 @@ pub fn collect_element_consts(job: &mut ComponentCompilationJob<'_>) { for op in view.create.iter_mut() { match op { CreateOp::Projection(proj) => { - if let Some(attrs) = projection_attrs.remove(&proj.xref) { - proj.attributes = Some(attrs); + // It's possible for multiple projection ops to reference the same xref + // (e.g. across conditional branches). Avoid consuming the pooled attrs so + // every matching projection op receives the same const reference. + if let Some(pooled_name) = projection_attrs.get(&proj.xref) { + proj.attributes = Some(OutputExpression::ReadVar(Box::new_in( + crate::output::ast::ReadVarExpr { + name: pooled_name.clone(), + source_span: None, + }, + allocator, + ))); } } CreateOp::ElementStart(elem) => {