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

Lowercase builtin vars #956

Merged
merged 36 commits into from
Jul 8, 2018
Merged

Conversation

rgrinberg
Copy link
Member

SCOPE_ROOT and ROOT are still remaining as uppercased but those still need to be renamed.

$ dune runtest --root dune-upper
Entering directory 'dune-upper'
File "dune", line 3, characters 41-46:
Error: Uppercase variables are removed in dune files. Use: %{make}
Copy link
Collaborator

Choose a reason for hiding this comment

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

As discussed in #948, what do you think about the following error message:

Error: Uppercase variables are removed in dune files.
Hint: Did you mean %{make} instead?

Error: Uppercase variables are removed in dune files. Use: %{make}
[1]

jbuilder retains the old names:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since it's about the file syntax rather than the tool, I think this should say "jbuild files" (and the commands can use dune runtest).

@ghost
Copy link

ghost commented Jul 6, 2018

@rgrinberg could you make the mechanism a bit more generic? We might want to add, remove or rename more variables in the future and we'll need support to do that. What about replacing vars : Value.t list String.Map.t in Super_context.t by vars : Var.t String.Map.t where:

module Var : sig
  module Info : sig
    type t =
      | Nothing
      | Since of Syntax.t * Syntax.Version.t
      | Renamed_in of Syntax.t * Syntax.Version.t * string
      | Deleted_in of Syntax.t * Syntax.Version.t
  end

  type t =
    { value : Value.t list
    ; info : Info.t
    }
end

@rgrinberg
Copy link
Member Author

@diml yeah, I think that's a good call. Can I do this refactor in a separate PR. I'll need to do essentially the same thing for #957 and it will be better to just do this change once for all variables.

@rgrinberg
Copy link
Member Author

Actually, I can just introduce this change here and change the other stuff to use this later. It's not as straightforward to change the other stuff as they aren't really using maps yet (I hope to change that).

@ghost
Copy link

ghost commented Jul 6, 2018

Indeed. One possibility would be to enrich a bit the Var.t type to allow variables that depend on the current directory:

module Var : sig
  module Info : sig
    type t =
      | Nothing
      | Since of Syntax.t * Syntax.Version.t
      | Renamed_in of Syntax.t * Syntax.Version.t * string
      | Deleted_in of Syntax.t * Syntax.Version.t
  end

  module Kind : sig
    type t =
      | Values of Value.t list
      | Root
      | Project_root
  end

  type t =
    { kind : Kind.t
    ; info : Info.t
    }
end

@ghost
Copy link

ghost commented Jul 6, 2018

Possibly Deps and Targets could even go in Kind.t

@rgrinberg
Copy link
Member Author

@diml actually, ROOT can be handled using the old scheme we discussed before. By introducing a Dir constructor that callers will know not to interpret as a dependency. The last 2 commits I just pushed implement that.

I'm not sure we should add deps or targets in there btw. Those expansions aren't available everywhere so it might be confusing to remember not to expand if a corresponding value has been found.

@ghost
Copy link

ghost commented Jul 6, 2018

@diml actually, ROOT can be handled using the old scheme we discussed before. By introducing a Dir constructor that callers will know not to interpret as a dependency. The last 2 commits I just pushed implement that.

That seems good, yes.

I'm not sure we should add deps or targets in there btw. Those expansions aren't available everywhere so it might be confusing to remember not to expand if a corresponding value has been found.

Well it's a two stage expansion, once we get to the Deps constructor, we either expand it appropriately or fail if we are in a place where %{deps} is not allowed. That would actually lead to slightly better error messages. The advantage of doing so is that then all variables can go in the same map and it's easier to handle renames, etc... We can even push this further and include %{xxx:...} forms as well:

module Var : sig
  module Info : sig
    type t =
      | Nothing
      | Since of Syntax.t * Syntax.Version.t
      | Renamed_in of Syntax.t * Syntax.Version.t * string
      | Deleted_in of Syntax.t * Syntax.Version.t
  end

  module Kind : sig
    type t =
      | Values of Value.t list
      | Project_root
      | Deps
      | Targets
  end

  module Form : sig
     type t =
      | Exe
      | Dep
      | Bin
      | Lib
      | ...
      | Read_lines
      | ...
  end

  type 'a t =
    { kind : 'a
    ; info : Info.t
    }
end

type t =
  { ...
  ; vars  : Var.Kind.t Var.t String.Map.t
  ; forms : Var.Form.t Var.t String.Map.t
  ...
  }

This would allow to completely factorize the versioning code for variables and %{...:...} forms.

@rgrinberg
Copy link
Member Author

rgrinberg commented Jul 6, 2018

What is the point of the Nothing constructor? Is the absence of the key in the map enough?

