usethesource / rascal

The implementation of the Rascal meta-programming language (including interpreter, type checker, parser generator, compiler and JVM based run-time system)
http://www.rascal-mpl.org
Other
406 stars 78 forks source link

Compiler can't infer keyword params on concrete syntax types #956

Closed TimSoethout closed 7 years ago

TimSoethout commented 8 years ago

Context: branch tree-keyword-parameters

When I add a keyword parameter on my concrete syntax type, the compiler will complain. The interpreter works fine.

import ParseTree;
import IO;
import Traversal;
import Node;

start syntax MyTree = "(" MyTree left "," MyTree right ")"
                    | Int
                    ;

layout MyLayout = [\ \t\n\r]*;

lexical Int = [0-9]+ | "Inf";   

&T<:Tree clean(&T<:Tree e) = delAnnotationsRec(e); 

public MyTree t = clean((MyTree)`(1, (2, 3))`);

data Tree(str simple = "");

public MyTree annotated = t[simple = "value"];

The compiler will complain on the last line with

Field simple does not exist on type MyTree

Obviously it is there via the keyword parameter on Tree.

PaulKlint commented 8 years ago

@TimSoethout thanks for your report. This is an area of the language where the compiler (in particular the type checker) is not yet completely mature. @mahills could you have a look at this? Maybe also a good occasion to finalize all the common keyword related issues.

mahills commented 7 years ago

I'm adding support for this, but the ability to add parameters like this could create problems -- I would assume that adding a parameter x to Tree would break any production that includes a label x on a nonterminal (e.g., syntax B = myB: "this" x:A "that"). This would be very non-local, so it would make sense to discuss this. We already have this problem with keyword params on ADTs, but at least in that case we don't have subtyping.

jurgenvinju commented 7 years ago

Cool!!

This feature is truly missing for a predictable and complete language design, one where concrete trees can always play the role of abstract data types. That's the goal.

I agree with your assessment, same rules apply for syntax trees as for abstract data. And the fields on Tree can and should not be shadowed by any user defined fields on non-terminals since that would break the dynamic semantics of generic code which operates on the Tree level but of course is always applied to concrete instances.

Finally this feature will interact later with data dependent parsers in some way or another. But that is as of yet unclear.

jurgenvinju commented 7 years ago

I don't think non locality is an issue, much. We can either prohibit interaction at the usage site, which can become cumbersome but it's ready to implement, or allow the user to disambiguate in case of multiple resolutions. So you'd have to type 'x.Tree::field' if field occurs in both Tree and a non terminal in scope..

mahills commented 7 years ago

I'm more concerned about the fact that adding the keyword parameter in one file could break other files, including library files that the user can't change. They would still be responsible for fixing it, and could (since it would be their addition that would have broken it), but it could be confusing.

jurgenvinju commented 7 years ago

I don't get that. If one module only has one of the definitions in scope, then out only sees that definition right? Ah.. I see.. The names could overlap dynamically and cancel each other out of we use the same mechanism for both levels in the type hierarchy...

We might: prefix non terminal fields with a unique prefix?

Add another layer of wrapping, trees with parameters nested around constructors with keyword parameters, or vice versa..

Something to think about indeed! Good point.

mahills commented 7 years ago

Right -- it could be that module M1 is fine, and M2 is fine, but when I import both into M3 it breaks because of a parameter name conflict. We already have this as an issue with ADTs, the issue here is the indirection -- adding something to Tree could break non-Tree types.

jurgenvinju commented 7 years ago

Statically we should resolve it with qualified names I think. And dynamically we will also need a prefix..

mahills commented 7 years ago

We can definitely resolve it, I guess part of the question is whether we want to. Resolving it would imply that we keep two or three copies of a parameter, e.g., the version declared on Tree, the common parameter declared on the type, and a production-specific parameter, all of which could have different types. Whether the prefix is needed or not would then depend on whether there is an issue (you could always use it, but it would be cumbersome if not needed).