@@ -6,6 +6,7 @@ import type { IrReport } from "./ir/types";
66import type { Register } from "./register" ;
77import {
88 type CanonicalUrl ,
9+ type ChoiceFieldInstance ,
910 type ConstrainedChoiceInfo ,
1011 type Field ,
1112 type Identifier ,
@@ -314,6 +315,50 @@ export const mkTypeSchemaIndex = (
314315 return findLastSpecialization ( schema ) . identifier ;
315316 } ;
316317
318+ /** Narrow choice declarations by finding the most derived schema that constrains each choice group.
319+ * When a child profile declares only specific choice instances without re-declaring the declaration,
320+ * restrict the declaration's choices array to only the allowed instances. */
321+ const narrowMergedChoiceDeclarations = (
322+ mergedFields : Record < string , Field > ,
323+ constraintSchemas : TypeSchema [ ] ,
324+ ) : Record < string , Field > => {
325+ const result = { ...mergedFields } ;
326+ for ( const [ declName , declField ] of Object . entries ( result ) ) {
327+ if ( ! isChoiceDeclarationField ( declField ) || declField . excluded ) continue ;
328+
329+ for ( const cSchema of constraintSchemas ) {
330+ const sFields = ( cSchema as RegularTypeSchema ) . fields ;
331+ if ( ! sFields ) continue ;
332+ if ( sFields [ declName ] && isChoiceDeclarationField ( sFields [ declName ] ) ) continue ;
333+
334+ const instancesInSchema = Object . entries ( sFields )
335+ . filter ( ( [ _ , f ] ) => isChoiceInstanceField ( f ) && ( f as ChoiceFieldInstance ) . choiceOf === declName )
336+ . map ( ( [ name ] ) => name ) ;
337+ if ( instancesInSchema . length === 0 ) continue ;
338+
339+ const allowed = new Set ( instancesInSchema ) ;
340+ result [ declName ] = { ...declField , choices : declField . choices . filter ( ( c ) => allowed . has ( c ) ) } ;
341+ break ;
342+ }
343+ }
344+
345+ // Compute prohibited for all choice declarations
346+ for ( const [ declName , declField ] of Object . entries ( result ) ) {
347+ if ( ! isChoiceDeclarationField ( declField ) ) continue ;
348+ const permitted = new Set ( declField . excluded ? [ ] : declField . choices ) ;
349+ const prohibited = Object . entries ( result )
350+ . filter (
351+ ( e ) : e is [ string , ChoiceFieldInstance ] =>
352+ isChoiceInstanceField ( e [ 1 ] ) && e [ 1 ] . choiceOf === declName ,
353+ )
354+ . filter ( ( [ name ] ) => ! permitted . has ( name ) )
355+ . map ( ( [ name ] ) => name ) ;
356+ if ( prohibited . length > 0 ) result [ declName ] = { ...declField , prohibited } ;
357+ }
358+
359+ return result ;
360+ } ;
361+
317362 const flatProfile = ( schema : ProfileTypeSchema ) : ProfileTypeSchema => {
318363 const hierarchySchemas = hierarchy ( schema ) ;
319364 const constraintSchemas = hierarchySchemas . filter ( ( s ) => s . identifier . kind === "profile" ) ;
@@ -339,6 +384,8 @@ export const mkTypeSchemaIndex = (
339384 }
340385 }
341386
387+ const narrowedFields = narrowMergedChoiceDeclarations ( mergedFields , constraintSchemas ) ;
388+
342389 const dependencies = Object . values (
343390 Object . fromEntries (
344391 constraintSchemas
@@ -362,7 +409,7 @@ export const mkTypeSchemaIndex = (
362409 return {
363410 ...schema ,
364411 base : nonConstraintSchema . identifier ,
365- fields : mergedFields ,
412+ fields : narrowedFields ,
366413 dependencies : dependencies ,
367414 extensions : mergedExtensions . length > 0 ? mergedExtensions : undefined ,
368415 } ;
0 commit comments