Mantis Bug Tracker

View Issue Details Jump to Notes ] Issue History ] Print ]
IDProjectCategoryView StatusDate SubmittedLast Update
0005759OCamlOCaml typingpublic2012-09-15 07:282013-04-26 00:34
Reporterfrisch 
Assigned Togarrigue 
PrioritynormalSeverityminorReproducibilityhave not tried
StatusresolvedResolutionfixed 
PlatformOSOS Version
Product Version 
Target Version4.01.0+devFixed in Version4.01.0+dev 
Summary0005759: Using well-disciplined type-propagation to disambiguate label and constructor names
DescriptionNow that the type-checker has a well-disciplined way to propagate type information (with a way to ensure principality), one should allow explicit type information to disambiguate constructor and label names. This seems to be a low-hanging fruit.

Not being to use in a given context two types sharing some name is an annoying issue with the language, with several workarounds but no satisfactory solution. Being able to share labels has become more important with the introduction of punning. Moreover, having nice short names for AST constructors (in the compiler itself) has become more important with -bin-annot and -ppx, since people are actually going to pattern match and build AST fragments. Later, if we add some "runtime type" features, concrete names used in the type declaration will affect the semantics of programs (e.g. by being directly visible in textual representation of values), making the "prefixing" work-around even less appealing.


TagsNo tags attached.
Attached Files? file icon record-disambiguation.diffs [^] (5,583 bytes) 2012-09-19 05:11
diff file icon patch_use_all_fields.diff [^] (5,835 bytes) 2012-10-18 15:12 [Show Content]
diff file icon sum_disambiguation.diff [^] (4,104 bytes) 2012-10-19 12:10 [Show Content]
diff file icon disambiguate.diff [^] (47,137 bytes) 2012-10-22 19:46 [Show Content]
diff file icon disambiguate-with-warnings.diff [^] (54,346 bytes) 2012-10-28 17:36 [Show Content]
diff file icon fix-warnings.diff [^] (7,383 bytes) 2012-10-29 19:49 [Show Content]
diff file icon fix-warnings2.diff [^] (7,145 bytes) 2012-10-30 14:14 [Show Content]
diff file icon fix-warnings3.diff [^] (11,824 bytes) 2012-10-31 22:24 [Show Content]
diff file icon better-errors.diff [^] (27,282 bytes) 2012-11-02 17:47 [Show Content]
diff file icon fix-unify.diff [^] (1,027 bytes) 2012-11-02 17:47 [Show Content]
diff file icon new-error.diff [^] (12,301 bytes) 2012-11-10 18:04 [Show Content]

- Relationships
parent of 0005525resolvedgarrigue Resolving record fields using all specified fields 
parent of 0005848resolvedgarrigue Assertion failure in type checker 
related to 0006000resolvedlpw25 Warning 40 ("Constructor or label name out of scope") should be an error 

-  Notes
(0008077)
garrigue (manager)
2012-09-15 09:26

As I told before, I have some 5 lines of code somewhere to make this work for records.
So this is only a political decision :-)
For pattern-matching, this is a little bit more complicated, due to the interaction
with polymorphic variant inference.
I need to find a solution for that, as this causes trouble with GADTs too.
(0008083)
frisch (developer)
2012-09-17 12:21

Our code base has many instances of declarations with identical names (we currently disambiguate syntactically by prefixing with the type name, using a local hack), so it might be a good test case for a better solution. I volunteer for testing your patch!
(0008104)
garrigue (manager)
2012-09-19 05:20

OK, I created a branch record-disambiguation, which allows field access based on types.
The patch is attached to this PR.
There is an important limitation: this patch only uses the type to determine the module in which the type was defined, and prefix the (unqualified) label with this module path. If the label is defined in several types in the same module, this may well fail.
Probably the cleanest behavior would be to obtain the label information from the type itself. However, it is not currently available in the environment (we would need a labels_by_path, similar to constrs_by_path). Additionally, one may question the wisdom of allowing to define labels such that only explicit type annotations can select them.
(0008106)
frisch (developer)
2012-09-19 10:30

> obtain the label information from the type itself

We could apply Datarepr.label_descrs. Not very clean (nor efficient), of course! It would make a lot of sense to keep the label descriptions directly in the Type_record constructor. Instead of:

  | Type_record of
      (Ident.t * mutable_flag * type_expr) list * record_representation

one would have:

  | Type_record of label_description list

(and we would keep the Ident.t in lbl_name). The code structure needs to be adapted a little bit, to create the type declaration and the labels (/constructors) at the same time, but I don't see any big problem to do so.


> allowing to define labels such that only explicit type annotations can select them

It is a common case in our code base...

I was actually thinking about issuing a warning when an "ambiguous" label or constructor is used without enough type information flowing in to disambiguate it. (Depending on the order of type declarations is a bad idea.)
(0008108)
gasche (developer)
2012-09-19 11:30
edited on: 2012-09-19 11:33

I'm not fond of Jacques' type-based implicit module qualification. I don't really see myself explaining to a surprised beginner reading some code "yeah, in this case you don't need to give the module path, because it was somehow inferred".
Besides, we already have good features to alleviate module access, namely local open, with a compact syntax, so I think you could do something equally convenient, less surprising and more explicit. It may be worth it to look at situations where current local open is not satisfying: does it work in patterns, for example? Or maybe the current state of "declaring a module alias has a heavy semantics with unsavory consequences" is a problem for conveniently short module names, and could be improved.


The nice thing with Alain's proposed semantics is that we're not adding yet another different way to do the same thing, but only using type information to enable a *new* feature that requires it. I can see myself explaining to a beginner "yeah, you should really avoid having duplicate label names available in the same environment, but if you do you can put annotations to disambiguate" (aside: Agda has type-based disambiguation of constructor names and its users are quite fond of it). I wouldn't personally use that feature much, but if it is considered I strongly support the use of a warning in all ambiguous cases.


Finally, I would like insist on the fact that I don't expect beginners to really understand how type information is propagated, and to adopt an optimistic model of "expect it to work and, if not, add type annotations". This means that what you think of as "if type information is available and is M.t, I can prefix the constructor with the path M" is likely to become, in the mind of the beginner, "I can access constructors under *any* path M implicitly".
This suggests a very different model of ambiguity in the case of Jacque's feature: if you write the constructor/field K without a path, the implementor point of view is "that can denote any constructor K currently in scope or, if type information is available and shows module M, M.K", but the user point of view is likely to be "it can denote any constructor K available in the environment under any module path". Under this view, I think it is unreasonable to combine both Alain's and Jacques' suggested behavior, because there should be an ambiguity (which ought to result in a warning) as soon as two arbitrarily deep submodules share the same constructor name.

(0008121)
garrigue (manager)
2012-09-20 03:55

Gabriel, did you read my note?
I was talking about a quick _implementation_ explicitly stating that the behavior was not as thorough as expected.
The reason is that using directly type information in a clean way requires changing the environment data structure and/or type representation, which in turn requires bootstrapping, which is heavy for a very first prototype.
But it is clearly the way to go, as label shadowing would make understanding the behavior complicated (note that the current module qualification mechanism is already broken in this respect, it would be better to fix it too).

Concerning the semantics, I have given it some thought over many years, and the catch is of course that we must keep backward compatibility.
When type information is available, we can use it and completely ignore where the label is defined.
However, when it is not, or when there are qualified labels, we need to look them up in the environment, using the current rule.
As you point out, combining the two mechanism may not be very intuitive, and this makes error reporting hard (we don't know the programmer's intent).
Fortunately, the worse think that may happen is a type error.
Concerning ambiguity detection, this is another problem.
This requires a way to keep track of all unqualified labels defined in the environment, but Ident.keys seems to provide that already, so this is actually not too difficult.
This would probably have to be yet another optional warning, since people using the old approach intentionally open modules to use a specific label definition, hiding previous ones.
(0008122)
gasche (developer)
2012-09-20 08:14

I did indeed read your note, but understood it as "I implemented a first desirable feature, whose more ambitious refinement will have to wait for a larger code change". Sorry for this misunderstanding.

> Concerning ambiguity detection, this is another problem.
> This would probably have to be yet another optional warning,
> since people using the old approach intentionally open modules
> to use a specific label definition, hiding previous ones.

Indeed, this is yet another issue. Writing M.(r.f) feels equivalent to r.M.f to the user, but the first is ambiguous and not the second. Or you could choose a different visibility behavior, where opening a module still shadows the previous definition(s) in scope, and the only ambiguity is between two labels defined in the same module. Users would then to have module-qualification to distinguish between modules, and type-annotation between in-module label reuses. Not sure if this is really intuitive, and doesn't cover Alain's use case of allowing name reuse between parsedtree and typedtree.
(0008123)
frisch (developer)
2012-09-20 10:23

Jacques: I had to add a call to Btype.repr in Env.ty_path, to have 'make world' succeed in the branch (Env.ty_path gets called with a "Tlink(Tconstr("Odoc_types.info",[],[]))"). I thought that lbl_res would always be a Tconstr node. Do you understand why the Btype.repr is now needed?
(0008133)
garrigue (manager)
2012-09-21 10:45

I have now committed a version that does "the right thing", by keeping the required information in the environment. Hopefully there is still no need to bootstrap.

The fix to the Tlink problem is the last commit.
One should never call expand_head directly on type definitions, I suppose.
It should also apply to the previous patch (but at a different location).

--- typing/typecore.ml (revision 12943)
+++ typing/typecore.ml (working copy)
@@ -2360,7 +2360,8 @@
     let record = {record with exp_type = instance env record.exp_type} in
     begin try
       let (label_path,label) = Env.lookup_label lid.txt env in
- match ty_exp.desc, (expand_head env label.lbl_res).desc with
+ let ty_res = instance Env.empty label.lbl_res in
+ match ty_exp.desc, (expand_head env ty_res).desc with
         Tconstr(p1,_,_), Tconstr(p2,_,_) when not (Path.same p1 p2) ->
           raise Exit
       | _ -> (record, label_path, label)
(0008135)
garrigue (manager)
2012-09-21 13:26

I have just committed in record-disambiguation@12945 a complete solution, which supports field access, field mutation, record construction and pattern-matching.

Note that combining two strategies under the same syntax can result in a slightly complex specification.
The worst case is record construction:
* we first check if there is a qualified field name, and use all the labels in its type to handle unqualified fields
* otherwise we use the labels from the type propagated from the context, either by a type annotation or a source expression (with construct)
* if there is no type from the context, we use finally the type from the first unqualified label
The last case could be discussed, as it means that label order has an impact on typability.
Add to that principality checks, where the warning should be disabled if there is another way to reach the same result...

See examples in tests/typing-misc/records.ml.
Hopefully error messages are clear enough that the semantics is not too confusing.

Variant constructors are not yet supported.
There is also an interaction with polymorphic variants for pattern-matching, which results in types from the context being ignored if a polymorphic variant appears in one of the patterns.
(0008144)
frisch (developer)
2012-09-22 20:19

Great! I'll have a look at it soon (not next week, though).

> * if there is no type from the context, we use finally the type from the first unqualified label

I think it would be better to look for the "last" defined type which contains all the labels (and give a warning if there are several such types). This is what we do in our version, and it only accepts more programs. It's already a good (syntactic) disambiguation scheme (for records; this doesn't apply so easily to sum types).
(0008149)
garrigue (manager)
2012-09-24 09:09

Actually the other option I was thinking of was just to revert to the original behavior when there was no type or qualifier.
I only implemented lookup of the first label because it was simpler...

I do not like much looking up types according to multiple labels. This looks robust, but one needs a completely new notion of multi-key lookup. Moreover, if we start having it for record creation and pattern-matching, why not have it for field access (with delayed resolution) too ? This is doable, but this is a real language change, as we need polymorphic records during typing.
(0008156)
frisch (developer)
2012-09-25 00:22

> but one needs a completely new notion of multi-key lookup

The can be implemented with the current environment, using a find_all on the first label to get a list of all matching record types, and then filtering (and issuing a warning if several record types remain).

The extension to field access seems much more complex, I'm not proposing such a language change (especially because disambiguation based on type information gives a simple solution). FWIW, we rarely use field accesses. Since the introduction of record punning and the warning on "non exhaustive" record patterns, we have good incentives to rely almost exclusively on record patterns.
(0008270)
frisch (developer)
2012-10-16 17:26

There is a bad interaction with private types. The following piece of code:

module rec X : sig
  type t = private {x: int}
end = struct
  type t = {x: int}

  let () = ignore {x = 2}
end


fails with:

Error: Cannot create values of the private type X.t
(0008271)
gasche (developer)
2012-10-16 17:35

tests/typing-private has no testcase with recursive module (typing-recmod/t16ok.ml has a private type but it's not used in a recursive module either). Maybe you could add this one to the test suite?
(0008272)
frisch (developer)
2012-10-16 17:45

One way to fix this issue is to restrict Mtype.enrich_typedecl:

===================================================================
--- typing/mtype.ml (revision 13019)
+++ typing/mtype.ml (working copy)
@@ -152,6 +152,7 @@
       try
         let orig_decl = Env.find_type p env in
         if orig_decl.type_arity <> decl.type_arity
+ || orig_decl.type_private <> decl.type_private
         then decl
         else {decl with type_manifest =
                 Some(Btype.newgenty(Tconstr(p, decl.type_params, ref Mnil)))}
===================================================================


I'm not totally convinced by this patch, because:

  1. I don't understand why the problem did not appear before, e.g. for mutable field assignments.

  2. I don't know if the fix would break existing code.
(0008273)
frisch (developer)
2012-10-16 17:52

Dual problem: while in trunk the following is rejected:

type t = {x: int}
type s = t = private {x: int}
let () = ignore {x = 2}

this is now accepted in the branch. Actually, one could argue that the definition of s should be rejected (it is accepted in the trunk and in the branch), because the manifest type is more public than the declaration itself.
(0008275)
garrigue (manager)
2012-10-17 13:22

The original code looks strange... but maybe this is for recursive modules.
Give me one more day before I look into it.
(0008278)
frisch (developer)
2012-10-17 19:12

Another problem: unbound labels raise an uncaught Not_found error (e.g. compiling the program "{x=0}"). What about calling Typetexp.find_label instead of Env.lookup_label in the case 2) of type_label_a_list?
(0008279)
frisch (developer)
2012-10-17 19:40