Also, do we need a Syntax.t yet? We're only using the dune syntax for now. But we can indeed add this parameter once we have other syntaxes to consider.

EDIT: Ah ok I see, nvm

@rgrinberg
Copy link
Member Author

The last commit implements a rough version of the scheme @diml was talking about. One thing that I decided to change was not to attached values to renamed variables. The reasoning behind this is that it's yet another thing to make sure we sync correctly between the different names. The cost here is of course the extra lookup for these renamed vars. But since users are expected to transition, this cost will not be paid for long. In any case, if we switch the variable maps to be tables, the lookup will be much faster.

Also, there might be some type system game we could play to avoid those assert false for the impossible cases. Open to suggestions here.

@rgrinberg
Copy link
Member Author

I added a commit that removes a few assert false clauses at the expense of a lot GADT boilerplate. It's more of a joke than a serious improvement so I'm leaning towards reverting it :)

@rgrinberg
Copy link
Member Author

By the way, we could just include the Kind inside the info and all these problems will go away:

module Info : sig
  type 'a t =
    | No_info of 'a
    | Renamed_in of Syntax.Version.t * string
    | Since of Syntax.Version.t * 'a
    | Deleted_in of Syntax.Version.t * 'a
end

@rgrinberg
Copy link
Member Author

And this is what I did. The code looks a lot cleaner. Switching to a hash table was fairly easy as well.

@diml I'm wondering if we should make variable names interned. Seems like there will be some performance benefit and we'll have a distinct type for these kinds of strings.

@@ -33,6 +33,245 @@ module Env_node = struct
}
end

module Var : sig
Copy link

Choose a reason for hiding this comment

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

This module is big enough, let's put it in its own file.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I agree, but this module is really private to super contexts. Can you think of a good name for this? Longer term, I'm thinking of renaming String_with_vars to Template and then it will be possible to have a Var module.

By the way, this really highlights the need to have sub library like modules that we've talked about.

Copy link
Member Author

Choose a reason for hiding this comment

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

So for now I'll create make var.ml for this module.

