3333import org .pkl .core .CommandSpec ;
3434import org .pkl .core .CommandSpec .OptionType ;
3535import org .pkl .core .CommandSpec .OptionType .Collection ;
36+ import org .pkl .core .CommandSpec .OptionType .Enum ;
3637import org .pkl .core .CommandSpec .OptionType .Primitive ;
3738import org .pkl .core .FileOutput ;
3839import org .pkl .core .PNull ;
3940import org .pkl .core .PType ;
40- import org .pkl .core .PklBugException ;
4141import org .pkl .core .SecurityManager ;
4242import org .pkl .core .SecurityManagerException ;
4343import org .pkl .core .ast .ExpressionNode ;
5050import org .pkl .core .ast .member .DefaultPropertyBodyNode ;
5151import org .pkl .core .ast .member .ModuleNode ;
5252import org .pkl .core .ast .member .ObjectMember ;
53+ import org .pkl .core .ast .member .PropertyTypeNode ;
5354import org .pkl .core .ast .type .TypeNode ;
5455import org .pkl .core .ast .type .UnresolvedTypeNode ;
5556import org .pkl .core .externalreader .ExternalReaderProcessException ;
@@ -115,43 +116,36 @@ public CommandSpec parse(VmTyped commandModule) {
115116 private VmClass getOptionsClass (VmTyped commandModule ) {
116117 var optionsProperty = commandModule .getVmClass ().getProperty (Identifier .OPTIONS );
117118 if (optionsProperty == null ) {
118- throw new PklBugException ("no property `options` found in pkl:Command sub-module" );
119+ throw exceptionBuilder ()
120+ .cannotFindMember (commandModule , Identifier .OPTIONS )
121+ .withSourceSection (commandModule .getVmClass ().getSourceSection ())
122+ .build ();
119123 }
120124 var optionsPropertyTypeNode = optionsProperty .getTypeNode ();
121125 if (optionsPropertyTypeNode == null ) {
122- throw new RuntimeException (
123- "no type annotation on `options` property in pkl:Command sub-module" ); // TODO
126+ throw exceptionBuilder ()
127+ .withSourceSection (optionsProperty .getSourceSection ())
128+ .evalError ("commandOptionNoTypeAnnotation" , "options" , " in `pkl:Command` subclass" )
129+ .build ();
124130 }
125131 var optionsTypeNode = optionsPropertyTypeNode .getTypeNode ();
126132 if (!(optionsTypeNode instanceof TypeNode .ClassTypeNode node )) {
127- throw new RuntimeException (
128- "type annotation on `options` property in pkl:Command sub-module is not a class type" ); // TODO
133+ throw exceptionBuilder ()
134+ .withSourceSection (optionsTypeNode .getSourceSection ())
135+ .evalError (
136+ "commandOptionsTypeNotClass" , optionsTypeNode .getSourceSection ().getCharacters ())
137+ .build ();
129138 }
130139 var clazz = node .getVmClass ();
131140 if (clazz .isAbstract ()) {
132- throw new RuntimeException (
133- "class of `options` property in pkl:Command sub-module must not be abstact" ); // TODO
141+ throw exceptionBuilder ()
142+ .withSourceSection (clazz .getHeaderSection ())
143+ .evalError ("commandOptionsTypeAbstractClass" , clazz .getQualifiedName ())
144+ .build ();
134145 }
135146 return clazz ;
136147 }
137148
138- // name from property name
139- // description from doc comment
140- // required if not nullable
141- // parse: when specified, don't validate for known option types
142- // args:
143- // * multiple may be specified
144- // * no List allowed if command has children
145- // * only last arg may be a List
146- // types:
147- // * Primitive:
148- // * Number, Float, Int, UInt, Int8, Int16, Int32, UInt8, UInt16, UInt32
149- // * Boolean
150- // * String, Char
151- // * Enum: union of string literals
152- // * Collection: List<Primitive>, Set<Primitive>
153- // * Map: Map<Primitive, Primitive>
154-
155149 private Pair <List <CommandSpec .Flag >, List <CommandSpec .Argument >> collectOptions (
156150 VmClass optionsClass ) {
157151 var flags = new ArrayList <CommandSpec .Flag >();
@@ -183,18 +177,24 @@ private Pair<List<CommandSpec.Flag>, List<CommandSpec.Argument>> collectOptions(
183177
184178 var defaultValue = getDefaultValue (prop );
185179 if (flagAnnotation != null && argAnnotation != null ) {
186- throw new RuntimeException (
187- "found both @Flag and @Argument annotations for property `XXX`" ); // TODO
180+ throw exceptionBuilder ()
181+ .withSourceSection (prop .getSourceSection ())
182+ .evalError ("commandOptionBothFlagAndArgument" , prop .getName ())
183+ .build ();
188184 } else if (argAnnotation != null ) {
189185 if (defaultValue != null ) {
190- throw new RuntimeException (
191- "unexpected default value for @Argument property `XXX`" ); // TODO
186+ throw exceptionBuilder ()
187+ .withSourceSection (prop .getSourceSection ())
188+ .evalError ("commandArgumentUnexpectedDefaultValue" , prop .getName ())
189+ .build ();
192190 }
193191 var parseFunction = getParseFunction (argAnnotation );
194192 var type = getOptionType (prop , parseFunction , null );
195193 if (type instanceof OptionType .Map ) {
196- throw new RuntimeException (
197- "@Argument option cannot have `Map` type for property `XXX`" ); // TODO
194+ throw exceptionBuilder ()
195+ .withSourceSection (prop .getSourceSection ())
196+ .evalError ("commandArgumentUnexpectedMapType" , prop .getName ())
197+ .build ();
198198 }
199199 argNames .add (name );
200200 args .add (
@@ -210,7 +210,10 @@ private Pair<List<CommandSpec.Flag>, List<CommandSpec.Argument>> collectOptions(
210210 hide = (Boolean ) VmUtils .readMember (flagAnnotation , Identifier .HIDE );
211211 }
212212 if ("help" .equals (name ) || "h" .equals (shortName )) {
213- throw new RuntimeException ("flags may not have name 'help' or short name 'h'" ); // TODO
213+ throw exceptionBuilder ()
214+ .withSourceSection (prop .getSourceSection ())
215+ .evalError ("commandFlagHelpCollision" , prop .getName ())
216+ .build ();
214217 }
215218
216219 flagNames .add (name );
@@ -232,7 +235,10 @@ private Pair<List<CommandSpec.Flag>, List<CommandSpec.Argument>> collectOptions(
232235 for (var arg : args ) {
233236 if (arg .type () instanceof Collection ) {
234237 if (multipleArgName != null ) {
235- throw new RuntimeException ("more than one List/Set @Argument found" ); // TODO
238+ throw exceptionBuilder ()
239+ .withSourceSection (optionsClass .getSourceSection ())
240+ .evalError ("commandArgumentsMultipleListOrSet" )
241+ .build ();
236242 }
237243 multipleArgName = arg .name ();
238244 }
@@ -249,15 +255,26 @@ private Pair<List<CommandSpec.Flag>, List<CommandSpec.Argument>> collectOptions(
249255 if (bodyNode == null || bodyNode .getBodyNode () instanceof DefaultPropertyBodyNode ) {
250256 var typeNode = prop .getTypeNode ();
251257 if (typeNode == null ) {
252- throw new RuntimeException ("missing type annotation for property `XXX`" ); // TODO
258+ throw exceptionBuilder ()
259+ .withSourceSection (prop .getSourceSection ())
260+ .evalError ("commandOptionNoTypeAnnotation" , prop .getName (), "" )
261+ .withHint ("Option properties require type annotations." )
262+ .build ();
253263 }
254264
255265 if (stripConstraints (typeNode .export ()) instanceof PType .Union union
256266 && union .getDefaultElement () != null ) {
257267 var elements = union .getElementTypes ();
258268 for (var element : elements ) {
259269 if (!(element instanceof PType .StringLiteral )) {
260- throw new RuntimeException ("Property doesn't have valid option type" ); // TODO
270+ throw exceptionBuilder ()
271+ .withSourceSection (prop .getSourceSection ())
272+ .evalError (
273+ "commandOptionUnsupportedType" ,
274+ prop .getName (),
275+ "" ,
276+ typeNode .getSourceSection ().getCharacters ())
277+ .build ();
261278 }
262279 }
263280 return ((PType .StringLiteral ) union .getDefaultElement ()).getLiteral ();
@@ -287,14 +304,19 @@ private OptionType getOptionType(
287304 @ Nullable Object defaultValue ) {
288305 var typeNode = prop .getTypeNode ();
289306 if (typeNode == null ) {
290- throw new RuntimeException ("missing type annotation for property `XXX`" ); // TODO
307+ throw exceptionBuilder ()
308+ .withSourceSection (prop .getSourceSection ())
309+ .evalError ("commandOptionNoTypeAnnotation" , prop .getName (), "" )
310+ .build ();
291311 }
292312 var type = stripConstraints (typeNode .export ());
293313 var required = defaultValue == null ;
294314 if (type instanceof PType .Nullable nullable ) {
295315 if (!required ) {
296- throw new RuntimeException (
297- "unexpected nullable type with default for property `XXX`" ); // TODO
316+ throw exceptionBuilder ()
317+ .withSourceSection (prop .getSourceSection ())
318+ .evalError ("commandOptionTypeNullableWithDefaultValue" , prop .getName ())
319+ .build ();
298320 }
299321 required = false ;
300322 type = nullable .getBaseType ();
@@ -303,10 +325,11 @@ private OptionType getOptionType(
303325 return new Primitive (Primitive .Type .STRING , required );
304326 }
305327
306- return getOptionType (type , required );
328+ return getOptionType (prop , typeNode , type , required );
307329 }
308330
309- private OptionType getOptionType (PType type , boolean required ) {
331+ private OptionType getOptionType (
332+ ClassProperty prop , PropertyTypeNode typeNode , PType type , boolean required ) {
310333 type = stripConstraints (type );
311334 if (type instanceof PType .Class classType ) {
312335 var clazz = classType .getPClass ();
@@ -321,27 +344,39 @@ private OptionType getOptionType(PType type, boolean required) {
321344 } else if (clazz == BaseModule .getStringClass ().export ()) {
322345 return new Primitive (Primitive .Type .STRING , required );
323346 } else if (clazz == BaseModule .getListClass ().export ()) {
324- var elementType = getOptionType (classType .getTypeArguments ().get (0 ), true );
325- if (!(elementType instanceof Primitive primitive )) {
326- throw new RuntimeException ("Property doesn't have valid option type" ); // TODO
347+ var elementType = getOptionType (prop , typeNode , classType .getTypeArguments ().get (0 ), true );
348+ if (!(elementType instanceof Primitive || elementType instanceof Enum )) {
349+ throw exceptionBuilder ()
350+ .withSourceSection (prop .getSourceSection ())
351+ .evalError ("commandOptionUnsupportedType" , prop .getName (), "element " , elementType )
352+ .build ();
327353 }
328- return new Collection (Collection .Type .LIST , primitive , required );
354+ return new Collection (Collection .Type .LIST , elementType , required );
329355 } else if (clazz == BaseModule .getSetClass ().export ()) {
330- var elementType = getOptionType (classType .getTypeArguments ().get (0 ), true );
331- if (!(elementType instanceof Primitive primitive )) {
332- throw new RuntimeException ("Property doesn't have valid option type" ); // TODO
356+ var elementType = getOptionType (prop , typeNode , classType .getTypeArguments ().get (0 ), true );
357+ if (!(elementType instanceof Primitive || elementType instanceof Enum )) {
358+ throw exceptionBuilder ()
359+ .withSourceSection (prop .getSourceSection ())
360+ .evalError ("commandOptionUnsupportedType" , prop .getName (), "element " , elementType )
361+ .build ();
333362 }
334- return new Collection (Collection .Type .SET , primitive , required );
363+ return new Collection (Collection .Type .SET , elementType , required );
335364 } else if (clazz == BaseModule .getMapClass ().export ()) {
336- var keyType = getOptionType (classType .getTypeArguments ().get (0 ), true );
337- if (!(keyType instanceof Primitive primitiveKeyType )) {
338- throw new RuntimeException ("Property doesn't have valid option type" ); // TODO
365+ var keyType = getOptionType (prop , typeNode , classType .getTypeArguments ().get (0 ), true );
366+ if (!(keyType instanceof Primitive || keyType instanceof Enum )) {
367+ throw exceptionBuilder ()
368+ .withSourceSection (prop .getSourceSection ())
369+ .evalError ("commandOptionUnsupportedType" , prop .getName (), "key " , keyType )
370+ .build ();
339371 }
340- var valueType = getOptionType (classType .getTypeArguments ().get (1 ), true );
341- if (!(valueType instanceof Primitive primitiveValueType )) {
342- throw new RuntimeException ("Property doesn't have valid option type" ); // TODO
372+ var valueType = getOptionType (prop , typeNode , classType .getTypeArguments ().get (1 ), true );
373+ if (!(valueType instanceof Primitive || valueType instanceof Enum )) {
374+ throw exceptionBuilder ()
375+ .withSourceSection (prop .getSourceSection ())
376+ .evalError ("commandOptionUnsupportedType" , prop .getName (), "value " , valueType )
377+ .build ();
343378 }
344- return new OptionType .Map (primitiveKeyType , primitiveValueType , required );
379+ return new OptionType .Map (keyType , valueType , required );
345380 }
346381 } else if (type instanceof PType .Alias aliasType ) {
347382 var alias = aliasType .getTypeAlias ();
@@ -367,14 +402,28 @@ private OptionType getOptionType(PType type, boolean required) {
367402 var choices = new ArrayList <String >(elements .size ());
368403 for (var element : elements ) {
369404 if (!(element instanceof PType .StringLiteral stringLiteral )) {
370- throw new RuntimeException ("Property doesn't have valid option type" ); // TODO
405+ throw exceptionBuilder ()
406+ .withSourceSection (prop .getSourceSection ())
407+ .evalError (
408+ "commandOptionUnsupportedType" ,
409+ prop .getName (),
410+ "" ,
411+ typeNode .getSourceSection ().getCharacters ())
412+ .build ();
371413 }
372414 choices .add (stringLiteral .getLiteral ());
373415 }
374416 return new OptionType .Enum (choices , required );
375417 }
376418
377- throw new RuntimeException ("Property doesn't have valid option type" ); // TODO
419+ throw exceptionBuilder ()
420+ .withSourceSection (prop .getSourceSection ())
421+ .evalError (
422+ "commandOptionUnsupportedType" ,
423+ prop .getName (),
424+ "" ,
425+ typeNode .getSourceSection ().getCharacters ())
426+ .build ();
378427 }
379428
380429 private static PType stripConstraints (PType type ) {
@@ -452,7 +501,13 @@ private List<CommandSpec> collectSubcommands(VmTyped commandInfo) {
452501 (key , member , value ) -> {
453502 var spec = parse ((VmTyped ) value );
454503 if (subcommandNames .contains (spec .name ())) {
455- throw new RuntimeException ("command XXX has duplicate subcommands named YYY" ); // TODO
504+ throw exceptionBuilder ()
505+ .withSourceSection (member .getSourceSection ())
506+ .evalError (
507+ "commandSubcommandConflict" ,
508+ VmUtils .readMember (commandInfo , Identifier .NAME ),
509+ spec .name ())
510+ .build ();
456511 }
457512 subcommandNames .add (spec .name ());
458513 subcommands .add (spec );
@@ -630,13 +685,13 @@ private void checkPropertyIsUndefined(VmTyped value, Identifier name) {
630685 if (member != null ) {
631686 var memberNode = member .getMemberNode ();
632687 if (memberNode == null ) {
633- throw new VmExceptionBuilder () // TODO
634- .adhocEvalError ( "Command modules must not assign or amend the `%s` property " , name )
688+ throw new VmExceptionBuilder ()
689+ .evalError ( "commandMustNotAssignOrAmendProperty " , name )
635690 .withSourceSection (member .getSourceSection ())
636691 .build ();
637692 } else if (!(memberNode .getBodyNode () instanceof DefaultPropertyBodyNode )) {
638- throw new VmExceptionBuilder () // TODO
639- .adhocEvalError ( "Command modules must not assign or amend the `%s` property " , name )
693+ throw new VmExceptionBuilder ()
694+ .evalError ( "commandMustNotAssignOrAmendProperty " , name )
640695 .withSourceSection (memberNode .getSourceSection ())
641696 .build ();
642697 }
0 commit comments