I've uploaded patch_use_all_fields.diff, which use all fields in record expressions and patterns to look for a type (for expressions, it looks for a record type with exactly the listed labels; for patterns, it looks for a record type with at least the listed labels).

Note that we don't need to change the environment to implement this. The multi-label lookup loops over all types declaring the first label, and then finds the first one with the expected remaining labels. It might be slightly inefficient if users creates hundreds of types sharing the same labels.

At LexiFi, we have been using this multi-label lookup for several years and we are quite happy about it. It is not clear to me how important it remains in presence of type-based disambiguation, but it might well be a useful addition (especially effective when using record patterns instead of the dot notation, as we do most of the time). An interesting variation could be to use the subset semantics for record pattern, only when the pattern explicitly allows for more fields ("; _"). Unfortunately, this breaks backward compatibility.
(0008280)
frisch (developer)
2012-10-17 20:22

One could even argue that the multi-label lookup is better behaved. Using the type of the first label and then type-checking the whole record with this expected type makes type-checking dependent of the order of fields in the source code (even in -principal mode), which is not very nice.
(0008283)
garrigue (manager)
2012-10-18 09:36

Fixed the uncaught Not_found.

Concerning the problem with recursive modules, the situation is more complex.
The reason the problem didn't appear before is that we didn't try to expand the type of a label before using it for lookup. So the status private/public depended on the label itself, not the type it belongs to.

Now, if we start using the expanded type, we run into problems, because its labels may have a different status.
The right thing to do is to expand just until we can see the labels, and no further.
I implemented that in the branch.

Also, your modification to enrich_typedecl is wrong, because it would result in removing equations between internal and external types, which are needed to solve (partially) the double vision problem.
(The arity case is probably for bootstrapping recursive modules, starting with parameterless types.)
(0008284)
frisch (developer)
2012-10-18 19:12

Shouldn't we always apply Ctype.repr when matching lbl_res (or the application of instance to lbl_res)? I've got an assertion failure in type_label_a_list after merging your branch on our local version (so I cannot directly reproduce it), because ty_res.desc was not a Tconstr (calling Ctype.repr solved it).
(0008285)
frisch (developer)
2012-10-18 19:42
edited on: 2012-10-18 19:45

I believe the call to Env.find_type in Typecore.expand_path should be protected. It can raise a Not_found exception which would be left uncaught, if a .cmi file is missing. Example:

bug.ml: type t = {x:int}
bug2.ml: type t = Bug.t = {x:int}
bug3.ml: open Bug2;; let f ({x} : t) = x

now compile bug.ml, bug2.ml, remove bug.cmi, and compile bug3.ml --> Not_found.

(0008286)
frisch (developer)
2012-10-18 19:48

After adding a call to Ctype.repr in type_label_a_list and protecting the call to Env.find_type in expand_path, I'm able to compile all our code base with a local merge of this branch. Hurrah!
(0008288)
garrigue (manager)
2012-10-19 05:54

OK, I protected the call to Env.find_type.
There are probably other places where missing cmi's break the compiler...

Concerning lbl_res, I think this is an instance of my comment 8133: there is an invariant that lbl_res should be a Tconstr, not a Tlink, but it can be easily broken if you do anything on lbl_res without taking an instance, in particular calling expand_head.
I think this is important to keep this invariant, otherwise lbl_res could end up pointing to another (equivalent) path, which may have an impact with recursive modules.

Finally, the discussion about how to resolve labels in the absence of type annotation.
This is problematic, because we want something simple, and backward compatible, knowing
that there are already two ways to lookup labels: direct, or using a module path qualifier.

My idea was just a small improvement over the current approach: use the first label
(or qualified label if there is one) to determine the record type, and then lookup the labels
in this record. The idea was just to have a more homogeneous behavior with the
annotated case: always lookup individual labels from the type rather than from
the environment.

Your idea of using the labels is more powerful, but it is a change of vision compared with
the current approach. It practice it may be nicer, but then why stop there, and not do like
in Caml heavy, choosing the record type according to all label uses? (Basing it on type inference
would be problematic, but a simple analysis might be sufficient.)
Also, it is important to support the qualified case in your approach too, which means
also doing lookup for qualified labels.
In the end don't we end up being too clever, while there is an easy way to disambiguate,
using a type annotation?
(0008289)
frisch (developer)
2012-10-19 10:01

> There are probably other places where missing cmi's break the compiler...

FWIW, we use this ability to compile with missing cmi's quite a lot in our code base, to protect internal data structures of libraries (and make them transparent only in selected parts of the code). We did not find any problem (like uncaught Not_found exceptions) with this approach.

Most of calls to Env.find_type are directly protected with a Not_found handler. Maybe it would be better to have it return an optional type.

> My idea was just a small improvement over the current approach: use the first label (or qualified label if there is one) to determine the record type, and then lookup the labels in this record.

This is indeed backward compatible, but it breaks the property that (at least in -principal mode) type-checking does not depend on the order of fields in the record expressions/patterns. Multi-label lookup is also backward compatible and it preserves this property. Also, it fits nicely with the new overall strategy to type check records (see my diff for type_label_a_list: just a single line change).

> Also, it is important to support the qualified case in your approach too, which means also doing lookup for qualified labels.

Yes, indeed. (Although I believe that qualified labels are much less useful in presence of type-based resolution.)

> Your idea of using the labels is more powerful, but it is a change of vision compared with the current approach.

The main change of vision is to resolve labels from types rather than from the environment. Now the question is how the type is deduced from the labels if no explicit type information is available. Switching from the rule "Look for the last type declaration in the current scope which defines the first label listed in the record" to "Look for the last type declaration in the current scope which defines all the labels listed in the record" does not seem such a big change of vision.

> It practice it may be nicer, but then why stop there, and not do like
in Caml heavy, choosing the record type according to all label uses? (Basing it on type inference would be problematic, but a simple analysis might be sufficient.)

What would this simple analysis be? I don't see how to do something simple, predictible, and robust to minor code changes.

> In the end don't we end up being too clever, while there is an easy way to disambiguate, using a type annotation?

Maybe. I won't argue too much for inclusion in OCaml, but I guess we'll keep the multi-label lookup internally, even in presence of type-based selection (not only for backward compatibility of our code, but also to preserve the property that the order of fields does not matter).
(0008290)
frisch (developer)
2012-10-19 12:10

Added a patch to add type-based disambiguation for constructors.
(0008291)
lpw25 (developer)
2012-10-22 19:45
edited on: 2012-10-23 01:08

Personally, I find the proposed semantics a bit awkward, and potentially confusing.

The problem is that it attempts to find the label from the type information (ignoring the environment) and then falls back on using the environment. This means that removing a type annotation can lead to an "Unknown label/constructor" error, which I think most programmers would find very confusing.

An alternative semantics would be to allow the environment to contain multiple labels/constructors with the same name, and try to use the type information to disambiguate them. If they can't be disambiguated by type information then the most recently declared is chosen.

The main practical difference between the two semantics is that the alternative semantics would not allow an unqualified reference to a label or constructor that is not present in the environment. This means that changing type information can only lead to a type error, not an "Unknown label" error.

I have attached a patch which implements the alternative semantics (including variant constructors, and disambiguation using the other fields in a record).

Alain, do these semantics cover the use cases in your code base? And can your code base be compiled using my patch?

(0008293)
garrigue (manager)
2012-10-23 06:35

The original patch from years ago was only about disambiguating the dot notation for record access.
In that case there is no real alternative that I can think of, and I think it makes sense.
The question about error messages is only about error messages: the message could say "there was no type information and no label definition was found in the environment".
The reason we need to look up the environment is of course backward compatibility.

Now, when we start combining type information with other kinds of disambiguation mechanisms I agree this becomes hairy, and I'm not really convinced.
In some way, Pierre's proposal of writing "t^.C" for constructor C of type t is cleaner, since it avoids trying to propagate type information in more complex situations. In particular backward propagation (needed for variant constructor) and propagation inside patterns are much less well understood than the direct propagation used for the dot notation.
(0008294)
frisch (developer)
2012-10-23 08:22

Currently, we have a local hack, very similar to Pierre's proposal, allowing to qualify labels/constructors with a (capitalized) type name instead of a module name. So one could write: {MyModule.MyType.x = ...; y = ...} where MyModule.myType refers to a type.

With Jacques' solution, such a record can be written: ({x = ...; ...} : MyModule.myType), and thanks to type propagation, this can be further simplified in many cases. For instance:

   let r : MyModule.myType = {x = ...; y = ...} in ...

or:

  MyModule.do_something {x = ...; y = ...}

or:

  {MyModule.default_env with x = ...}


I find it nice, and I'm not sure the risk of tricky error messages is worth imposing a more heavy:

   let r : MyModule.myType = {MyModule.x = ...; y = ...} in ...


Also, for constructors, this is even worth. With Jacques' solution, one could write a clean:

  let foo (x : MyModule.mySumType) =
    match x with
    | A -> ...
    | B -> ...
    | ...

whereas if we require explicit module qualification on constructors, one would need to qualify each case (and with more complex patterns, this can be quite heavy), or open the module (locally). Pierre's proposal included some preliminary rewriting of the patterns to propagate syntactically the t^. prefixes.


Error messages are important, but all errors related to label lookup could be turned into something more meaningful by adding a single type annotation with the expected type on type of the record pattern/expression. If this is explained to the users (maybe in the error messages themselves), I think Jacques' solution is ok.
(0008296)
lpw25 (developer)
2012-10-23 10:24

My concern was really about having a cleaner semantics, rather than a specific error message.

Consider the following case:

  A.ml:

    type foo = Foo of int

  B.ml:

    open A

    type bar = foo

  C.ml:

    open B

    let f (b: bar) = Foo 3

When reading this code, in order to find the definition of Foo, rather than simply examine the modules open in the current environment, I must chain backwards through type abbreviations to find it in a module that I might not even have been aware of.

I had thought that the intention of this proposal was to make it easier to use two modules that use the same record labels within the same environment, and to support modules that reuse a label name. Since, even when these are open in the current environment, they currently require the module system in order to disambiguate them.

However, if your use cases are about using type information to avoid some module qualifications when accessing labels from a module that is not open in the current environment, then my alternative semantics are obviously not appropriate. Although, in my opinion, there are already good mechanisms (locally open modules) for reducing the number of module qualifications.
(0008297)
gasche (developer)
2012-10-23 10:25

I agree with lpw25 remark that type information should be used to disambiguate what is meant if several different things are identified by the same path, but not (yet) to infer one path rather than an other. Path resolution can currently be thought of as a separate pass happening before type-checking; it is more predictable, easier to explain, and more robust in the face of type system changes.

I think that Alain's examples should be solved by locally opening MyModule. Path manipulations (open, module aliases...) have a broader scope than type-based disambiguation, so if some situation may be resolved by improving either features I think path manipulations should be preferred. (For example, I suppose you didn't suggest making a module alias for MyModule because of the current associated costs; can we improve on this front?)
(0008300)
frisch (developer)
2012-10-23 10:49

It is very common for us to have several record/sum types in the same module with common names for labels/constructors. This is not solved by path manipulation or module opening.

A typical scenario would be a cleaner version of the Parsetree/Typedtree modules in the OCaml compiler, where one could get rid of prefixes in front of labels and constructors. Similar notions are available in different sub-languages.

Even when types are defined in different modules, it is not practical to rely on module opening. Consider a simple type-checker, mapping from one kind of AST to another one, with common constructor names in both AST:

 let rec type_check env ({desc; loc = _} : UntypedAst.t) : TypedAst.t =
    match desc with
    | Var x -> {typ = lookup env x; desc = Var x}
    | ...
(0008301)
frisch (developer)
2012-10-23 10:54

> When reading this code, in order to find the definition of Foo, rather than simply examine the modules open in the current environment, I must chain backwards through type abbreviations to find it in a module that I might not even have been aware of.

This is not very different from a similar example with structural types (objects/polymorphic variants), and since we have a dump of the typedtree (-bin-annot), I expect IDE and editors to be able to give this information to you quite easily.

What could be useful would be to *allow* using module qualification on constructors/labels (to be checked against the expected type), so that you can be more explicit if you wish (and maybe add a warning to report cases where type-based resolution yields implicitly a non-local type).
(0008303)
lpw25 (developer)
2012-10-23 11:57
edited on: 2012-10-23 13:09

Alain, all the examples you give are supported by my alternative semantics, and are examples of when I think that disambiguation *should* be allowed.

For instance, as long as UntypedAst and TypedAst are already opened in the environment, then your code example should work with my patch.

I agree that allowing multiple records with common label names in a Module should be supported using type-based disambiguation. I also agree that type-based disambiguation should allow you to open two modules which have overlapping label names. My concern is about allowing labels/constructors to be used when they aren't present in the environment.

> This is not very different from a similar example with structural types (objects/polymorphic variants),