else
String_with_vars.Var.fail var ~f:(fun var ->
sprintf "Variable %s is available in since version %s. \
Current version is %s"
Copy link

Choose a reason for hiding this comment

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

Could you factorize these error messages between syntax.ml and here? They'll be more consistent this way.

(Syntax.Version.to_string syntax_version))
| Renamed_in (in_version, new_name) -> begin
if syntax_version >= in_version then
String_with_vars.Var.fail var ~f:(fun old_name ->
Copy link

Choose a reason for hiding this comment

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

I don't find Var.fail particularly clear. I think this code would be clearer if we stick to Loc.fail (String_with_vars.Var.loc var) "..."


let rec expand t ~syntax_version ~var =
let name =
match String_with_vars.Var.destruct var with
Copy link

Choose a reason for hiding this comment

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

This is complicated version of var.name, let's add Var.name : t -> string

end

type 'a t =
| Nothing of 'a
Copy link

Choose a reason for hiding this comment

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

the name Nothing was meant to mean no information. Nothing of 'a looks a bit odd, maybe just T of 'a?

Copy link
Member Author

Choose a reason for hiding this comment

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

I changed it to No_info. T is already our convention for open types.

begin match String_with_vars.Var.destruct var with
| Single _ -> ()
| Pair (_, _) ->
Exn.code_error "expand_vars can't expand forms"
Copy link

Choose a reason for hiding this comment

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

I think this error can leak to the user. For instance if they write %{read:...} in (flags ...) or (copy_files ...).

Copy link
Member Author

Choose a reason for hiding this comment

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

You sure about that? We're making sure to call this on the right map. I.e. the one that contains only forms or only single variables.

Copy link

Choose a reason for hiding this comment

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

Can you add a test with a %{read:...} inside a copy_files just to be sure?

@@ -84,26 +324,39 @@ let installed_libs t = t.installed_libs
let find_scope_by_dir t dir = Scope.DB.find_by_dir t.scopes dir
let find_scope_by_name t name = Scope.DB.find_by_name t.scopes name

let expand_var_no_root t var = String.Map.find t.vars var
let expand_vars t ~syntax_version ~var : Var.Kind.t option =
begin match String_with_vars.Var.destruct var with
Copy link

Choose a reason for hiding this comment

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

Let's add Var.is_form : t -> bool. This should make this code slightly clearer.

match targets_written_by_user with
| Infer -> Loc.fail loc "You cannot use %s with inferred rules." var
| Alias -> Loc.fail loc "You cannot use %s in aliases." var
| Static l -> Some (Value.L.dirs l) (* XXX hack to signal no dep *)
Copy link

Choose a reason for hiding this comment

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

The targets function is used only once, I think it'll be clearer if it's inlined. It took me a while to understand why we needed a hack for instance.

@@ -3,8 +3,7 @@ We are dropping support for findlib in dune
$ dune build --root in-dune target.txt
Entering directory 'in-dune'
File "dune", line 2, characters 25-37:
Error: The findlib special variable is not supported in jbuild files, please use lib instead:
%{lib:pkg} in dune files
Error: Variable %{findlib:pkg} has been renamed to %{lib:pkg} since 1.0
Copy link

Choose a reason for hiding this comment

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

We should keep the term variable for variables without payload. These should be called forms, so:

Error: The %{findlib:...} form has been renamed to %{lib:...} since version 1.0 of the dune language.

Note: we need to make sure to not print the actual payload in such error messages. If we include the payload (pkg in thie case), then we are talking about this instance of the form. If we print ..., we are talking about the form in general, which is what we want here.

@ghost
Copy link

ghost commented Jul 7, 2018

This is a lot cleaner indeed and it's nice to see this code being cleanup! it dates from the origin of jbuilder...

@rgrinberg I'm not sure we should use a hash table here. In the future we might want to locally extend the environment and using a mutable data structure will make that difficult.

Regarding interning variables, I'm pretty sure the difference wouldn't be noticeable; I can only count about 1500 variables in the jbuild files of the whole dune-universe.

@rgrinberg
Copy link
Member Author

I'm not sure we should use a hash table here. In the future we might want to locally extend the environment and using a mutable data structure will make that difficult.

Not necessarily. I was thinking that it should be possible to extend an environment by chaining the lookup. Which of course will also require a different representation. It's just that I thought we're paying for the map keys to be in sorted order, and I don't ever really see that being useful. I'll bring back the maps as it's really simple to swap the implementation.

Regarding interning variables, I'm pretty sure the difference wouldn't be noticeable; I can only count about 1500 variables in the jbuild files of the whole dune-universe.

Yeah, I didn't really expect it to be a huge gain either. We can revisit this if variables become more common place.

@rgrinberg
Copy link
Member Author

Have another look @diml. I think I've addressed all your comments, but there were quite a few. So let me know if I missed anything.

@ghost
Copy link

ghost commented Jul 8, 2018

BTW, some of the terminology (var, form, ...) came up during development but we never really discussed it properly and as a result things are not always consistent. It'd be good to settle on a consistent naming for both user facing things (doc and error messages) and the code. I suggest the following one:

thing name in documentation and error messages name in the code
%{x} variable var
%{x:y} macro macro
%{...} (either a variable or a macro) percent form pform

@ghost
Copy link

ghost commented Jul 8, 2018

I'm having a look now

src/var.ml Outdated
let name = String_with_vars.Var.name var in
Option.bind (String.Map.find t name) ~f:(fun v ->
let what var =
sprintf "Variable %s" (String_with_vars.Var.to_string var) in
Copy link

Choose a reason for hiding this comment

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

We should check here if var is a form or variable In order to display a different error message. If it's a form we should display: The %{<var-name>:...} form, i.e. with the payload replaced by ... so that it's clear we are talking about the form in general and not just this particular instance.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah yes. Makes sense. I only made this fix for a particular error message.

@@ -51,6 +53,5 @@ This form does not exist, but displays an hint:
$ dune build --root jbuild-invalid @test-dep
Entering directory 'jbuild-invalid'
File "jbuild", line 5, characters 16-37:
Error: ${dep:generated-file} is not supported in jbuild files.
Hint: Did you mean ${path:generated-file} instead?
Error: Variable ${dep:generated-file} is only available since version 0.0 of the dune language
Copy link

Choose a reason for hiding this comment

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

This looks odd. Shouldn't this say since version 1.0 instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

Whoops, I just substituted the wrong version.

@rgrinberg
Copy link
Member Author

Alright fixed those comments. I've renamed a few things to use the new naming convention now. There's probably more at the moment, but I'm not sure if it all needs to be in this PR.

Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Remove all gatd's and the extra record type

Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Don't include the payload in these messages

Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
@ghost
Copy link

ghost commented Jul 8, 2018

Agreed. Looks good, could you just add a test with something like (copy_files %{read:x}/*)? I still have a doubt about what error message will be produced.

Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
This is a temporary hack until we have a real sexp type

Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
@rgrinberg
Copy link
Member Author

Good call on adding such a test. Not only you were right about the error message, but also we caught a bug with respect to our pretty printing of errors themselves. I've added only a temporary fix for the printing of error messages for now. The real fix will come once we have a better type for errors.

Signed-off-by: Rudi Grinberg <rudi.grinberg@gmail.com>
@ghost
Copy link

ghost commented Jul 8, 2018

nice, that's good that we caught this other issue now

@rgrinberg rgrinberg merged commit 1b53107 into ocaml:master Jul 8, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants