Skip to content

fix(compiler-sfc): infer boolean prop types from generic constraints#15028

Open
ifer47 wants to merge 1 commit into
vuejs:mainfrom
ifer47:fix/generic-boolean-prop-runtime-type
Open

fix(compiler-sfc): infer boolean prop types from generic constraints#15028
ifer47 wants to merge 1 commit into
vuejs:mainfrom
ifer47:fix/generic-boolean-prop-runtime-type

Conversation

@ifer47

@ifer47 ifer47 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • infer runtime types from generic parameter constraints when unresolved type parameters are used as type-only props
  • infer conditional type runtime constructors from both branches so T extends true ? boolean : never can preserve Boolean
  • fall back to null when runtime inference returns an empty type list, e.g. from never

Root Cause

When a generic props alias such as Props<T extends boolean> was instantiated with an unresolved SFC generic, runtime prop inference kept the unresolved outer type parameter and lost the alias constraint. That made props like multiple: T and checked: T extends true ? boolean : never compile without a Boolean runtime constructor, so Vue could not apply boolean casting.

Fixes #13787.

Validation

  • pnpm vitest --project unit packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts --run
  • pnpm vitest --project unit packages/compiler-sfc/__tests__/compileScript/resolveType.spec.ts --run
  • pnpm vitest --project unit packages/compiler-sfc/__tests__ --run
  • pnpm check
  • pnpm exec prettier --check packages/compiler-sfc/src/script/resolveType.ts packages/compiler-sfc/src/script/defineProps.ts packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts

Summary by CodeRabbit

  • Bug Fixes

    • Improved type handling for props so generic constraints, conditional types, and never-like cases are reflected more accurately in generated output.
    • Fixed prop normalization so empty inferred types now fall back consistently, helping avoid incorrect runtime prop shapes.
    • Production builds now omit extra development-only prop details while keeping type information intact.
  • Tests

    • Added coverage for generic boolean-constrained prop scenarios, including binding classification and production-mode output.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Extends resolveType.ts to handle TSTypeParameter, TSConditionalType, and TSNeverKeyword nodes during runtime type inference, adds resolveGenericTypeParameter and isUnresolvableTypeReference helpers, fixes empty type array normalization in defineProps.ts, and adds a test for the generic boolean constraint scenario.

Changes

Generic boolean prop type inference fix

