@@ -104,16 +104,43 @@ def _parse_need_type(
104104 global_base_opts : dict [str , Any ],
105105):
106106 """Build a single ScoreNeedType dict from the metamodel entry, incl defaults."""
107+
108+ # Check for overlapping option names between mandatory / optional options / links
109+ # and global_base_opts, as this would cause issues for usability.
110+ mandatory_options = yaml_data .get ("mandatory_options" , {})
111+ optional_options = yaml_data .get ("optional_options" , {})
112+ mandatory_links = yaml_data .get ("mandatory_links" , {})
113+ optional_links = yaml_data .get ("optional_links" , {})
114+
115+ overlap_checks : list [tuple [str , dict [str , Any ], str , dict [str , Any ]]] = [
116+ ("mandatory_options" , mandatory_options , "optional_options" , optional_options ),
117+ ("mandatory_options" , mandatory_options , "global_base_opts" , global_base_opts ),
118+ ("optional_options" , optional_options , "global_base_opts" , global_base_opts ),
119+ ("mandatory_links" , mandatory_links , "optional_links" , optional_links ),
120+ ("mandatory_links" , mandatory_links , "global_base_opts" , global_base_opts ),
121+ ("optional_links" , optional_links , "global_base_opts" , global_base_opts ),
122+ ]
123+ errors : list [str ] = []
124+ for a_name , a , b_name , b in overlap_checks :
125+ if overlap := set (a .keys ()) & set (b .keys ()):
126+ # FIXME: remove once "version" is clarified with process team
127+ if overlap == {"version" }:
128+ continue
129+
130+ errors .append (
131+ f"Directive '{ directive_name } ': { a_name } and { b_name } overlap: { overlap } ."
132+ )
133+
107134 t : ScoreNeedType = {
108135 "directive" : directive_name ,
109136 "title" : yaml_data ["title" ],
110137 "prefix" : yaml_data .get ("prefix" , f"{ directive_name } __" ),
111138 "tags" : yaml_data .get ("tags" , []),
112139 "parts" : yaml_data .get ("parts" , 3 ),
113- "mandatory_options" : yaml_data . get ( " mandatory_options" , {}) ,
114- "optional_options" : yaml_data . get ( " optional_options" , {}) | global_base_opts ,
115- "mandatory_links" : yaml_data . get ( " mandatory_links" , {}) ,
116- "optional_links" : yaml_data . get ( " optional_links" , {}) ,
140+ "mandatory_options" : mandatory_options ,
141+ "optional_options" : optional_options | global_base_opts ,
142+ "mandatory_links" : mandatory_links ,
143+ "optional_links" : optional_links ,
117144 }
118145
119146 # Ensure ID regex is set
@@ -126,7 +153,7 @@ def _parse_need_type(
126153 if "style" in yaml_data :
127154 t ["style" ] = yaml_data ["style" ]
128155
129- return t
156+ return t , errors
130157
131158
132159def _parse_needs_types (
@@ -136,14 +163,21 @@ def _parse_needs_types(
136163 """Parse the 'needs_types' section of the metamodel.yaml."""
137164
138165 needs_types : dict [str , ScoreNeedType ] = {}
166+ all_errors : list [str ] = []
139167 for directive_name , directive_data in types_dict .items ():
140168 assert isinstance (directive_name , str )
141169 assert isinstance (directive_data , dict )
142170
143- needs_types [directive_name ] = _parse_need_type (
171+ needs_types [directive_name ], parsing_errors = _parse_need_type (
144172 directive_name , directive_data , global_base_options_optional_opts
145173 )
174+ all_errors .extend (parsing_errors )
146175
176+ if all_errors :
177+ raise SystemExit (
178+ "ERROR: Please resolve these overlaps in the metamodel.yaml to ensure proper functionality:\n "
179+ + "\n " .join (all_errors )
180+ )
147181 return needs_types
148182
149183
@@ -207,7 +241,6 @@ def load_metamodel_data(yaml_path: Path | None = None) -> MetaModelData:
207241 )
208242
209243 # Convert "types" from {directive_name: {...}, ...} to a list of dicts
210-
211244 needs_types = _parse_needs_types (
212245 data .get ("needs_types" , {}), global_base_options_optional_opts
213246 )
0 commit comments