@@ -153,6 +153,13 @@ let get_columns = (ty: Typ.t): option(list(string)) => {
153153 };
154154};
155155
156+ /* Check if a type is an Option type (+None +Some(?)) */
157+ let is_option_type = (ty: Typ . t ): bool => {
158+ let ctx = Builtins . ctx_init(Some (Int ));
159+ Typ . is_consistent(ctx, ty, BuiltinsADT . Option . t)
160+ && Typ . is_more_precise(ctx, ty, BuiltinsADT . Option . t);
161+ };
162+
156163let strip_parens =
157164 Exp . map_term(~f_exp= (continue, e) =>
158165 switch (e. term) {
@@ -343,6 +350,81 @@ let filter_by_column = (op, info: info, column: string): Base.segment => {
343350 );
344351};
345352
353+ /* Drop rows where the option column is None, unwrapping Some values */
354+ let drop_nones_column = (info: info , column: string ): Base . segment => {
355+ IdTagged . FreshGrammar . (
356+ apply_transformation(
357+ info,
358+ Exp . (
359+ ap(
360+ Forward ,
361+ var("filter_map" ),
362+ tuple([
363+ deferral(InAp ),
364+ fn(
365+ Pat . var("row" ),
366+ ap(
367+ Forward ,
368+ var("option_map" ),
369+ tuple([
370+ dot(var("row" ), label(column)),
371+ fn(
372+ Pat . var("v" ),
373+ tuple_extension(
374+ var("row" ),
375+ tuple([ tup_label(label(column), var("v" ))] ),
376+ ),
377+ None ,
378+ None ,
379+ ),
380+ ] ),
381+ ),
382+ None ,
383+ None ,
384+ ),
385+ ] ),
386+ )
387+ ),
388+ )
389+ );
390+ };
391+
392+ /* Replace None values with an expression hole for user to fill in default */
393+ let provide_default_column = (info: info , column: string ): Base . segment => {
394+ IdTagged . FreshGrammar . (
395+ apply_rowwise_transformation(
396+ info,
397+ Exp . (
398+ fn(
399+ Pat . var("row" ),
400+ tuple_extension(
401+ var("row" ),
402+ tuple([
403+ tup_label(
404+ label(column),
405+ match(
406+ dot(var("row" ), label(column)),
407+ [
408+ /* None => hole for user to fill in */
409+ (BuiltinsADT . Option . pat_none, empty_hole() ),
410+ /* Some(x) => x */
411+ (
412+ Pat . ap(BuiltinsADT . Option . pat_some, Pat . var("v" )),
413+ var("v" ),
414+ ),
415+ ] ,
416+ ),
417+ ),
418+ ] ),
419+ ),
420+ None ,
421+ None ,
422+ )
423+ ),
424+ )
425+ );
426+ };
427+
346428let get_dynamic_type = (exp: Exp . t ): option(Typ . t) => {
347429 let statics = Statics . mk(CoreSettings . on, Builtins . ctx_init(Some (Int )));
348430 IdTagged . rep_id(exp)
@@ -807,6 +889,33 @@ let build_column_menu =
807889 | None => []
808890 };
809891
892+ /* Option type actions: Drop Nones and Provide Default */
893+ let option_items =
894+ switch (column_type) {
895+ | Some (ty ) =>
896+ is_option_type(ty)
897+ ? [
898+ Action ({
899+ text: "Drop Nones" ,
900+ action: () =>
901+ Effect . Many ([
902+ local(CloseMenu ),
903+ parent(SetSyntax (drop_nones_column(info, h))),
904+ ] ),
905+ }),
906+ Action ({
907+ text: "Provide Default" ,
908+ action: () =>
909+ Effect . Many ([
910+ local(CloseMenu ),
911+ parent(SetSyntax (provide_default_column(info, h))),
912+ ] ),
913+ }),
914+ ]
915+ : []
916+ | None => []
917+ };
918+
810919 base_items
811920 @ [
812921 Action ({
@@ -821,7 +930,8 @@ let build_column_menu =
821930 @ conversion_submenu
822931 @ move_items
823932 @ sort_submenu
824- @ filter_submenu;
933+ @ filter_submenu
934+ @ option_items;
825935 | _ => [] // Unknown menu path
826936 };
827937};
0 commit comments