Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle submodules for type "either" #14053

Closed
wants to merge 1 commit into from

Conversation

aszlig
Copy link
Member

@aszlig aszlig commented Mar 19, 2016

So far the either type only handled "flat" types, so you couldn't do something like:

{
  type = either int (submodule {
    options = ...;
  });
}

Not only caused this the submodule's options not being checked but also
not show up in the documentation.

This was something we stumbled on with #13916.

Cc: @edolstra, @nbp

aszlig added a commit that referenced this pull request Mar 19, 2016
This reverts commit 0f0805b, because @nbp had concerns about whether
this would be a good idea and pointed out problems with this.

We currently do not have a case where "either" is used in conjunction
with submodules, but I'm reverting it anyway to prevent people from
adding options using that type in that way.

This is now being reviewed in #14053.

Signed-off-by: aszlig <aszlig@redmoonstudios.org>
So far the "either" type only handled "flat" types, so you couldn't do
something like:

type = either int (submodule {
  options = ...;
});

Not only caused this the submodule's options not being checked but also
not show up in the documentation.

This was something we stumbled on with NixOS#13916.

Signed-off-by: aszlig <aszlig@redmoonstudios.org>
Cc: @edolstra
@aszlig
Copy link
Member Author

aszlig commented Mar 19, 2016

For reference, I'm adding the concerns raised by @nbp here:

This patch adds ambiguous behaviour in the way options are processed. One simple value can change the way all other values are interpreted without any warning.

foo = {
  value = 42;
};

from one module can make the change the semantic of value if it is defined in both submodules.

If we are going to do so, I think the merge function should ensure that the set of options are completely disjoint.

In the mean time, I do not think this is a good idea, and suggest we should not accept this patch.
Also, why did this patch went into master without requesting a review?

aszlig referenced this pull request Mar 19, 2016
So far the "either" type only handled "flat" types, so you couldn't do
something like:

type = either int (submodule {
  options = ...;
});

Not only caused this the submodule's options not being checked but also
not show up in the documentation.

This was something we stumbled on with #13916.

Signed-off-by: aszlig <aszlig@redmoonstudios.org>
Cc: @edolstra
@aszlig
Copy link
Member Author

aszlig commented Mar 19, 2016

@nbp: And here again, I still don't understand what you mean by "changing the semantics", for example if I use something like this:

{ lib, ... }:

{
  options = {
    foo = lib.mkOption {
      type = with lib.types; either str int;
      description = "foo";
    };
  };

  imports = lib.singleton { foo = 666; };

  config = {
    foo = "bar";
  };
}

I get an error as expected:

error: The option `foo' has conflicting definitions for type either, in `...' and `...'.

@nbp
Copy link
Member

nbp commented Mar 20, 2016

What I meant by "changing the semantics" is best explained with the following example:

If you have an option definition that can be interpreted with both side of the "either", then you will consider the first operand of either to merge the values.

The problem appears, as soon as you have a second option definition, but which only validates with the second type. In such case, the first option definition would be interpreted with the second type instead of the first one, as it used to be without the second option definition.

I think it would be wise to ensure that either is exclusive. I understand the need for having a simple type, as a way to make a simple/refined interface. (even if I would be tempted to have the simple type definition be mapped to one of the submodule options instead of using either)

Still I think the patch which got landed lack the disjoint feature, which I do not want to have to explain nor support in the future.

@@ -246,7 +246,25 @@ rec {
either = t1: t2: mkOptionType {
name = "${t1.name} or ${t2.name}";
check = x: t1.check x || t2.check x;
merge = mergeOneOption;
merge = loc: defs:
if all t1.check (getValues defs) then t1.merge loc defs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will recommend to go for all t1.check values && all (x: ! t2.check x) values in the condition, and identically for the second condition.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, another way could be to ensure that either isn't applied to the same types, so things like either submodule1 submodule1 will cause an error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OTOH, things such as either attrs submodule will hit the same issue in this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this would be fine, because submodules is a refined attribute set. So this would still be ambiguous. And this might be even worse with either submodule attrs, as all attribute sets would be considered as valid submodules containing undeclared options.

Edit: this would be fine to raise an error in the case of either attrs submodule.

@nbp
Copy link
Member

nbp commented Mar 20, 2016

@aszlig I wonder, would it make more sense to do it as an option set attribute which is interpreted to forward simple value (no attribute set) as the value of an option which is located below, such as:

options = {
  _defaultOption = "display";
  display = mkOption { … };
  primary = mkOption { … };
  extraConfig = mkOption { … };
};

What do you think?

This way we would have a similar feature without the burden of creating submodules, when we just want a shortcut notation.

One case where this would make total sense, would be for url, where we might want to decompose them into multiple options:

let
  parseUrl = url: {
    protocol = …;
    domain = …;
    port = …;
    path = …;
  };

in

{
  options.services.foobar = {
    listen = {
      _default = "url";
      protocol = mkOption { … };
      domain = mkOption { … };
      port = mkOption { … };
      path = mkOption { … };
      url = mkOption { … };
    };
  };
  config = {
    listen = mkDefault (parseUrl config.url);
    firewall.openTcpPort.port = config.listen.port;
  };
}

@aszlig
Copy link
Member Author

aszlig commented Mar 20, 2016

Hm, I think this makes more sense and we should also ensure that either will bail out with an error if someone tries to use it in conjunction with a submodule, because right now either foo submodule doesn't typecheck the submodule at all and pretty much functions like either foo attrs or either foo function.

@nbp
Copy link
Member

nbp commented Mar 20, 2016

@aszlig

Hm, I think this makes more sense and we should also ensure that either will bail out with an error if someone tries to use it in conjunction with a submodule

I agree, if we have the possibility of failing sooner we should consider it.

@CMCDragonkai
Copy link
Member

What's the consensus now, so I can make the necessary changes to my PR? #13916

@nbp
Copy link
Member

nbp commented Apr 14, 2016

@CMCDragonkai I think the above discussion settled on the _default attribute, as a short-cut to an option definition which is declared in the same set. I am not sure if @aszlig wanted to work on it or not.

@aszlig
Copy link
Member Author

aszlig commented Apr 15, 2016

@nbp: I haven't started to work on it, yet. Will see whether I get to it next week, but I can't promise that it'll work out.

@CMCDragonkai
Copy link
Member

Any news on this?

@aszlig
Copy link
Member Author

aszlig commented Apr 24, 2017

@CMCDragonkai: Yep, we now have types.coercedTo, which should solve this. Implementing this and pushing it to #15353.

@aszlig aszlig closed this Apr 24, 2017
@CMCDragonkai
Copy link
Member

@aszlig Cool, so how do we use it?

@nh2
Copy link
Contributor

nh2 commented May 21, 2017

I'm seeing a problem where I'm doing something like

types.either (types.submodule submodule1) (types.submodule submodule2);

where the 2 submodules have different options inside (for example the first one has option a and the second one option b inside), and I get the error:

error: The option myoption.b defined in ... does not exist.

It seems to bail out immediately checking only the left side of the either, instead of allowing that either side's types are allowed.

Is it possible that this issue was not fully solved?

@nbp
Copy link
Member

nbp commented Aug 20, 2017

@nh2 This issue is fully solved, but this is a deliberate limitation of the either type.

You cannot use either with types which have the same representation. Checking for submodules it is the same as matching one attribute set. There is no (cheap) way to do it here.

Moreover, this would be difficult to document, and thus confusing for the users.

If you want to do so, create 2 options, where each has a submodule type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants