!wfudwzqQUiJYJnqfSY:nixos.org

NixOS Module System

203 Members
48 Servers

Load older messages


SenderMessageTime
8 May 2026
@jopejoe1:matrix.orgjopejoe1 changed their display name from jopejoe1 (4094@epvpn) to jopejoe1.08:46:19
9 May 2026
@me:linj.techlinj joined the room.05:02:21
11 May 2026
@maelys_pan:matrix.orgMaëlys joined the room.20:40:21
12 May 2026
@artify:artify.zoneRichard Tichý joined the room.11:19:41
@koutensky:matrix.nesad.fit.vutbr.czMichal Koutenský joined the room.18:16:59
14 May 2026
@cwtuh:matrix.orgtuhana joined the room.15:12:49
19 May 2026
@snow101:matrix.org@snow101:matrix.org left the room.08:37:05
@amadaluzia:4d2.orgamadaluzia changed their profile picture.20:58:55
22 May 2026
@polykernel:kde.orgpolykernel joined the room.16:40:58
@polykernel:kde.orgpolykernel What is the best way to propagate checks added by addCheck in the type merge process? I tried overriding functor in the extended type but the functor attached to the type after merging options declarations is not the one I overwrote. 17:00:24
@polykernel:kde.orgpolykernel

Here is a MWE:

{
  pkgs ? import <nixpkgs> { },
  lib ? pkgs.lib,
}:

let
  testSubmodule =
    { config, ... }:
    {
      freeformType = lib.types.lazyAttrsOf lib.types.raw;

      options.a = lib.mkOption {
        type = lib.types.int;
        default = 0;
      };
    };

  isSubmoduleFn =
    m:
    let
      args = builtins.functionArgs m;
    in
    args ? lib || args ? config || args ? options;

  staticInnerType =
    let
      baseType = lib.types.addCheck (lib.types.submodule testSubmodule) (
        v: builtins.isAttrs v || isSubmoduleFn v
      );
    in
    baseType
    // rec {
      functor = baseType.functor // {
        type =
          attrs:
          let
            mergedType = lib.types.addCheck (lib.types.submoduleWith attrs) (
              v: builtins.isAttrs v || isSubmoduleFn v
            );
          in
          mergedType
          // {
            inherit substSubModules;
            functor = mergedType.functor // {
              inherit (functor) type;
            };
          };
      };

      substSubModules =
        m:
        lib.types.addCheck (lib.types.submodule m) (v: builtins.isAttrs v || isSubmoduleFn v)
        // {
          inherit substSubModules;
        };
    };

  dynamicInnerType = lib.types.functionTo staticInnerType;

  innerType = lib.types.either staticInnerType dynamicInnerType // rec {
    substSubModules =
      m:
      (lib.types.either (staticInnerType.substSubModules m) (dynamicInnerType.substSubModules m))
      // {
        inherit substSubModules;
      };
  };

  testType = lib.types.lazyAttrsOf innerType;

  optionModule1 = {
    options.test = lib.mkOption {
      type = testType;
    };
  };

  optionModule2 = {
    options.test = lib.mkOption {
      type = testType.substSubModules [
        {
          options.b = lib.mkOption {
            type = lib.types.bool;
          };
        }
      ];
    };
  };

  staticConfig = {
    config.test.random2 = {
      b = false;
    };
  };

  dynamicConfig = {
    config.test.random1 =
      { arg1 }:
      {
        a = 1;
      };
  };

  result = lib.evalModules {
    modules = [
      optionModule1
      optionModule2

      staticConfig
      dynamicConfig
    ];
  };
in
result
17:01:39
@polykernel:kde.orgpolykernel The check on staticInnerType gets dropped after the type merge. 17:02:19
@polykernel:kde.orgpolykernel * What is the best way to propagate checks added by addCheck in the type merge process? I tried overriding functor in the extended type but the functor attached to the type after merging options declarations is not one supplied in my override (I am not sure why this is the case?). 17:06:14
@jonhermansen:matrix.orgJon Hermansen changed their display name from jonhermansen to Jon Hermansen.19:18:49
@polykernel:kde.orgpolykernel

After some reading, I realized I needed to override typeMerge as overriding functor after mkOptionType has materialized does not change the typeMerge field. I ended up making a function that builds a custom submodule type instead of shallowing attributes. Here is the updated example that works in case it helps anyone in the future:

{
  pkgs ? import /home/sheaf/projects/github/nixpkgs { },
  lib ? pkgs.lib,
}:

let
  testSubmodule =
    { config, ... }:
    {
      freeformType = lib.types.lazyAttrsOf lib.types.raw;

      options.a = lib.mkOption {
        type = lib.types.int;
        default = 0;
      };
    };

  isSubmoduleFn =
    m:
    let
      args = builtins.functionArgs m;
    in
    args ? lib || args ? config || args ? options;

  customSubmoduleWith =
    attrs:
    let
      t = lib.types.addCheck (lib.types.submoduleWith attrs) (v: builtins.isAttrs v || isSubmoduleFn v);
    in
    t
    // rec {
      substSubmodules = m: customSubmoduleWith (attrs // { modules = m; });
      functor = t.functor // {
        type = customSubmoduleWith;
      };
      typeMerge = lib.types.defaultTypeMerge functor;
    };

  customSubmodule =
    module:
    customSubmoduleWith {
      shorthandOnlyDefinesConfig = true;
      modules = [ module ];
    };

  staticInnerType = customSubmodule testSubmodule;

  dynamicInnerType = lib.types.functionTo staticInnerType;

  innerType = lib.types.either staticInnerType dynamicInnerType;

  testType = lib.types.lazyAttrsOf innerType;

  optionModule1 = {
    options.test = lib.mkOption {
      type = testType;
    };
  };

  optionModule2 = {
    options.test = lib.mkOption {
      type =
        let
          t = customSubmodule {
            options.b = lib.mkOption {
              type = lib.types.bool;
            };
          };
        in
        lib.types.lazyAttrsOf (lib.types.either t (lib.types.functionTo t));
    };
  };

  staticConfig = {
    config.test.random2 = {
      b = false;
    };
  };

  dynamicConfig = {
    config.test.random1 =
      { arg1 }:
      {
        a = 1;
      };
  };

  result = lib.evalModules {
    modules = [
      optionModule1
      optionModule2

      staticConfig
      dynamicConfig
    ];
  };
in
result
21:50:44
@polykernel:kde.orgpolykernelRedacted or Malformed Event21:50:54
@polykernel:kde.orgpolykernel I found where my error is. I need to override typeMerge as well since shallowing functor after the mkOptionType call won't affect the typeMerge field. 21:53:07
@polykernel:kde.orgpolykernel

Here is the updated working example in case it helps anyone in the future:

{
  pkgs ? import <nixpkgs> { },
  lib ? pkgs.lib,
}:

let
  testSubmodule =
    { config, ... }:
    {
      freeformType = lib.types.lazyAttrsOf lib.types.raw;

      options.a = lib.mkOption {
        type = lib.types.int;
        default = 0;
      };
    };

  isSubmoduleFn =
    m:
    let
      args = builtins.functionArgs m;
    in
    args ? lib || args ? config || args ? options;

  customSubmoduleWith =
    attrs:
    let
      t = lib.types.addCheck (lib.types.submoduleWith attrs) (v: builtins.isAttrs v || isSubmoduleFn v);
    in
    t
    // rec {
      substSubmodules = m: customSubmoduleWith (attrs // { modules = m; });
      functor = t.functor // {
        type = customSubmoduleWith;
      };
      typeMerge = lib.types.defaultTypeMerge functor;
    };

  customSubmodule =
    module:
    customSubmoduleWith {
      shorthandOnlyDefinesConfig = true;
      modules = [ module ];
    };

  staticInnerType = customSubmodule testSubmodule;

  dynamicInnerType = lib.types.functionTo staticInnerType;

  innerType = lib.types.either staticInnerType dynamicInnerType;

  testType = lib.types.lazyAttrsOf innerType;

  optionModule1 = {
    options.test = lib.mkOption {
      type = testType;
    };
  };

  optionModule2 = {
    options.test = lib.mkOption {
      type =
        let
          t = customSubmodule {
            options.b = lib.mkOption {
              type = lib.types.bool;
            };
          };
        in
        lib.types.lazyAttrsOf (lib.types.either t (lib.types.functionTo t));
    };
  };

  staticConfig = {
    config.test.random2 = {
      b = false;
    };
  };

  dynamicConfig = {
    config.test.random1 =
      { arg1 }:
      {
        a = 1;
      };
  };

  result = lib.evalModules {
    modules = [
      optionModule1
      optionModule2

      staticConfig
      dynamicConfig
    ];
  };
in
result
21:54:16
@hsjobeki:matrix.orghsjobekihttps://github.com/NixOS/nixpkgs/issues/396021 This limitation of addCheck is known. Fyi the more "idiomatic" way is to use mkOptionType to create a custom type such as it is done in lib/types.nix itself. 22:06:04
@polykernel:kde.orgpolykernelAh, for some reason I skipped over the link to that issue in the comment when reading. Thanks for the pointer.22:16:22
@polykernel:kde.orgpolykernelI haven't done a lot of thinking on this but is it possible to overcome this limitation by using some fixpoint mechanism like overlays? The problem seems to be that the type functor only has access to the type when it is defined (at the mkOptionType call site), not the type that is actually materalized (after all // updates). If the materialized type is accessible to the type functor, I think we can create a replica of the type.22:31:18
23 May 2026
@mattsturg:matrix.orgMatt Sturgeon

using some fixpoint mechanism like overlays?

Maybe mkOptionType could apply mkExtensible, which adds the .extend attribute? addCheck could then apply an overlay to <type>.extend 🤔

20:30:14
24 May 2026
@jwh4j4ez25q:matrix.orgmaurice joined the room.18:17:10
26 May 2026
@nam3l33ss:matrix.org·☽•Nameless☆•777 · ± changed their profile picture.05:42:37
@phanirithvij:matrix.orgphanirithvij changed their display name from loudgolem to phanirithvij.11:41:58
27 May 2026
@isabel:isabelroses.comisabel changed their profile picture.21:17:16
28 May 2026
@define9293:matrix.orgdefine9293 joined the room.02:06:27
29 May 2026
@necoro:nixos.devNecoro joined the room.09:16:41
@louis2747:matrix.orgLouis2747 joined the room.09:52:55
@toonn:matrix.orgtoonn joined the room.10:16:32

Show newer messages


Back to Room ListRoom Version: 10