Layer / File(s) Summary
resolveGenericTypeParameter and isUnresolvableTypeReference helpers
packages/compiler-sfc/src/script/resolveType.ts
Introduces two new local helpers: resolveGenericTypeParameter picks between the provided type argument and the type-parameter's constraint when the argument is unresolvable; isUnresolvableTypeReference detects unresolvable references via resolveTypeReference try/catch.
TSTypeParameter instantiation in innerResolveTypeElements and inferRuntimeType
packages/compiler-sfc/src/script/resolveType.ts
Generic type-parameter instantiation during type-reference resolution and runtime inference now calls resolveGenericTypeParameter instead of using the raw type argument. Adds a TSTypeParameter case in innerResolveTypeElements that resolves the constraint. Fixes scope to resolved._ownerScope || scope in the recursive inferRuntimeType call.
New inferRuntimeType cases: TSConditionalType, TSNeverKeyword, TSTypeParameter
packages/compiler-sfc/src/script/resolveType.ts
TSConditionalType flattens runtime inference across trueType and falseType; TSNeverKeyword returns []; TSTypeParameter infers from its constraint when present.
Empty type array normalization in defineProps.ts
packages/compiler-sfc/src/script/defineProps.ts
Changes the type field fallback from a falsy check (type || ['null']) to an explicit length check (type.length ? type : ['null']) so an empty inferred type array is correctly normalized to ['null'].
Test: generic boolean constraint
packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
Adds a test case (#13787) for generic="TMultiple extends boolean" asserting generated prop type shapes, skipCheck/union handling, bindings classification, and production-mode output.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • vuejs/core#12876: Both PRs modify resolveType.ts to improve runtime type inference for generic type parameters via added TSTypeParameter/type-parameter context handling.
  • vuejs/core#13245: Both PRs change how resolved type references are recursively inferred and scoped inside inferRuntimeType.
  • vuejs/core#14297: Both PRs update inferRuntimeType to preserve correct scope via _ownerScope during type resolution.

Suggested labels

ready to merge, scope: sfc, :hammer: p3-minor-bug

Suggested reviewers

  • sxzz
  • edison1105

Poem

🐰 A generic hop, a boolean leap,
TSTypeParameter no longer asleep!
trueType, falseType, both get their say,
Empty arrays? ['null'] saves the day.
Boolean casting works — hip, hip, hooray! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: inferring boolean prop types from generic constraints.
Linked Issues check ✅ Passed The changes address #13787 by resolving generic constraints, conditional types, and never for runtime boolean prop inference.
Out of Scope Changes check ✅ Passed The modified code and added test stay focused on generic prop type inference and boolean casting behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 107 kB 40.3 kB 36.2 kB
vue.global.prod.js 165 kB 60.3 kB 53.7 kB

Usages

Name Size Gzip Brotli
createApp (CAPI only) 49 kB 19 kB 17.4 kB
createApp 57.1 kB 22.1 kB 20.2 kB
createSSRApp 61.6 kB 23.9 kB 21.8 kB
defineCustomElement 63.3 kB 24 kB 21.9 kB
overall 71.9 kB 27.5 kB 25.1 kB

@pkg-pr-new

pkg-pr-new Bot commented Jun 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@15028
npm i https://pkg.pr.new/@vue/compiler-core@15028
yarn add https://pkg.pr.new/@vue/compiler-core@15028.tgz

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@15028
npm i https://pkg.pr.new/@vue/compiler-dom@15028
yarn add https://pkg.pr.new/@vue/compiler-dom@15028.tgz

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@15028
npm i https://pkg.pr.new/@vue/compiler-sfc@15028
yarn add https://pkg.pr.new/@vue/compiler-sfc@15028.tgz

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@15028
npm i https://pkg.pr.new/@vue/compiler-ssr@15028
yarn add https://pkg.pr.new/@vue/compiler-ssr@15028.tgz

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@15028
npm i https://pkg.pr.new/@vue/reactivity@15028
yarn add https://pkg.pr.new/@vue/reactivity@15028.tgz

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@15028
npm i https://pkg.pr.new/@vue/runtime-core@15028
yarn add https://pkg.pr.new/@vue/runtime-core@15028.tgz

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@15028
npm i https://pkg.pr.new/@vue/runtime-dom@15028
yarn add https://pkg.pr.new/@vue/runtime-dom@15028.tgz

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@15028
npm i https://pkg.pr.new/@vue/server-renderer@15028
yarn add https://pkg.pr.new/@vue/server-renderer@15028.tgz

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@15028
npm i https://pkg.pr.new/@vue/shared@15028
yarn add https://pkg.pr.new/@vue/shared@15028.tgz

vue

pnpm add https://pkg.pr.new/vue@15028
npm i https://pkg.pr.new/vue@15028
yarn add https://pkg.pr.new/vue@15028.tgz

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@15028
npm i https://pkg.pr.new/@vue/compat@15028
yarn add https://pkg.pr.new/@vue/compat@15028.tgz

commit: ba64eeb

@ifer47 ifer47 marked this pull request as ready for review June 29, 2026 09:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts (1)

241-272: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a direct never regression assertion.

This fixture proves the boolean-constrained path, but it never hits the empty runtime type list that packages/compiler-sfc/src/script/defineProps.ts Line 233 now normalizes from [] to ['null']. Adding a prop that resolves fully to never would cover the exact branch this PR changed and prevent type: [] from regressing silently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts` around
lines 241 - 272, The current defineProps boolean-constraint test covers the
generic boolean path but misses the runtime normalization branch in
defineProps.ts where an empty type list is converted from [] to ['null']. Extend
the existing compileScript/defineProps.spec.ts fixture by adding a prop in the
same Props<TMultiple> setup that resolves to never, and assert its generated
runtime prop shape so the test directly exercises the empty-runtime-types case
and guards the new normalization behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/compiler-sfc/src/script/resolveType.ts`:
- Around line 2051-2053: `inferRuntimeType` can now return an empty array for
`TSNeverKeyword`, but callers like `defineModel` and the `keyof` index-signature
handling in `resolveType` are not guarding against that shape. Update the
consumers of `inferRuntimeType` so they explicitly handle `[]` before using the
result: in `defineModel`, avoid passing an empty array into
`toRuntimeTypeString` and skip or default the generated runtime `type` when it
would otherwise become undefined; in the `keyof` path in `resolveType`, check
for an empty array before reading `[0]` so `undefined` is never added to the key
set.

---

Nitpick comments:
In `@packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts`:
- Around line 241-272: The current defineProps boolean-constraint test covers
the generic boolean path but misses the runtime normalization branch in
defineProps.ts where an empty type list is converted from [] to ['null']. Extend
the existing compileScript/defineProps.spec.ts fixture by adding a prop in the
same Props<TMultiple> setup that resolves to never, and assert its generated
runtime prop shape so the test directly exercises the empty-runtime-types case
and guards the new normalization behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b5d23ffe-4ec8-42ce-bb4d-7b8a6414901c

📥 Commits

Reviewing files that changed from the base of the PR and between c0606e9 and ba64eeb.

⛔ Files ignored due to path filters (1)
  • packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineProps.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (3)
  • packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
  • packages/compiler-sfc/src/script/defineProps.ts
  • packages/compiler-sfc/src/script/resolveType.ts

Comment on lines +2051 to +2053
case 'TSNeverKeyword':
return []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all inferRuntimeType call sites and surrounding usage to check for [0]/non-empty assumptions
rg -nP -C3 '\binferRuntimeType\s*\(' packages/compiler-sfc/src

Repository: vuejs/core

Length of output: 13450


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant sections around inferRuntimeType and its direct consumers.
sed -n '1688,1738p' packages/compiler-sfc/src/script/resolveType.ts
printf '\n----\n'
sed -n '2166,2192p' packages/compiler-sfc/src/script/resolveType.ts
printf '\n----\n'
sed -n '2008,2058p' packages/compiler-sfc/src/script/resolveType.ts
printf '\n----\n'
sed -n '2100,2140p' packages/compiler-sfc/src/script/resolveType.ts
printf '\n----\n'
sed -n '210,245p' packages/compiler-sfc/src/script/defineProps.ts
printf '\n----\n'
sed -n '118,150p' packages/compiler-sfc/src/script/defineModel.ts

# Find direct index / length-sensitive usage of inferRuntimeType results.
printf '\n==== direct usage search ====\n'
rg -n -C2 'inferRuntimeType\([^)]*\)\s*\[0\]|inferRuntimeType\([^)]*\)\.length|inferRuntimeType\([^)]*\)\s*$' packages/compiler-sfc/src/script

Repository: vuejs/core

Length of output: 9665


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect runtime-type string generation and other downstream consumers for empty-array assumptions.
rg -n -C2 'function toRuntimeTypeString|const toRuntimeTypeString|toRuntimeTypeString\(' packages/compiler-sfc/src/script

printf '\n----\n'
rg -n -C2 'inferRuntimeType\(ctx, .*?\)\[0\]|\.includes\(UNKNOWN_TYPE\)|\.filter\(t => t !== UNKNOWN_TYPE\)|type\.length \? type' packages/compiler-sfc/src/script

printf '\n----\n'
sed -n '1,120p' packages/compiler-sfc/src/script/defineModel.ts

Repository: vuejs/core

Length of output: 8937


Handle the empty-array case before consumers use it
inferRuntimeType can now return [] for TSNeverKeyword, but not all callers handle that shape:

  • packages/compiler-sfc/src/script/defineModel.ts:131 passes the result to toRuntimeTypeString, which returns undefined for an empty array and can emit invalid type: undefined.
  • packages/compiler-sfc/src/script/resolveType.ts:1713 indexes [0] in the keyof index-signature path, so an empty result can leak undefined into the set.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/compiler-sfc/src/script/resolveType.ts` around lines 2051 - 2053,
`inferRuntimeType` can now return an empty array for `TSNeverKeyword`, but
callers like `defineModel` and the `keyof` index-signature handling in
`resolveType` are not guarding against that shape. Update the consumers of
`inferRuntimeType` so they explicitly handle `[]` before using the result: in
`defineModel`, avoid passing an empty array into `toRuntimeTypeString` and skip
or default the generated runtime `type` when it would otherwise become
undefined; in the `keyof` path in `resolveType`, check for an empty array before
reading `[0]` so `undefined` is never added to the key set.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Boolean casting not working when using type-only props with generic components

1 participant