The difference is that `Foo is always a valid expression (which may have the wrong type), whereas Foo is only valid if Foo is a defined constructor in scope.

> since we have a dump of the typedtree (-bin-annot), I expect IDE and editors to be able to give this information to you quite easily.

IDEs could definitely alleviate these kinds of problems, but I don't think you should need an IDE in order to follow the scoping rules of a language.

(0008304)
frisch (developer)
2012-10-23 13:34

> as long as UntypedAst and TypedAst are already opened in the environment

Indeed. But is it worth requiring to open modules? I don't feel very strongly one way or the other. There are also drawbacks in opening modules (namespace pollution), so we need to balance the pros and cons.
(0008305)
garrigue (manager)
2012-10-23 14:56

OK, so now a new point has appeared.
Whether the path of labels is intrinsically meaningful or not.
My point of view (and probably Alain's too) is that using path for labels is broken from the beginning.
It makes sense for types, because you are not allowed to have twice the same type name in the same module. But there is no restriction for labels and constructors, so the situation can grow very confused.
In particular two labels and constructors may have the same path.
My bet is that this system of paths for labels was just a hack to avoid dealing with overloading.
This works in simple situations, but trying to find a meaning there seems far fetched to me.
Worse, I'm getting the impression that what you're talking about is not paths, but long identifiers.
For me long identifiers were just something you translate to paths looking up the environment.
But now you're suggesting that we should translate long identifiers directly into labels and constructors, using the prefix just to determine the candidate types. I don't think this is really clean...

My idea was just that, in order to keep backward compatibility, we have just two readings of labels:
either as something that you look up directly in the environment, or as names that you look up in a type, without using the environment. The two reading are unfortunate, but at least they are not mixed
(actually some aspects of my patch mixed them, and maybe this is not a good idea).

Last, for me the example about using type abbreviations is not a drawback, but an advantage.
If you see labels as names, once you know the type, qualifiers do not matter anyway.

Note that this discussion is probably more about labels, since they are destructors.
Constructors are a bit of a different story.
(0008306)
lpw25 (developer)
2012-10-23 16:07
edited on: 2012-10-23 16:08

I agree that referring to labels using paths is not ideal, and that starting from scratch a "polymorphic records" system might be better.

However, I think that you either have to treat labels/constructors as constants (a la polymorphic records/variants) which are universal, or as names that have paths and fit into some kind of namespace.
I think that if you combine the two approaches you will cause confusion.

I agree that my concerns apply much more to constructors than destructors, although I would also point out that while individual labels are only destructors, groups of labels (in record notation) are constructors.

I would still like to see type-based label/constructor disambiguation in OCaml, but as a method of dealing with the shortcomings of referring to labels/constructors using paths, rather than a partial replacement for it.

(0008308)
gasche (developer)
2012-10-23 17:50

Jacques, could you detail a bit more the difference between constructors and destructors? It seems to me that, at least as long as you stick to the "restricted" proposal of disambiguating between names having the same "long identifier", record labels and variant constructors can be handled the same way, and this is equally useful for field access, record or variant construction and pattern matching.

(Thanks for clarifying your viewpoint on the "large" proposal, based on the idea that field labels (and constructor names?) were already in a big global namespace, with long identifiers being only a particular disambiguation technique.)

I find Leo's example to be quite telling. With the "restricted" proposal, it is *relatively* easy (modulo name pollution of "open") to know among which possibilities the type-checker (or the reader) will look the definition. On the contrary, the typing environment in which compilation happens is not syntactically explicit in the source file. Happily, the necessary type information for the "large" proposal to apply will not be present if you haven't written the complete module name *somewhere* in a type annotation, but type information propagation means that you basically have to look everywhere in your source files. I expect this to be rather confusing and painful in practice (though Alain is right to point out that something akin to Ocamlspotter for label disambiguation may do the trick; we could even consider including it by default in caml-mode).
(0008319)
garrigue (manager)
2012-10-24 08:49

I don't understand Leo's example.
My approach is only about using known type information.
In the example, there is no type annotation on Foo, so we don't know anything about Foo, and this is clearly going to fail.

If we were to write (Foo 3 : bar), then we know that Foo must be a constructor of bar, which is just an alias for foo, so we can indeed find its definition in A.foo. I don't see at all why this should be confusing: there is only one such type. So for me this is the opposite: you only have to find the type definition,
which is not ambiguous, rather than having to look up constructors, which can appear in multiple types.

Or maybe you are developing Pierre's argument, that one should be able to determine the origin by just looking at the source code, ignoring all type information, in order to make debugging easier. I have no problem with that, but it seems to me that this should also exclude any implicit disambiguation: you don't want either to look up several definitions before finding the right one.


The constructor/destructor distinction is about the flow of type information, and how we check it.
When you use -principal, it is possible to know in a principal way whether a type annotation reaches its use location following the data flow. In particular this means that reducing the program would still leave the type annotation in a safe place.
This information can be used for destructors, but for constructors we just propagate the expected type, in a way that is not stable by reduction (except if you put your annotation directly on the constructor).
For patterns, there is yet another problem, as patterns containing polymorphic variants cannot get type information from the outside.
So yes, we can get some kind of type information for constructors too, but in a less safe way, and we might want to be more restrictive about that, to avoid small changes to break programs.
And this problem is completely orthogonal to whether you look at names in a flat or structured name space.
(0008320)
frisch (developer)
2012-10-24 12:20

This discussion becomes a bit philosophical!

I agree with Jacques that interpreting long identifiers as something else than a path (rather, as a set of objects amongst which we can pick, using type information) is new, but I also believe this can be reconciled with the current behavior. A longident M.x can be interpreted as a lookup of (unqualified) x in the environment obtained by opening M in an initially empty environment. I believe this gives the same behavior as of today for looking up types, values, etc. Now *if* we say that a lookup for an unqualified label name x in a given environment is only able to pick a label available at the toplevel (unprefixed) of this environment, I think we retrieve Leo's semantics.

With this point of view of interpreting a longident as a way to change the lookup environment, the question is whether we want to allow an unprefixed label name to be resolved into something which is not available in this environment (because of explicit type information), and indeed, it doesn't really make sense (as it would simply ignore an explicit prefix, i.e. you could write {M.x = 10; ...} and have x resolved to a label which is not in M at all).

So I think that Leo's semantics is the correct choice *if* we want to see longidents as selectors for the "toplevel" environment in which name resolution happens. This point of view is coherent with the current behavior, although I'm not convinced (yet) that it's the most practical choice.
(0008321)
garrigue (manager)
2012-10-24 13:14

OK, let's make things clearer.
What you guys are suggesting is to keep the current path-based approach, but to make labels and constructors overloaded, using either co-occurrence (for record construction and matching) or type information to choose one of them.
I agree this makes sense, but to me it feels like using a hammer to crush a fly.
Namely, overloading is a powerful concept, and one ocaml has carefully avoided until now.
Moreover, I find it difficult to make it agree nicely with the current behavior, on which we still have to fall-back on if we cannot resolve ambiguity. Namely, we have overloading, but still make an "arbitrary" choice if we cannot resolve it. (Usually it is admitted that any reasonable notion of overloading has to be symmetrical, not privileging one definition over another, but I agree there are some exceptions.)

The reason I insist so much to see the label name space as flat, is that it avoids overloading. Using a label or constructor is semantically independent of its type, we need the type for typing purposes only.

So let's first get a dynamic semantics. Mine is clear: just forget the qualifiers and see labels and constructors as names, using an eventual prefix only to look up the type when it is not known.
(0008322)
frisch (developer)
2012-10-24 14:23

Jacques, I don't suggest to keep the path-based approach, I was just explaining that Leo's semantics could be explained by a different interpretation of path resolution, compatible with today's behavior (but different from it).

I still tend to prefer your solution which does not require to open a module only to make it possible to use the labels/constructors of types defined in it without an explicit prefix.

> using an eventual prefix only to look up the type when it is not known.

In your patch, the prefix is used instead of the type if it is provided, i.e. prefixes are not a fallback for missing type information. (And of course it would be weird to ignore the prefix if is it provided.)

I would suggest a slight variation of it: if we have both the type and a prefix, use the type to disambiguate the reference and then check that the result is compatible with the prefix. This allows the user to specify the prefix for (checked) documentation purposes.
(0008323)
frisch (developer)
2012-10-24 14:54
edited on: 2012-10-24 14:56

Hmm, I'm confused now. I thought your proposal would allow:

module A = struct
  type t = {x: int}
end

let r : A.t = {x = 2}

but actually it doesn't, because the label is looked up in the environment even though the type is fully known (to report non-principality breaches).

(0008324)
gasche (developer)
2012-10-24 15:14

Jacques, a dynamic semantics for the "restricted" proposal where type information is used only to disambiguate between labels of different types sharing the same long identifiers, rather than as an alternative to the long-identifier-prefixing method (in your world where labels and constructors are just names, as for polymorphic variant constructors and object methods):

We can understand the definition of a new record type as the introduction of a type along with constructors and destructors, which are constants.

  type 'a t = {x:int; y:'a}

would have the effect of

  type 'a t
  val mk_t: ~x:int -> ~y:'a -> 'a t
  val x : 'a t -> int
  val y : 'a t -> 'a

And the obvious reduction rules added to the reduction theory of the language (x (mk_t ~x:a ~y:b) => x, etc.).

This vision has scoped labels and constructors (which are lexically a subset of the language identifiers). Indeed, the proposed "restricted" disambiguation scheme is exactly adding static overloading on this specific class of identifiers. I'm not sure the argument that "we've avoided overloading so far" has much merit when compared to your "large" disambiguation scheme that has, in effect, all the "bad" semantics properties of generalized overloading of label and constructor names -- as it would accept all examples accepted by the "restricted" proposal, plus some more.

I consider that "are we ready to add some static overloading to OCaml?" is a pre-requisite question to answer before answering any of those proposals. My guess would be "yes" (but admittedly static overloading is not always satisfactory in presence of type abstraction).
(0008325)
garrigue (manager)
2012-10-24 15:36

Gabriel, sorry, I don't understand at all the sentence:
``I'm not sure the argument that "we've avoided overloading so far" has much merit when compared to your "large" disambiguation scheme that has, in effect, all the "bad" semantics properties of generalized overloading of label and constructor names -- as it would accept all examples accepted by the "restricted" proposal, plus some more. ''

My point is that I don't do any overloading, since the interpretation of labels and constructors is structural.
I don't know what "bad semantics properties" you are talking about.

For Alain, it is easy to accept "let r : A.t = {x = 2}", since the annotation is exactly on the record.
Is it not the case with the current patch?
Also, for qualified labels M.l when the type is known, the situation is a bit complicated.
For me the qualifier expressed the intent to retrieve the label from the environment, so I would rather keep the current lookup strategy, even if this can mean failing in some situations.
Are there real world situations where this would be a problem?
(0008326)
gasche (developer)
2012-10-24 15:48
edited on: 2012-10-24 15:50

> My point is that I don't do any overloading,
> since the interpretation of labels and constructors is structural.
> I don't know what "bad semantics properties" you are talking about.

Why have we "carefully avoided" static overloading so far? It (1) mixes badly with type abstraction, and (2) makes the code harder to read by giving more work to understand what is being meant. Pain point (1) is accepted as a fatality by everyone in this discussion, as we only consider disambiguation "in presence of type information". Pain point (2) is also present in your semantic model of flat label namespace, and even slightly worse as the ambiguity set for a given label are always larger -- than with the other semantics: overloading of scoped labels and constructors. This is what I meant; apologies for being unclear.

(0008327)
frisch (developer)
2012-10-24 16:00

> Is it not the case with the current patch?

I don't think so (except this is because of my own commits to your branch...), because there is still a lookup of the label in the environment, even if the type information is available. This can indeed be fixed easily (commit 13045).
(0008328)
frisch (developer)
2012-10-24 16:12

> Are there real world situations where this would be a problem?

Indeed, it's probably not so important, because one can just tell the users to add a type annotation directly on the record instead of a prefix. This is also explicit.

Would you go as far as discouraging the use of prefixes for non-local labels, even in non-ambiguous cases?
(0008329)
garrigue (manager)
2012-10-24 16:13

OK, Gabriel, now I understand better your point.
I basically agree for (1): the goal here is not to extend the type system, so we're not going to introduce new type abstraction, and as a result we become dependent on type annotations (but can still be principal if done properly).

Concerning (2), this is really a difference of interpretation.
Actually the name of the branch is wrong. This is not about disambiguation, but about recovering proper type information for a label/constructor. That is, for me there is no ambiguity set to start with (hence my reticences about Alain's label set approach). If the type is known, you just have to look at its definition, full stop. This is related to (1): here we do not consider the case where there is no type information. Of course this leaves the problem of what to do when there is no type information. Falling back on the current behavior is not so nice, but I see no alternative.

Last, you forget problem (0), which is for me the main one: in presence of overloading, the dynamic semantics depends on the typing. But here this is not the case.
(0008330)
garrigue (manager)
2012-10-24 16:46

> > Is it not the case with the current patch?

> I don't think so (except this is because of my own commits to your branch...), because there is still a lookup of the label in the environment, even if the type information is available. This can indeed be fixed easily (commit 13045).

This was a bug. The specification is clear: the expected_type should be used, but we just need a warning if we are not principal, and cannot use the information from a label.

I just did a bit of clean up (not supposed to use polymorphic variants there)
(0008331)
garrigue (manager)
2012-10-24 16:59

> Would you go as far as discouraging the use of prefixes for non-local labels, even in non-ambiguous cases?

No, not at all. There is nothing inherently wrong with the current approach, and it has the advantage of being completely explicit.

But I would discourage combining the two approaches in the same piece of code. Gabriel has a point: if you are using some type annotations to allow removing prefixes, then using prefixes for other ones is bound to be confusing.
(0008332)
lpw25 (developer)
2012-10-24 17:22
edited on: 2012-10-24 19:05

As I see it, the issue is that up until now all objects in OCaml could be divided into two sorts:

 - Named objects (e.g. values, types, labels) which can be refered to using a longidentifier, have some kind of definition, and are generally considered to be part of a module in some way. These named objects are part of the environment (they have scope), and are subject to the namespace management features of OCaml (e.g. opening modules).

- Constants (e.g. literals, polymorphic variant tags, method names) which are not scoped and can be refered to anywhere in the program.

This division is easy to explain and easy for people to understand.

The problem with the "large" proposal is that it makes labels and constructors members of both sorts simultaneously. I think that people will find this confusing.

The fact that you can refer to a specifc instance of a label by using a long identifier encourages people to think about them as named objects that have a definition and a scope. Making this not the case in some circumstances (i.e. when type information is available) will seem counter-intuitive.

If records were polymorphic then you would never need to use a longidentifer to choose a specific instance of a label and so labels would just be constants.
If the appropriate label had always been selected via the name of the type (a la Pierre's proposal) then people would be used to thinking of labels and constructors as merely part of a type, rather than as named objects in there own right.
However, since labels *are* refered to directly as named objects, people will expect them to behave as such (e.g. obey the same scoping rules as other named obejcts).

I also think that GADTs can be seen as a step towards treating constructors as named objects in there own right.

(0008333)
garrigue (manager)
2012-10-24 22:53
edited on: 2012-10-24 23:12

Leo, I would prefer naming the two categories as scoped names and non-scoped names, since they are both just names really.

My proposed understanding is a bit different.
Labels and constructors would be seen a themselves non-scoped, but resolved by types, yet the environment contains hints to look them up (for compatibility). So indeed they end up using a scope in some cases, even if they are not scoped by themselves.
I agree this is not so nice, and in fact it may be as simple to say they belong to both categories.

But the so-called "restricted" proposal introduces a 3rd category, of overloaded scoped names.
Personally I think this is worse than combining two existing categories, because you then have to learn a new concept of "ambiguity set", and how one can select in them (is it only by types, or are there other resolution mechanisms?).
I also think that having a clean dynamic semantics that does not depend on types is important.
Using the "restricted" proposal, you either depend on types, or you have to use a "non-scoped" semantics for labels, which is somehow in contradiction with making the scope essential in typing.


By the way, I don't think that GADTs introduce anything new here. They are just providing types for constructor names, just as normal type definitions.

On the other hand something new would happen if we were to introduce extensible types, since this would weaken the relation between a constructor and the type it belongs to. If we follow the approach for exceptions, constructors of those types have to be scoped.
I think that all the proposals are broken in that situation :-( Mine because you still have to see some constructors as scoped, and the "restricted" one because you really need to uses paths, not just long identifiers.
Some re-thinking to do...

(0008334)
lpw25 (developer)
2012-10-24 23:43

>But the so-called "restricted" proposal introduces a 3rd category, of overloaded scoped names.
Personally I think this is worse than combining two existing categories, because you then have to learn a new concept of "ambiguity set", and how one can select in them (is it only by types, or are there other resolution mechanisms?).

I agree that the need for ambiguity is not ideal.

I think that to a certain extent this ambiguity already exists in OCaml. For example:

  module M = struct type foo = Foo type foobar = Foo end

The module M contains two constructors which, if the other didn't exist, could be considered to be M.Foo.
Unlike values or types, both these consturctors can still be accessed through M. The foobar one can be accessed as M.Foo, and the foo one can be accessed via a module alias:

  module type S = struct type foobar = Foo type foo = Foo end
  module N = M : S
  N.Foo


Much more importantly, the issue of ambiguity sets can only affect programs which declare multiple labels/constructors of the same name. Whereas treating labels/constructors as (occasionally) non-scoped names can affect any program.


> or you have to use a "non-scoped" semantics for labels, which is somehow in contradiction with making the scope essential in typing.

I don't see this as too much of an issue, I think that scoping is often a feature of the type system and not part of the dynamic semantics. Many features of type systems are designed to disallow programs which have a valid dynamic semantics.


> By the way, I don't think that GADTs introduce anything new here. They are just providing types for constructor names, just as normal type definitions.

I only meant that they make constructors feel a bit more like normal values, and that they further separate a constructor from its type (since it no longer fully describes the constructor's return type).


> On the other hand something new would happen if we were to introduce extensible types, since this would weaken the relation between a constructor and the type it belongs to. If we follow the approach for exceptions, constructors of those types have to be scoped...

Yes, both exceptions and extensible types must be scoped. They must also be unique within a signature, because of how they are implemented.

Any kind of active patterns or pattern synonyms would also have to be scoped.
(0008335)
garrigue (manager)
2012-10-25 01:40

>> or you have to use a "non-scoped" semantics for labels, which is somehow in contradiction with making the scope essential in typing.
>
>I don't see this as too much of an issue, I think that scoping is often a feature of the type system and not part of the dynamic semantics. Many features of type systems are designed to disallow programs which have a valid dynamic semantics.

Leo, nice to see that at least we agree on the dynamic semantics.
For me this is very important, as I'm not yet ready to drop the benefits of not depending on types (at least for something as solving a "minor" gripe)
You're right that typing and dynamic semantics are independent problems.
My point was simply that to avoid overloading, at some point we must consider labels independently of scope.

> Much more importantly, the issue of ambiguity sets can only affect programs which declare multiple labels/constructors of the same name. Whereas treating labels/constructors as (occasionally) non-scoped names can affect any program.

Honestly the difference seems very small to me.
As soon as you open two modules defining the same label, you get exactly the same problem.
And without scope it doesn't affect any program, only programs which define at least two labels with the same name :-)

Also, requiring opening a module to access its labels/constructors has often been seen as a weakness of the current OCaml approach: suddenly you get much more in scope that you wanted, since values are imported too. I think Alain shares the same concern, and type-based selection without opening a module provides a solution to this problem.

>> On the other hand something new would happen if we were to introduce extensible types, since this would weaken the relation between a constructor and the type it belongs to. If we follow the approach for exceptions, constructors of those types have to be scoped...
>
>Yes, both exceptions and extensible types must be scoped. They must also be unique within a signature, because of how they are implemented.

I'm starting to think that we should put this discussion on hold, and first find an adequate proposal for extensible types. I cannot come up easily with a dynamic semantics that would allow disambiguation without overloading for them. Exceptions are not so much a problem as we can see them as an exception to the rule, but having a completely different semantics for extensible types and normal ones is somehow counter-intuitive.
Records do not have this problem, as they cannot be extended.
(0008336)
frisch (developer)
2012-10-25 06:59

> Also, requiring opening a module to access its labels/constructors has often been seen as a weakness of the current OCaml approach

I confirm! This makes it tedious to use "small" datatypes introduced specially for the parameters or results of a function. For instance, one might like to write in some API:

  type create_mode = Immediate | Delayed
  type created = {thing: thing; warnings: string list}
  type create_result = Ok of created | Error1 of string | Error2 of ...
  val create: create_mode -> blabla -> create_result

and simply call: match API.create Immediate blabla with Ok {thing; _} -> ... | ....

With local open, one can do: API.(match create Immediate blabla with ...) but (1) this is not very nice, syntactically; (2) we still suffer from name pollution (risk of breaking code of the pattern matching if the module API is later extended with more definitions). People tend to use polymorphic variants instead, but (1) this makes error messages more complex; (2) they don't support all features of standard sum types (GADTs, non regular recursion). Moreover, objects instead of records do not really work for returning several named results (much heavier at runtime, no direct pattern matching, no punning).


----

What's the concrete problem of allowing type information to select labels for non prefixed references even if the corresponding type is not in the local scope? I don't really believe it makes the code harder to read, because no semantic ambiguity arises. It might make typing errors somehow more difficult to track, or the code a little bit weaker w.r.t. local modifications, but this is inherent to the type-based resolution. And the .cmt files contain enough information to allow adding a "jump-to-definition" feature for labels/constructors to IDE/editors (and the current "show-type" is already quite useful).

----

Similarly, what's the concrete problem of allowing a (non-empty) prefix to mandate that labels (even if selected through their type) need to be defined in the specified module? Indeed, it is a slightly new interpretation of longidents (and it makes the non-prefixed labels a special case), but it seems easy enough to understand. I believe this gives a good solution for extensible sum types (as in Leo's proposal, 0005584) as well; it makes it possible to have two constructors of the same name in the same module but in two different extensible sum types, relying on both the type information *and* a prefix to distinguish between them. (Two constructors of the same name in the same module and in the same extensible type cannot be distinguished; I think we don't care about this case, right?)
(0008337)
lpw25 (developer)
2012-10-25 12:32
edited on: 2012-10-25 12:54

>> Much more importantly, the issue of ambiguity sets can only affect programs which declare multiple labels/constructors of the same name. Whereas treating labels/constructors as (occasionally) non-scoped names can affect any program.

>Honestly the difference seems very small to me.
>As soon as you open two modules defining the same label, you get exactly the same problem.
>And without scope it doesn't affect any program, only programs which define at least two labels with the same name :-)

My point is that, while I agree that "ambiguity sets" may cause some confusion (although I think they will cause much less confusion than occasionally treating labels as non-scoped), this confusion can only arise in programs that contain multiple labels with the same name.
The confusion caused by occasionally treating labels as non-scoped, on the other hand, can occur in any program that uses labels in the presence of type information.

In other words, all the additional programs that would be accepted under the "restricted" extension contain multiple labels with the same name. While many of the additional programs accepted by the "large" extension do not.


> Also, requiring opening a module to access its labels/constructors has often been seen as a weakness of the current OCaml approach

I agree with Gabriel that such problems should be solved using methods that apply to all scoped names, not just labels/constructors (e.g. making module aliases cheaper).

I also think that this should be considered a separate proposal from this one. So that there are two proposals:
1. Use type information to allow multiple labels/constructors with the same name to be used in the same environment.
2. Allow type information to replace explicit module qualification for labels/constructors.


> I'm starting to think that we should put this discussion on hold, and first find an adequate proposal for extensible types. I cannot come up easily with a dynamic semantics that would allow disambiguation without overloading for them.

It does seem that allowing extensible types and any type disambiguation would require a dynamic semantics containing overloading. Although, as Gabriel pointed out, the reasons for avoiding overloading do not really apply to this case.


Originally, I had thought that exceptions/extensions should be handled differently from normal constructors. So exceptions/extensions would shadow previous exceptions/extensions with the same name, which is how my patch handles them.
However, having thought about it, a more consistent solution would be to support modules and signatures that have multiple exceptions/extensions with the same name. I don't think this would be difficult to do, and it would mean that exceptions and extensions behaved much more like other constructors.
In this case, the "restricted" semantics works just as well for exceptions/extensions as other constructors. Obviously, for exceptions and extensions whose name and type were the same it would not be possible to disambiguate them based on type, but this is just the same as when no type information is available.

As a side note, I hope to bring my patch for extensible types (0005584) up to date next week, so that might be a good opportunity to consider how extensible types can be added to OCaml.


> What's the concrete problem of allowing type information to select labels for non prefixed references even if the corresponding type is not in the local scope?

There is no real problem with treating labels as non-scoped names on its own. It is the confusion that is caused by treating them as non-scoped sometimes and scoped at other times that is the problem. Since labels sometimes have scope (which is required for backwards compatibility) people will expect them to always be scoped.

As Jacques said, his idea is to have two "readings" of labels, whereas the "restricted" semantics maintains a single "reading" for labels.

(0008338)
gasche (developer)
2012-10-25 13:19

(Trying to participate less and make short answers to help avoid bloat. I agree with most of what has been said above, in particular by Leo.)

> But the so-called "restricted" proposal introduces a 3rd category

In my mind before starting the discussion there was only one category: the scoped view of labels. I would argue that your proposal introduces the non-scoped view of labels as a *new* category. So I see this in terms of choice between two different new categories, unscoped labels or scoped-but-ambiguous labels. Granted, the {M.x = ...; y = ...} feature can be understood as already bringing the "unscoped labels" category in the language, but I always understood it as simple syntactic sugar to avoid repeated long prefixes -- and have a slight dislike for this feature that competes with local open.

> I also think that having a clean dynamic semantics that does not depend
> on types is important. Using the "restricted" proposal, you either depend
> on types, or you have to use a "non-scoped" semantics for labels,
> which is somehow in contradiction with making the scope essential in typing.

I'm fine with assuming that we make a type-directed elaboration step before having a type-erasable semantics (I hope that other features, such as Alain/Grégoire/Pierre's runtime type representations, will be added, that will require such an elaboration anyway); the elaboration of concern here is simple, easy to explain and well-behaved. I'm also fine with dodging the question by choosing a dynamic semantics with non-scoped labels (which amounts to integrating the overloading in the runtime rather than in the compilation process). I don't see either as a problem for the restricted proposal.


During this discussion I've come to understand Jacques and Alain's point of view better. I still have a preference for the ambiguous-scoped-labels proposal, as it is less ambiguous and therefore makes code easier to understand, but I know understand well how I would explain the other non-scoped-label proposal to beginners (... just saying that labels are non-scoped names) and would not be unhappy if it was integrated. Thanks to all for taking the time to explain. I'm considering writing a summary document (blog post) for people that haven't followed this lengthy discussion.

I'm mostly ignorant about extensible types so I'll let you discuss this. (Besides "nobody wants to implement it", why don't we consider having extensible product types as well? Since GADTs we know have the dual of polymorphic fields, and I enjoy the symmetry.)
(0008339)
frisch (developer)
2012-10-25 13:43

I think the following would be quite practical:

 - Labels with a non-empty prefix are looked up in the environment, using type information (if available), to choose between all results (as in Leo's proposal). If no type information is available, the most recent definition is picked.

 - For labels with no prefix: first try to use the type information, otherwise look for the most recent definition in the environment (as in Jacques' proposal).

It's quite simple to explain that "all information available locally is taken into account": the explicit prefix (if provided) and the type information (if available) (and, I'd add, the set of all label names...), with a default on the most recent matching definition if needed. In addition to that, one needs to know that a non-local label needs some hint to be found (an explicit prefix, type information, or module opening). I don't think this creates any confusion.
(0008341)
garrigue (manager)
2012-10-25 14:53

> It does seem that allowing extensible types and any type disambiguation would require a dynamic semantics containing overloading. Although, as Gabriel pointed out, the reasons for avoiding overloading do not really apply to this case.

The reasons to avoid it at the typing level are indeed weaker. But this doesn't change anything about the reasons to avoid it in the dynamic semantics. Not needing types for evaluation is much easier.


> (Besides "nobody wants to implement it", why don't we consider having extensible product types as well? Since GADTs we know have the dual of polymorphic fields, and I enjoy the symmetry.)

It's just that I don't see what semantics you would give to it...
The point with extensible types is that you can extend the type even after having created a value.
How do you do that for records?
There are proposals to that with objects, but not with records I believe.

> (I hope that other features, such as Alain/Grégoire/Pierre's runtime type representations, will be added, that will require such an elaboration anyway)

I would still prefer such an elaboration to be restricted to where it is strictly needed.
You could probably use Grégoire and Pierre's mechanism to generate overloaded constructors if you want :-)
They actually do that in Haskell.

Cheers, Jacques
(0008342)
garrigue (manager)
2012-10-25 16:43

OK, my last attempt at finding the common points.

Since you didn't really buy my argument that the dynamic semantics needs non-scoped names, let's go back to the typing part.

In my approach, and I believe in the "restricted" version too, the "disambiguation" is only about choosing between two lookup methods: either look up directly the name in the environment, or in presence of type information look it up inside its type. At least this is what the simplest algorithm is going to do, and in type systems lookup is usually algorithmic.

Note that we can still build a perfectly declarative type system to choose between the two: since we have restricted type-based selection to known types, it is only going to happen when there is only one possible choice, i.e. looking up the name in the environment would either give the same result, or an incompatible type which would result in failure.

The interesting part here is that lookup through the type is not overloading even at the type level: we are not choosing among a list of candidates by looking at their types, but just getting the information directly from the type.
It is also weaker: we do not choose a constructor according to the type of its argument for instance.

If we agree on this part, then the "restricted" version is really only about "restricting" the lookup in the type to only succeed if the label/constructor was exported to the current environment in a way accessible with the qualified name. So our disagreement is just that you see this restriction as "principled" (more coherent with the name lookup behavior), while I see it as "arbitrary" (why do more work just to exclude a useful behavior, for which we have a reasonable explanation). I understand your point, but I don't see the discussion going any further...

Note that there are also other proposals on the table which consider stronger forms of lookup, like using sets of labels for record creation, or to allow the type to be decided by one pre-selected label. They are orthogonal (could work with the "restricted" version too), but add to the specification.

There are also some connected details, like what to do with qualified names. In a non-scoped approach I think it would be more intuitive to disable type lookup for them, but this is only one choice.
(0008343)
lpw25 (developer)
2012-10-25 18:07

>In my approach, and I believe in the "restricted" version too, the "disambiguation" is only about choosing between two lookup methods: either look up directly the name in the environment, or in presence of type information look it up inside its type.

> The interesting part here is that lookup through the type is not overloading even at the type level: we are not choosing among a list of candidates by looking at their types, but just getting the information directly from the type.

The "restricted" approach is probably better categorised as a *single* lookup method which returns all possibile labels/constructors for the given longidentifier, followed by a type-based disambiguation which *is* basically a form of overloading.

This is certainly how my patch implements it, the lookup returns a list of candidates and the expected type is used to eliminate some of those candidates. The most recently defined of the remaining candidates is then chosen. (It also uses the set of labels when disambiguating a record creation).

This view makes it seem a bit less arbitrary.

I also think that the restricted approach may be more consisent if extensible types are included in OCaml. Although this may need some more thought.


> There are also some connected details, like what to do with qualified names. In a non-scoped approach I think it would be more intuitive to disable type lookup for them, but this is only one choice.

Regardless of which approach is taken I do think that the follwoing code should work:

  module M = struct type foo = Foo type foobar = Foo end

  let f : M.foo = M.Foo

So I think that Alain's proposal to check qualified names against type information is probably an improvement on the origianal "large" approach.


> I understand your point, but I don't see the discussion going any further...

I agree that the important points of the discussion are probably well established now. I would welcome the opinions of some of the other developers, to see if there is more of a consensus outside of us four.

My preference is still for the "restricted" semantics. I don't think that the "large" semantics are particularly bad, just not as consistant with the current semantics.
(0008344)
frisch (developer)
2012-10-25 20:13

Here is a summary of the discussion, for new readers. There are really two independent points where Jacques' and Leo's semantics disagree.

1. Do we allow combining type-based resolution and qualified names?

Jacques:
> what to do with qualified names. In a non-scoped approach I think it would be more intuitive to disable type lookup for them,
> but this is only one choice.

Leo:
> I do think that the follwoing code should work:
> module M = struct type foo = Foo type foobar = Foo end
> let f : M.foo = M.Foo


2. Do we allow type-based resolution of an unqualified label/constructor to result in a non-local item?

Jacques believes that disallowing this case would be an arbitrary restriction to the type-based resolution (requiring additional work), whereas Leo's position is that it makes the type-checking more principled.


(My personal "vote": Leo's answer to 1 and Jacques' answer to 2.)
(0008345)
gasche (developer)
2012-10-26 11:16
edited on: 2012-10-26 11:19

A remark/question: if we have a vision of labels as unscoped even in the static semantics, does this mean that OCaml type inference is not principal? If labels are unscoped names, the principal type for (fun x -> x.l) would be of the form `{l:'a;...} -> 'a` -- and a principal (because unique) choice can only be made in presence of type information.
On the contrary, a scoped label model has principal type inference with non-overloaded (non-ambiguous) labels. The moment we start introducing ambiguous label (that is name resolution to a candidate set rather than a unique candidate) we lose principality again, unless we reject the cases where the syntactic "label shadowing" rule applies to pick the last definition. But this would break existing code -- I would be in favor of at least having a warning in this situation, when the (scoped) label resolves to more than one candidate.


Another question: how would/should the large/unscoped proposal handle the following case?

  module M = struct type t = { x : int } end
  type t = { x : bool }

  let x (t : M.t) = t.x

It is an edge case: Jacques has proposed to disable type-based lookup if the label is qualified, but in this case the non-qualified label is also a long identifier that exists in the environment.

(Partially answering to myself: according to the distinction above, Alain is in favor of silently making the type-correct choice.)


> Since you didn't really buy my argument that the dynamic semantics needs non-scoped names, let's go back to the typing part.

I'm fine with a dynamic semantics using non-scoped names if you like it better than a semantics with scoped names that requires a tiny amount of elaboration. My pain point with non-scoped names is that they make the program potentially harder to understand (said otherwise, they augment the risk of being wrong about what the program actually mean if I make a mistake writing it), but this issue won't happen dynamically as you only run programs that have been accepted by the type-checker. I was merely pointing out that non-scoped names in the runtime semantics is not necessarily better than the elaboration from an aesthetic point of view (you could consider that this semantics, at the time of record access evaluation, forces you to dispatch on the field name to get the corresponding index in the record, which is a way of paying a runtime cost for something you could have decided statically by exactly this small overloading resolution step; loosing one of the good reasons for insisting on having a type-erasing semantics in the first place).

(0008346)
garrigue (manager)
2012-10-26 13:17

> A remark/question: if we have a vision of labels as unscoped even in the static semantics, does this mean that OCaml type inference is not principal? If labels are unscoped names, the principal type for (fun x -> x.l) would be of the form `{l:'a;...} -> 'a` -- and a principal (because unique) choice can only be made in presence of type information.

The whole point using "known" type information is that we have a principal way to determine what is known "for sure" from what is just an artefact of the inference algorithm. So yes, we are still principal, but only when using the -principal flags (without it we accept more programs).

> Another question: how would/should the large/unscoped proposal handle the following case?

  module M = struct type t = { x : int } end
  type t = { x : bool }

  let x (t : M.t) = t.x

> It is an edge case: Jacques has proposed to disable type-based lookup if the label is qualified, but in this case the non-qualified label is also a long identifier that exists in the environment.

I was only proposing to disable it when the label is qualified in the source code.
This is a typical case where we want to use type information, and we would fail otherwise.

>> Since you didn't really buy my argument that the dynamic semantics needs non-scoped names, let's go back to the typing part.

> I'm fine with a dynamic semantics using non-scoped names if you like it better than a semantics with scoped names that requires a tiny amount of elaboration. My pain point with non-scoped names is that they make the program potentially harder to understand (said otherwise, they augment the risk of being wrong about what the program actually mean if I make a mistake writing it), but this issue won't happen dynamically as you only run programs that have been accepted by the type-checker. I was merely pointing out that non-scoped names in the runtime semantics is not necessarily better than the elaboration from an aesthetic point of view (you could consider that this semantics, at the time of record access evaluation, forces you to dispatch on the field name to get the corresponding index in the record, which is a way of paying a runtime cost for something you could have decided statically by exactly this small overloading resolution step; loosing one of the good reasons for insisting on having a type-erasing semantics in the first place).

Actually it was not really about your not accepting it as a good semantics (Leo was agreeing) from the beginning, but rather as not seeing it as a reason to see non-scoped names as a good choice for typing too :-)
Note of course that we are only talking of formal semantics here.
It is clear that the real implementation must be efficient, and the type system ensures that we can always elaborate to efficient code (which is what compilation is about).
My first problem with elaboration based semantics (at a theoretical level) is that you are working on something that is no longer the source program, so it gets more difficult to be sure that what you're doing is correct. This is a very frequent criticism of module systems for instance. The elaboration you propose is simple, so the risk of confusion is small, but already you are losing a bit of the intuition of what a record really "is".
But the real problem is that, since you are depending on types, if you are not careful enough with your type system and allow ambiguity to creep in (which might eventually happen if you don't use -principal, but I don't have a concrete example of that), then the semantics of a program might depend on the result of type inference. I don't like to depend on properties of the type system for the coherence of my semantics, since it is easy to introduce ambiguity when you mix lots of features. This is the reason why the semantics of labeled arguments, which was elaboration-based in O'Labl, is independent of typing in OCaml.
(0008347)
gasche (developer)
2012-10-26 14:23

I've come to peace with your extended proposal. I'm fine with either choices.

(My reasoning: your remark about the disambiguation step not being expressible as a source program without annotations is reasonable, and suggests that it would still be interesting to add the `type^.field` feature to explain the disambiguation step. But what's the difference between `x.type^.field` and `(x:type).field`? Once you're there, it seems overly restrictive to not allow the annotation to be placed somewhere close, for example in the prototype/annotation of the value being defined.)

I would like to capture the cases I consider most "dangerous" for readability and make a warning out of them, but I can't seem to really decide what they are. What I would like to capture is the fact that the type is explicitly annotated somewhere "close enough" in the program code, and does not come implicitly from the use of a type-constraining non-local function.

I still think that the "restricted" proposal will be more robust (and, as Alain, that we should equally support the case where several types share field names in the same module). In particular, I would like to raise a warning each time the absence of type information led to the choice of a default candidate (with the current rule) despite the presence of several possible candidates. This is workable with the restricted proposal (warning only fires if you have repeated labels in the same module), unworkable in practice with the large one (fires as soon as you repeat labels anywhere in your typing environment).
(0008348)
garrigue (manager)
2012-10-27 13:06

OK, now let's make everybody happy.
We can easily get the behavior you want by warning about two things:
* defaulting to a label when there are other possible choices inside the same scope,
* using type to choose a label which is not in scope whereas the label was unqualified.
This could be a single warning, or two separate ones.
I have nothing against warnings as long as I can disable them :-)

This means that we need to support the combination of type and qualified labels.
I would suggest that we retain a path based semantics for the module part:
if the label is M1.M2.x, then we are only allowed to choose among labels inside the unique
module M1.M2.
I.e., if a module is shadowed, then its labels can no longer be accessed with qualified paths
(but still potentially through unqualified labels, for the not faint of heart).
This is to avoid introducing a kind of "hierarchical merging of structures", which appears nowhere else.

Is everybody happy with that?
(0008349)
frisch (developer)
2012-10-27 15:27

> Is everybody happy with that?

I am!

(I don't think anyone suggested to support shadowed module names.)
(0008352)
lpw25 (developer)
2012-10-28 17:43
edited on: 2012-10-28 17:45

> Is everybody happy with that?

That seems a reasonable compromise.

I have attached a patch that implements this combination. It basically checks all the labels that are in scope to see if any have the expected type, and if none do then it looks in the type itself (possibly emitting a warning).

This patch includes a warning for labels/constructors used ambiguously, and another warning for labels/constructors used out of scope.
For record constructors/patterns, if there is no type information available, it also uses the other label ids to chose amongst the labels in the environment.

Alain, can you compile your code base with this patch?


> I would suggest that we retain a path based semantics for the module part

I agree, I think anything else would be very confusing. Also, unlike labels/constructors, a module cannot have multiple sub-modules with the same name.

(0008353)
garrigue (manager)
2012-10-29 09:01
edited on: 2012-10-29 09:01

I've applied your path to the branch record-disambiguation, after a bit of clean-up.
In particular, I have factorized the code for disambiguation on labels and constructors.
At last an application for functors in the front-end!

Also I streamlined the algorithm, so that in the case of records we disambiguate according to the label list only once (it seemed to me that it was done repeatedly for each label)

We probably need to improve error messages. Tell me of your experience.

(0008356)
lpw25 (developer)
2012-10-29 19:58

> I've applied your path to the branch record-disambiguation, after a bit of clean-up.

The clean-up looks good.

> Also I streamlined the algorithm, so that in the case of records we disambiguate according to the label list only once (it seemed to me that it was done repeatedly for each label)

If you disambiguate by label list before you disambiguate by type then you get some false positives for the out of scope warning. As in the following:

  # type foo = {x: int; y:int};;
  type foo = { x : int; y : int; }
  
  # type bar = {x: int};;
  type bar = { x : int; }
  
  # let b: bar = {x =3; y=4;};;
  Characters 14-15:
    let b: bar = {x =3; y=4;};;
                  ^
  Warning 40: x is used out of scope.
  Characters 20-21:
    let b: bar = {x =3; y=4;};;
                      ^
  Error: The record field label y belongs to the type foo
         but is mixed here with labels of type bar


Also, if you disambiguate by label list only once then you don't check the other labels properly. This allows some incorrect programs, such as:

  # module M = struct type foo = {x:int;y:int} end;;
  module M : sig type foo = { x : int; y : int; } end

  # module N = struct type bar = {x:int;y:int} end;;
  module N : sig type bar = { x : int; y : int; } end

  # let r = { M.x = 3; N.y = 4; };;
  val r : M.foo = {M.x = 3; y = 4}


I've attached a small patch that fixes these issues.

> We probably need to improve error messages.

I agree. I didn't really give any thought to them in my original patch.
(0008359)
garrigue (manager)
2012-10-30 00:38

Yes, I realized the problem after committing.
At first I thought it would be enough to eliminate mixed prefixes, but of course this is not backward compatible.
And if you allow mixed prefixes, you can get even stranger situations.

module M = struct type foo = {x:int;y:int} end
module N = struct type bar = {x:int;y:int} end

module MN = struct include M include N end
module NM = struct include N include M end
let r = {MN.x = 3; NM.y = 4};;

I.e., there are two candidates, and they are both in scope...

OK, I'll go back to doing disambiguation label by label.
This is really an overkill, but I don't see any other solution.
(0008360)
garrigue (manager)
2012-10-30 02:11

I have fixed the warnings, and added some examples to the test suite.
I only used part of your patch, but it is not because it is wrong: I just want to be sure that I understand all the code, and often I understand better if have structured it myself.
(For instance, I still don't understand much about usage warnings, see my comment in NameChoice.disambiguate)

A small concern about the test suite: how do we test warnings?
I tried adding -w A to Makefile.toplevel, but it produced a huge quantity of garbage.
Even adding it locally to a directory is not fine grain enough.
It would be nice to be able to add flags on a per-file basis.
(0008363)
frisch (developer)
2012-10-30 09:44

> This is really an overkill, but I don't see any other solution.

I propose to reject the case when a given record expression/pattern mentions two syntactically different prefixes. Strictly speaking, this is not backward compatible but I don't believe that people have actually written such horrors. (And if they had, the fix is trivial: just remove all the prefixes but one.) Let's not suffer from past design mistakes on which nobody relies!
(0008368)
lpw25 (developer)
2012-10-30 14:35
edited on: 2012-10-30 14:36

> I only used part of your patch, but it is not because it is wrong: I just want to be sure that I understand all the code, and often I understand better if have structured it myself.

Unfortunately there are still some problems.

For non-principal type information, you disambiguate by ids before you disambiguate by types. This can cause false positives for the out of scope warning:

  # type foo = { x: int; y: int };;
  type foo = { x : int; y : int; }
  
  # type bar = { x:int; y: int; z: int};;
  type bar = { x : int; y : int; z : int; }
  
  # let f r = ignore (r: foo); {r with x = 2; z = 3};;
  Characters 27-48:
    let f r = ignore (r: foo); {r with x = 2; z = 3};;
                               ^^^^^^^^^^^^^^^^^^^^^
  Warning 18: this type-based record disambiguation is not principal.
  Characters 27-48:
    let f r = ignore (r: foo); {r with x = 2; z = 3};;
                               ^^^^^^^^^^^^^^^^^^^^^
  Warning 40: this record contains fields that are out of scope: x.
  Characters 42-43:
    let f r = ignore (r: foo); {r with x = 2; z = 3};;
                                              ^
  Error: The record field label z belongs to the type bar
         but is mixed here with labels of type foo

It can also cause bound labels to be treated as unbound:

  # type foo = { x: int; y: int; };;
  type foo = { x : int; y : int; }

  # type bar = { x: int; y: int; z: int; };;
  type bar = { x : int; y : int; z : int; }

  # type other = { a: int; b: int };;
  type other = { a : int; b : int; }

  # let f r = ignore (r: foo); { r with x = 3; a = 4 };;
  Characters 43-44:
    let f r = ignore (r: foo); { r with x = 3; a = 4 };;
                                               ^
  Error: Unbound record field label a
  Did you mean a?

For principal type information, you don't disambiguate by ids at all, which can cause false positives for the ambiguity warning, as well as less accurate error messages:

  # type foo = { x: int; y: int };;
  type foo = { x : int; y : int; }
  
  # type bar = { x: int; y:int; z:int };;
  type bar = { x : int; y : int; z : int; }
  
  # type other = { a:int; b:int };;
  type other = { a : int; b : int; }
  
  # let r = {x=1; y=2};;
  val r : foo = {x = 1; y = 2}
  
  # let r: other = {x=1; y=2};;
  Characters 15-25:
    let r: other = {x=1; y=2};;
                   ^^^^^^^^^^
  Warning 41: this record contains fields that are ambiguous: x y.
  Characters 16-17:
    let r: other = {x=1; y=2};;
                    ^
  Error: The record field label x belongs to the type bar
         but is mixed here with labels of type other

All these problems are avoided by simply disambiguating by ids only after trying (and failing) to disambiguate by type.
Since disambiguating by type needs to be able to compare the final result with its choice (to give non-principal warnings correctly) this means that disambiguate_label_by_ids must be passed to Label.disambiguate as an optional parameter (which I labelled "filter").

I have attached a new patch that makes the smallest changes to fix these problems.


> (For instance, I still don't understand much about usage warnings, see my comment in NameChoice.disambiguate)

I am a bit confused by your comment. Does it mean that the call to mark_type_used is needed, and that you find that strange, or that you don't think that the call is needed?

I don't think the call is needed, and did not have it in my "disambiguate-with-warnings" patch. Unless you know a particular case that does need it, then I think you should remove it.
If you do know of a case that needs it, that might be worth putting in a test, because it is not obvious.


> This is really an overkill, but I don't see any other solution.

On the contrary, the version in my new patch should be more efficient than a "single check", because it only needs to search through a lbl_all if one of the labels is actually ambiguous rather than for all record constructors.

It is also more robust because it doesn't depend on any implicit assumptions about the relationships between the labels, and it doesn't treat the first label differently from the other labels.

It would only really be overkill if there were multiple records sharing hundreds of labels.


> I propose to reject the case when a given record expression/pattern mentions two syntactically different prefixes.

This isn't the only thing that goes wrong if you have a single check (the ambiguity warnings also don't work) so you wouldn't actually gain anything from this, and you would lose backwards compatibility.

Also, while that code is a bit pointless, it is pretty reasonable to expect that it should work, especially from Jacques view that labels are unscoped, and the paths are just helpful pointers.

(0008370)
frisch (developer)
2012-10-30 16:03

> This isn't the only thing that goes wrong if you have a single check

Fair enough. I was just arguing that it is not really a problem of backward compatibility, because I doubt any real code around would be broken by a more restrictive constraint. (Strictly speaking, each new OCaml version has the potential to break existing code as soon as it adds something to one of the stdlib modules.)
(0008372)
frisch (developer)
2012-10-30 16:41

> Alain, can you compile your code base with this patch?

It takes some effort to synchronize our local changes with such a patch (we have other changes to records: lazy fields, explicit label resolution with a type prefix, etc), so I prefer to wait for the patch to stabilize a little bit before synchronizing again.
(0008377)
garrigue (manager)
2012-10-31 10:12

> For non-principal type information, you disambiguate by ids before you disambiguate by types. This can cause false positives for the out of scope warning:

OK, I'll look into it.
We really need a way to test warnings...

> For principal type information, you don't disambiguate by ids at all, which can cause false positives for the ambiguity warning, as well as less accurate error messages:

Strange, when the type information is principal, there should never be ambiguity warnings.
The branch when a label is not found in the inferred type seems wrong from the beginning :-)

> On the contrary, the version in my new patch should be more efficient than a "single check", because it only needs to search through a lbl_all if one of the labels is actually ambiguous rather than for all record constructors.

I was thinking in terms of worse case complexity, which of course happens only when there are (many) ambiguous fields...
(0008378)
garrigue (manager)
2012-10-31 13:42

I've fixed those problems in the record-disambiguation branches, and added a directory typing-warnings to test with all warnings enabled.

>> (For instance, I still don't understand much about usage warnings, see my comment in NameChoice.disambiguate)

>I am a bit confused by your comment. Does it mean that the call to mark_type_used is needed, and that you find that strange, or that you don't think that the call is needed?

Both. I would have thought that calling use would in turn make the type used, but I get a warning when compiling gc.ml if I remove that line. Very mysterious.
(0008382)
frisch (developer)
2012-10-31 18:22

Could it be related to the unused declaration of lookup_all_label in env.ml (it should be lookup_all_labels)? (If this the case, that would be a perfect example of why those warnings are useful :-)
(0008383)
frisch (developer)
2012-10-31 18:45

I've fixed the typo, and the following stops reporting a wrong warning:

include (struct
  type t2 =
      {
       a: float;
       b: float;
      }

  let _u = {a = 123.; b = 4231.}
end : sig end)


It's a self-healing warning :-)
(0008385)
frisch (developer)
2012-10-31 18:56

Consider:
=======================
module A = struct type t = {x: int} end
module B = struct type t = {x: int} end
let f (r : B.t) = r.A.x
=======================

It gives a weird error message:

Error: The record type B.t has no field x
(0008388)
frisch (developer)
2012-10-31 19:15

ocamldoc has some custom warnings for overridden labels. I believe they should be removed, since it's now perfectly fine to be in this situation.
(0008394)
frisch (developer)
2012-10-31 21:54

After synchronizing with our local version, I've successfully compiled LexiFi's entire code base with the current version of the patch. No real problem to report. We had very few occurrences where the patch broke existing code, but I think this is only related to other local changes (namely: (i) two record types with the same definition are considered equal w.r.t. the subtyping relation; (ii) implicit subtyping on expressions, i.e. when the type-checker would like to complain that an expression has type t1 when it should have t2, we check if t1 is a subtype of t2 and if so, we accept the expression). The fix was trivial (removing or fixing a prefix).

Since there does not seem to be any opposition to the inclusion of the patch, maybe we should integrate it in the trunk rather quickly to benefit from more feedback from the community, especially on error messages.
(0008396)
lpw25 (developer)
2012-10-31 22:23

> I've fixed those problems in the record-disambiguation branches, and added a directory typing-warnings to test with all warnings enabled.

I think that the warnings now behave as they did in my original patch, so I have no more complaints about them.

The only thing that remains for me to complain about is the new Wrong_name error. It seems unnecessary since it is only emitted in cases where a Label_mismatch or Unbound_label error would be emitted slightly later.
It also provides less information than those errors.
I think that it is important to differentiate between using a label that is unbound (and so could well be a typo) and using a label for the wrong type (which could well be an incorrect type annotation).
Also you lose the benefit of the new "Did you mean ...?" spell checking.

I have attached a small patch that takes the new error out. It also does a small clean up, and fixes an uncaught Ctypes.Unify bug.
(0008402)
garrigue (manager)
2012-11-01 01:05
edited on: 2012-11-01 08:21

> It's a self-healing warning :-)

Great. I thought it was enabled on the compiler itself, but it was not the case yet.

> let f (r : B.t) = r.A.x
> Error: The record type B.t has no field x

Indeed, the path is lost in the error message, which is confusing.
I change the error message to
Error: The record type B.t has no label A.x

> The only thing that remains for me to complain about is the new Wrong_name error. It seems unnecessary since it is only emitted in cases where a Label_mismatch or Unbound_label error would be emitted slightly later.
> It also provides less information than those errors.
> I think that it is important to differentiate between using a label that is unbound (and so could well be a typo) and using a label for the wrong type (which could well be an incorrect type annotation).

Here I disagree.
If you have given type information, it is supposed to be correct.
So if you have told that something has type M.t, and the error message corresponds to resolution ignoring this information, this is not helpful.
Whether a label is defined or not does not matter that much here, since this is not the right one anyway.

> Also you lose the benefit of the new "Did you mean ...?" spell checking.
Wrong_name now calls spellcheck too, restricted to the labels of M.t.

> and fixes an uncaught Ctypes.Unify bug.
Could you be more concrete? I do not see what your patch fixes.

(0008410)
gasche (developer)
2012-11-02 17:46

A proposal for a slight extension of the semantics, in the goal of helping make it more homogeneous.

The current model is that a qualified label `P.l` may actually denote the set of candidates {P.l^t1, P.l^t2..., P.l^tn} where the {t1,..,tn} are the types defined at the long prefixed that have this label `l` (I use `l^t` to denote the unambiguous label `l` at type `t`). On the contrary, an unqualified label `l` may denote the set of candidates {P1.l^t1, P2.l^t2...} for any prefix Pk leading to a type tk having a label l -- but if the chosen candidate Pk is non-empty, a warning is raised. In other words, we look "everywhere in the environment".

This suggests the following homogeneous semantics: the label `P.l` may actually denote the set of candidates `{P.Q1.l^t1, P.Q2.l^t2, P.Q3.l^t3...}` for any (Qk,tk). In other words, a qualification P.l is considered as a prefix of the environment to consider (with a warning raised if this prefix is not precisely the long prefix needed to access the scoped label).

This allows the user to say "I'll let the type information decide but I know that the label I'm thinking of is somewhere in the sub-hierarchy starting at Foo.Bar", that is be a bit more precise than just "do anything with the type information" -- which can be helpful if the type information happens to be wrong because of an earlier mistake. The conceptual upside is to give an homogeneous semantics instead of having to discontinuous cases to consider (giving a surprisingly special status to unqualified label). The downside is that, with the previous semantics, users could qualify by hand and then be sure that "no funny stuff happens" (but here any funny stuff, that is approximative qualification, would still raise a warning).

I'm not pushing hard for this change, just explaining it in case you like it -- and wish to implement it. I would also understand a "eww, that's too complex".
(0008411)
lpw25 (developer)
2012-11-02 17:47
edited on: 2012-11-02 17:56

> If you have given type information, it is supposed to be correct.

I disagree with this a bit. Not all expected type information comes from type annotations. If there is a type error it may well to be caused by the expected type rather than by the label/constructor.


Having played around with it a bit, I think that the Wrong_name error is better than Unbound_label for unqualified names.

I don't think it is very good for qualified names. It doesn't really make sense to say that type foo doesn't have a label called M.x. Since qualified names are still being treated as scoped, it makes more sense to use "unbound" errors for them, like other scoped names.
The "unbound" errors also differentiate between an unbound label and an unbound module within the longidentifier.

I also think that Label_mismatch is better than Wrong_name when a label exists but has the wrong type. This is because Label_mismatch shows the type of the label as well as the expected type, and because it has the same format as other unification errors.
Also a spell check is unlikely to be helpful in this case.

I also noticed that both Wrong_name and Label_mismatch can appear in situations where they don't make any sense. For example:

  # type foo = { mutable y:int };;
  type foo = { mutable y : int; }

  # let f (r: int) = r.y <- 3;;
  Characters 19-20:
    let f (r: int) = r.y <- 3;;
                       ^
  Error: The record field label y belongs to the type foo
         but is mixed here with labels of type int


I have attached a patch (better-errors.diff) which does the following:
1. Only use wrong_name for unqualified labels/constructors.
2. Use Label_mismatch instead of Wrong_name for an incorrectly typed label.
3. Fix uses of Label_mismatch so that they don't print a label's full path unnecessarily.
4. Change the wording of Label_mismatch so that it always makes sense.
5. Only use Label_mismatch or Wrong_name if the expected type is actually a record (or variant) type. This is done by replacing the expected type with a new type variable, and then unifying the two at the end to produce a unification error.


> Could you be more concrete? I do not see what your patch fixes.

The following raises an uncaught CTypes.Unify:

  type foo = { y: int; z: int };;

  type bar = { x: int };;

  let f (r: bar) = ({ r with z = 3 } : foo)

I've separated the fix for this bug into its own patch (fix-unify.diff).


My "better errors" patch avoids this, but I also found another uncaught exception bug:

  # type foo = { x: int };;
  type foo = { x : int; }

  # let r : foo = { N.x = 2 };;
  Characters 16-19:
    let r : foo = { N.x = 2 };;
                    ^^^
  Error: The record type foo has no label N.xFatal error: exception Not_found

(0008416)
frisch (developer)
2012-11-05 09:54

The following expression

  (A.X : int option)

raises this error message:

Error: The variant type option has no constructor A.XFatal error: exception Not_found


I assume there is something wrong with the spellchecking here.
(0008417)
frisch (developer)
2012-11-05 10:32

After playing a little bit with the new behavior, I've found that in some cases, I'd prefer type information to flow from the rhs of a let-binding to the lhs instead of the opposite. A typical case is to deconstruct the result of a function with a record pattern:

  let {x; y} = foo .... in ...

This can be achieved by doing a pattern matching instead:

  match foo .... with {x; y} -> ...


Of course, in other cases, it's more useful to have the information flow as it currently does, from the pattern to the bound expression:

  let x : t = .... in ...

I'm wondering if the current information flow could be tweaked to capture more useful cases, by keeping some predictability. (I don't have a proposal!)
(0008418)
gasche (developer)
2012-11-05 12:03

Another point in favor of the "homogeneous semantics" of http://caml.inria.fr/mantis/view.php?id=5759#c8410 [^] instead of the current two-case semantics.

In the initial implementation of Jacques, type-based disambiguation was used only to infer long prefixes, without solving the problem of conflicting labels in the same module. If you had

  (* library side *)
  type t1 = {x : int}
  type t2 = {x : float}
  val foo : t1

  (* user side *)
  let test = foo.x

This would be rejected by the type-checker, but you could write in the following style to avoid the conflict:

  module M = struct type t1 = {x : int} end
  module N = struct type t2 = {x : float} end
  val foo : M.t1

  let test = foo.x

With the lookup-into-submodules-style disambiguation applied only to unqualified path, this transformation fails if it is not done at the toplevel but inside a module. From

  (* library side *)
  module P =
    type t1 = {x : int}
    type t2 = {x : float}
    val foo : t1
  end

  (* user side *)
  let test = P.foo.P.x

you cannot move to

  module P =
    module M = struct type t1 = {x : int} end
    module N = type t2 = {x : float} end
    val foo : M.t1
  end

  let test = P.foo.P.x

without changing user code to P.foo.x.

Granted, for this transformation to work, you still have to ask your user to turn the "use-large-strategy" knob by not treating long prefix inference as an error.

The point is that for the users of the "large strategy" disambiguation, the homogeneous semantics that also supports qualified accesses probably provide a better experience by allowing more robust type-correctness-preserving program transformations (that work even with qualified label accesses, instead of forcing people to rewrite the code with unqualified accesses to have the robust semantics).
(0008444)
garrigue (manager)
2012-11-08 11:34

I have just committed revision 13075, fixing the problems described by Leo and Alain.
Sorry for the long delay.

> I also noticed that both Wrong_name and Label_mismatch can appear in situations where they don't make any sense. For example: ...

Very right, and a long standing problem. I've merged that part.

> The following raises an uncaught CTypes.Unify: ...

Thanks, indeed one of the unifications may trigger an exception. Fixed.

> Error: The record type foo has no label N.xFatal error: exception Not_found

Fixed by having lookup_all_labels fail if the prefix is incorrect.


However, I have not merged the part on reverting to use Label_mismatch.

> > If you have given type information, it is supposed to be correct.
> I disagree with this a bit. Not all expected type information comes from type annotations. If there is a type error it may well to be caused by the expected type rather than by the label/constructor.

I thought about it, and actually I started implementing your solution just before my commit of last week.
However, while it is true that the type may be wrong, I cannot find a situation where printing the type of the label/constructor is useful in solving the error.
Namely, if you get such an error, your problem is with the wrong type, and you are going to search where it is coming from. Knowing the "right" type doesn't help solving the problem.
Worse, if the label was ambiguous, this may not even be the right type.
So my conclusion is that it is not really useful to have a special case for non-ambiguous qualified labels or constructors.
(0008452)
frisch (developer)
2012-11-08 19:22

Thanks Jacques!

Wouldn't it be a good time to push the branch to the trunk? There seems to be a consensus on the approach now, and delaying the merge increase the chances of conflicts.
(0008453)
hongboz (developer)
2012-11-08 20:58
edited on: 2012-11-08 21:00

alain, if this is pushed to trunk, will anyone to rename parsetree, typetree to make the name more consistent? I am glad to see the changes ;-)

(0008455)
garrigue (manager)
2012-11-09 07:32

Alain, I'm just waiting a bit to be sure that everybody is satisfied.
Then I'll send a mail to caml-devel (probably next week).
(0008456)
gasche (developer)
2012-11-09 08:19

I would personally appreciate if the behavior respected the symmetry between record construction and pattern-matching on variant constructors; currently, both the {Foo.Bar.l1 = ...; l2 = ...} and the multi-name lookup are reserved to labels and could be extended to variant matching.

I'm planning to *consider* writing patches for those and propose them to submission. I don't think you should delay trunk-merging of the current patchset because of this. Besides, because of the implementation difficulties, it may be best in any case if those features land in a second step, rather than bundled with the current patchset.
(0008457)
garrigue (manager)
2012-11-09 10:00

Gabriel, I'm very reticent about doing multi-label lookup for sums.
I was already hesitating for records, but eventually accepted because record construction/destruction(in patterns) use multiple labels in a single syntactic construct.
However, there is no such syntactic construct for variants: constructors are always taken individually.
To do multi-label lookup, you would need to change the interpretation of pattern-matching as a whole.
(And a patch does not replace a discussion.)
(0008460)
frisch (developer)
2012-11-09 10:37

Does anyone have an opinion about allowing type-information to flow from the bound expression to the pattern (instead of the other way around) in a construction like "let {x; y} = foo in ..."?
(0008464)
gasche (developer)
2012-11-09 13:56

> (And a patch does not replace a discussion.)

Indeed, but I would feel better doing some work on my side first before drawing you into an argument.

Alain > given that the type-information propagation is a part of the observable (and therefore which-should-be-specified) static semantics of the language, I'd rather have a specification as clear as possible. Can you come up with a good spec that is good for you, that Jacques is ready to implement? I would personally be ok with a backtracking semantics if it allows to restore the symmetry, but...
(0008468)
frisch (developer)
2012-11-09 14:22

> given that the type-information propagation is a part of the observable (and therefore which-should-be-specified) static semantics of the language, I'd rather have a specification as clear as possible. Can you come up with a good spec that is good for you, that Jacques is ready to implement? I would personally be ok with a backtracking semantics if it allows to restore the symmetry, but...

My hope was that Jacques would come with such a proposal :-)


I would actually be rather comfortable with type-checking:

  let p = e in ...

as

  match e with p -> ...

plus the expected generalization. The only case where I really want type-information to flow from the lhs to the rhs is the explicit type annotation:

  let p : t = e in ...

but it's rather straightforward to translate that to:

  let p = (e : t) in ...

and IIRC, this is already what the parser or type-checker does.


I did not think about recursive bindings yet, maybe there are cases where it's really important to type patterns first.
(0008473)
garrigue (manager)
2012-11-09 14:35

Actually, there is no theory yet about propagating type information into patterns...
So we will have to come up with something reasonable.
But I agree that propagation from expression is easier to formalize that the other way round, so this may be possible. The only problem is that the typing of let expressions is very complicated (and unused-detection warnings do not help there :-)
(0008475)
garrigue (manager)
2012-11-09 15:12

Thinking a bit more about the handling of let.
Of course, if this is a "let rec", we have to type the pattern first, so we cannot propagate from the expression to the pattern.
But fortunately, in ocaml, the two are explicitly distinguished.
And since type_cases now generalizes type variables, we could completely delegate non-recursive lets to type_cases (patterns with GADTs are already delegated).
I see just two problems:
* explicit polymorphic types. Currently they are handled by let, but maybe we could handle them as normal type annotations.
* unused identifier warnings. I'm afraid they work differently with type_let and type_cases. Or am I wrong.
(0008480)
frisch (developer)
2012-11-09 19:35

> * unused identifier warnings. I'm afraid they work differently with type_let and type_cases. Or am I wrong.

There is some complexity in type_let, but only to support recursive definitions. Otherwise, this is quite simple. I think that different warnings are raised for unused bindings arising from let and from match, but the behavior is the same otherwise, and the code is prepared to be parametrized by the kind of warning to raise. Anyway, if you do the rest of the work, I promise to adapt the warnings :-)
(0008486)
lpw25 (developer)
2012-11-10 18:30

> Namely, if you get such an error, your problem is with the wrong type, and you are going to search where it is coming from. Knowing the "right" type doesn't help solving the problem.
> Worse, if the label was ambiguous, this may not even be the right type.
> So my conclusion is that it is not really useful to have a special case for non-ambiguous qualified labels or constructors.

This seems reasonable for unqualified labels, however I disagree for qualified labels. I think that the examples below both show errors that are easier to understand when the error message includes the type of the label.

I also think that the Wrong_name error implies that the label was looked for in the type itself, which is not true for qualified labels. I would rather the error better reflected what the typechecker is actually doing for qualified labels.
However, your point about ambiguity is a good one, and it is clear that Label_mismatch is not an appropriate error either.

It seems to me that we should probably add a new error, for cases where a qualified label/constructor does not have the expected type.
I have implemented such an error in the new-error.diff patch.

Here is an example of the new error in practice:

  # module N = struct module M = struct type foo = Foo end end;;
  module N : sig module M : sig type foo = Foo end end

  # module M = struct type foo = Foo end;;
  module M : sig type foo = Foo end

  # type foo2 = M.foo;;
  type foo2 = M.foo

  # open N;;

  # let r : foo2 = M.Foo;;
  Characters 15-20:
    let r : foo2 = M.Foo;;
                   ^^^^^
  Error: The constructor M.Foo belongs to the variant type N.M.foo
         but a constructor was expected belonging to the variant type
           foo2 = M.foo

Here is another example, this time with an ambiguous constructor:

  # module M = struct type foo = Foo end;;
  module M : sig type foo = Foo end

  # type foo2 = M.foo;;
  type foo2 = M.foo

  # module M = struct type foo = Foo type bar = Foo end;;
  module M : sig type foo = Foo type bar = Foo end

  # let r : foo2 = M.Foo;;
  Characters 15-20:
    let r : foo2 = M.Foo;;
                   ^^^^^
  Error: The constructor M.Foo belongs to one of the following variant types:
           M/1024.bar M/1024.foo
         but a constructor was expected belonging to the variant type
           foo2 = M/1018.foo

I think that it is much clearer from these error messages what is going on. It is clear that the type checker looked up the constructor, found one or two types that it might have belonged to, but they didn't match the type that was expected.


In the better-errors.diff patch I also made some changes to the wording of errors/warnings. In particular, I improved the new warning messages and I replaced all uses of "label" with "field" since that is what they are called in the OCaml manual. I think these changes are probably worth including.

Also, since Alain fixed the typo in env.ml, I don't think we still need the call to mark_type_used in typecore.ml that your comment describes as strange.
(0008487)
gasche (developer)
2012-11-10 18:52

While you're fixing typos in passing, there is a minuscule one at line 658 of typecore.ml:

655: let labels' = List.filter check_ids labels in
656: if keep && labels' = [] then (false, labels) else
657: let labels'' = List.filter check_closed labels' in
658: if keep & labels'' = [] then (false, labels') else (true, labels'')

The "&" should be "&&".
(0008495)
garrigue (manager)
2012-11-11 04:51

OK, I've merged new-error.diff, and switched from label to field (also removing other uses of label for records). (I was not aware of the wording in the manual, and the source code only uses "label"...)
I agree that error messages should reflect what the compiler does, and this is the case now.

I also removed the useless line (I remember checking that it was no longer needed, but clearly I forgot to commit that).

And changed & into && (which is strictly equivalent...)

I didn't change the warning. Do you really think it makes a difference?
(0008497)
lpw25 (developer)
2012-11-11 14:20

> I didn't change the warning. Do you really think it makes a difference?

Not particularly. The warnings can always be reworded later if people don't understand them.

I think all the warnings and errors are in good shape now.

I look forward to seeing this extension in the trunk.
(0008589)
garrigue (manager)
2012-12-07 15:31

The branch record-disambiguation was merged into trunk on 2012-12-06, at revision 13112.
(0008818)
hongboz (developer)
2013-02-05 02:43

Shall we add an explicit warning here when such fancy label resolving technique is applied? My concern is that it's hard to write code which is compatible with 4.00 or elder compilers. Thanks
(0008820)
frisch (developer)
2013-02-05 06:52

We generally aim at preserving backward compatibility (not breaking code accepted by a previous version of the compiler, except for good reasons). If you want to write code which can be compiled with both an old and a recent version of OCaml, you should thus use the old version during development. I don't think we want to add new warning for each new language feature or improvement to the type-inference introduced in newer releases.
(0008821)
hongboz (developer)
2013-02-05 15:13

Using the old compiler seems to reasonable, but not practical. I TA a compiler class using ocaml, but some students used a very old compiler 3.11. The problem with this new feature is that it is easy to be used unintentionally, unlike other features (let open XX in...). Thanks
(0008864)
garrigue (manager)
2013-02-19 03:46
edited on: 2013-02-19 04:16

Added warning 42 for disambiguated labels and constructors, as requested by hongboz.
Committed in trunk, revision 13297.


- Issue History
Date Modified Username Field Change
2012-09-15 07:28 frisch New Issue
2012-09-15 07:28 frisch Status new => assigned
2012-09-15 07:28 frisch Assigned To => garrigue
2012-09-15 09:26 garrigue Note Added: 0008077
2012-09-17 12:21 frisch Note Added: 0008083
2012-09-19 05:11 garrigue File Added: record-disambiguation.diffs
2012-09-19 05:20 garrigue Note Added: 0008104
2012-09-19 10:30 frisch Note Added: 0008106
2012-09-19 11:30 gasche Note Added: 0008108
2012-09-19 11:31 gasche Note Edited: 0008108 View Revisions
2012-09-19 11:33 gasche Note Edited: 0008108 View Revisions
2012-09-20 03:55 garrigue Note Added: 0008121
2012-09-20 08:14 gasche Note Added: 0008122
2012-09-20 10:23 frisch Note Added: 0008123
2012-09-21 10:45 garrigue Note Added: 0008133
2012-09-21 13:26 garrigue Note Added: 0008135
2012-09-22 20:19 frisch Note Added: 0008144
2012-09-24 09:09 garrigue Note Added: 0008149
2012-09-25 00:22 frisch Note Added: 0008156
2012-10-16 17:26 frisch Note Added: 0008270
2012-10-16 17:35 gasche Note Added: 0008271
2012-10-16 17:45 frisch Note Added: 0008272
2012-10-16 17:52 frisch Note Added: 0008273
2012-10-17 13:22 garrigue Note Added: 0008275
2012-10-17 19:12 frisch Note Added: 0008278
2012-10-17 19:29 frisch File Added: patch_use_all_fields.diff
2012-10-17 19:40 frisch Note Added: 0008279
2012-10-17 20:22 frisch Note Added: 0008280
2012-10-18 09:36 garrigue Note Added: 0008283
2012-10-18 15:12 frisch File Deleted: patch_use_all_fields.diff
2012-10-18 15:12 frisch File Added: patch_use_all_fields.diff
2012-10-18 19:12 frisch Note Added: 0008284
2012-10-18 19:42 frisch Note Added: 0008285
2012-10-18 19:45 frisch Note Edited: 0008285 View Revisions
2012-10-18 19:48 frisch Note Added: 0008286
2012-10-19 05:54 garrigue Note Added: 0008288
2012-10-19 10:01 frisch Note Added: 0008289
2012-10-19 12:10 frisch File Added: sum_disambiguation.diff
2012-10-19 12:10 frisch Note Added: 0008290
2012-10-22 19:45 lpw25 Note Added: 0008291
2012-10-22 19:46 lpw25 File Added: disambiguate.diff
2012-10-23 01:08 lpw25 Note Edited: 0008291 View Revisions
2012-10-23 06:35 garrigue Note Added: 0008293
2012-10-23 08:22 frisch Note Added: 0008294
2012-10-23 10:24 lpw25 Note Added: 0008296
2012-10-23 10:25 gasche Note Added: 0008297
2012-10-23 10:49 frisch Note Added: 0008300
2012-10-23 10:54 frisch Note Added: 0008301
2012-10-23 11:57 lpw25 Note Added: 0008303
2012-10-23 12:19 lpw25 Note Edited: 0008303 View Revisions
2012-10-23 13:09 lpw25 Note Edited: 0008303 View Revisions
2012-10-23 13:34 frisch Note Added: 0008304
2012-10-23 14:56 garrigue Note Added: 0008305
2012-10-23 16:07 lpw25 Note Added: 0008306
2012-10-23 16:08 lpw25 Note Edited: 0008306 View Revisions
2012-10-23 17:50 gasche Note Added: 0008308
2012-10-24 08:49 garrigue Note Added: 0008319
2012-10-24 12:20 frisch Note Added: 0008320
2012-10-24 13:14 garrigue Note Added: 0008321
2012-10-24 14:23 frisch Note Added: 0008322
2012-10-24 14:54 frisch Note Added: 0008323
2012-10-24 14:55 frisch Note Edited: 0008323 View Revisions
2012-10-24 14:56 frisch Note Edited: 0008323 View Revisions
2012-10-24 15:14 gasche Note Added: 0008324
2012-10-24 15:36 garrigue Note Added: 0008325
2012-10-24 15:48 gasche Note Added: 0008326
2012-10-24 15:49 gasche Note Edited: 0008326 View Revisions
2012-10-24 15:50 gasche Note Edited: 0008326 View Revisions
2012-10-24 16:00 frisch Note Added: 0008327
2012-10-24 16:12 frisch Note Added: 0008328
2012-10-24 16:13 garrigue Note Added: 0008329
2012-10-24 16:46 garrigue Note Added: 0008330
2012-10-24 16:59 garrigue Note Added: 0008331
2012-10-24 17:22 lpw25 Note Added: 0008332
2012-10-24 17:23 lpw25 Note Edited: 0008332 View Revisions
2012-10-24 17:24 lpw25 Note Edited: 0008332 View Revisions
2012-10-24 17:25 lpw25 Note Edited: 0008332 View Revisions
2012-10-24 17:30 lpw25 Note Edited: 0008332 View Revisions
2012-10-24 19:05 lpw25 Note Edited: 0008332 View Revisions
2012-10-24 22:53 garrigue Note Added: 0008333
2012-10-24 23:00 garrigue Note Edited: 0008333 View Revisions
2012-10-24 23:12 garrigue Note Edited: 0008333 View Revisions
2012-10-24 23:43 lpw25 Note Added: 0008334
2012-10-25 01:40 garrigue Note Added: 0008335
2012-10-25 06:59 frisch Note Added: 0008336
2012-10-25 12:32 lpw25 Note Added: 0008337
2012-10-25 12:33 lpw25 Note Edited: 0008337 View Revisions
2012-10-25 12:34 lpw25 Note Edited: 0008337 View Revisions
2012-10-25 12:54 lpw25 Note Edited: 0008337 View Revisions
2012-10-25 13:19 gasche Note Added: 0008338
2012-10-25 13:43 frisch Note Added: 0008339
2012-10-25 14:53 garrigue Note Added: 0008341
2012-10-25 16:43 garrigue Note Added: 0008342
2012-10-25 18:07 lpw25 Note Added: 0008343
2012-10-25 20:13 frisch Note Added: 0008344
2012-10-26 11:16 gasche Note Added: 0008345
2012-10-26 11:17 gasche Note Edited: 0008345 View Revisions
2012-10-26 11:19 gasche Note Edited: 0008345 View Revisions
2012-10-26 13:17 garrigue Note Added: 0008346
2012-10-26 14:23 gasche Note Added: 0008347
2012-10-27 13:06 garrigue Note Added: 0008348
2012-10-27 15:27 frisch Note Added: 0008349
2012-10-28 17:36 lpw25 File Added: disambiguate-with-warnings.diff
2012-10-28 17:43 lpw25 Note Added: 0008352
2012-10-28 17:45 lpw25 Note Edited: 0008352 View Revisions
2012-10-29 09:01 garrigue Note Added: 0008353
2012-10-29 09:01 garrigue Note Edited: 0008353 View Revisions
2012-10-29 19:49 lpw25 File Added: fix-warnings.diff
2012-10-29 19:58 lpw25 Note Added: 0008356
2012-10-30 00:38 garrigue Note Added: 0008359
2012-10-30 02:11 garrigue Note Added: 0008360
2012-10-30 09:44 frisch Note Added: 0008363
2012-10-30 14:14 lpw25 File Added: fix-warnings2.diff
2012-10-30 14:35 lpw25 Note Added: 0008368
2012-10-30 14:36 lpw25 Note Edited: 0008368 View Revisions
2012-10-30 16:03 frisch Note Added: 0008370
2012-10-30 16:41 frisch Note Added: 0008372
2012-10-31 10:12 garrigue Note Added: 0008377
2012-10-31 13:42 garrigue Note Added: 0008378
2012-10-31 18:22 frisch Note Added: 0008382
2012-10-31 18:45 frisch Note Added: 0008383
2012-10-31 18:56 frisch Note Added: 0008385
2012-10-31 19:15 frisch Note Added: 0008388
2012-10-31 21:54 frisch Note Added: 0008394
2012-10-31 22:23 lpw25 Note Added: 0008396
2012-10-31 22:24 lpw25 File Added: fix-warnings3.diff
2012-11-01 01:05 garrigue Note Added: 0008402
2012-11-01 08:21 garrigue Note Edited: 0008402 View Revisions
2012-11-02 17:46 gasche Note Added: 0008410
2012-11-02 17:47 lpw25 File Added: better-errors.diff
2012-11-02 17:47 lpw25 File Added: fix-unify.diff
2012-11-02 17:47 lpw25 Note Added: 0008411
2012-11-02 17:53 lpw25 Note Edited: 0008411 View Revisions
2012-11-02 17:56 lpw25 Note Edited: 0008411 View Revisions
2012-11-05 09:54 frisch Note Added: 0008416
2012-11-05 10:32 frisch Note Added: 0008417
2012-11-05 12:03 gasche Note Added: 0008418
2012-11-08 11:34 garrigue Note Added: 0008444
2012-11-08 19:22 frisch Note Added: 0008452
2012-11-08 20:58 hongboz Note Added: 0008453
2012-11-08 21:00 hongboz Note Edited: 0008453 View Revisions
2012-11-09 07:32 garrigue Note Added: 0008455
2012-11-09 08:19 gasche Note Added: 0008456
2012-11-09 10:00 garrigue Note Added: 0008457
2012-11-09 10:37 frisch Note Added: 0008460
2012-11-09 13:56 gasche Note Added: 0008464
2012-11-09 14:22 frisch Note Added: 0008468
2012-11-09 14:35 garrigue Note Added: 0008473
2012-11-09 15:12 garrigue Note Added: 0008475
2012-11-09 19:35 frisch Note Added: 0008480
2012-11-10 18:04 lpw25 File Added: new-error.diff
2012-11-10 18:30 lpw25 Note Added: 0008486
2012-11-10 18:52 gasche Note Added: 0008487
2012-11-11 04:51 garrigue Note Added: 0008495
2012-11-11 14:20 lpw25 Note Added: 0008497
2012-12-04 17:10 frisch Relationship added parent of 0005525
2012-12-07 15:31 garrigue Note Added: 0008589
2012-12-07 15:31 garrigue Status assigned => resolved
2012-12-07 15:31 garrigue Fixed in Version => 4.01.0+dev
2012-12-07 15:31 garrigue Resolution open => fixed
2013-02-05 02:43 hongboz Note Added: 0008818
2013-02-05 06:52 frisch Note Added: 0008820
2013-02-05 15:13 hongboz Note Added: 0008821
2013-02-19 03:46 garrigue Note Added: 0008864
2013-02-19 04:16 garrigue Note Edited: 0008864 View Revisions
2013-04-25 16:36 lpw25 Relationship added related to 0006000
2013-04-26 00:34 garrigue Relationship added parent of 0005848


Copyright © 2000 - 2011 MantisBT Group
Powered by Mantis Bugtracker