HaxeFoundation / haxe

Haxe - The Cross-Platform Toolkit
https://haxe.org
6.15k stars 658 forks source link

Suggestion: add a Constant variant for TypePath #6910

Closed bendmorris closed 6 years ago

bendmorris commented 6 years ago

Seeing some unintuitive behavior regarding what is considered a constant or valid expression value/metadata argument.

Valid

class Test {
    static function main():Void {
        var a = Test;
        trace(a);
        // Test.hx:3: Class<Test>
    }
}
var a = sys.net.Socket;
@:meta(sys.net.Socket) public function myFunction {}

Invalid (fails to parse)

class Test {
    static function main():Void {
        var a = Array<Test>;
        trace(a);
        // Test.hx:3: characters 28-29 : Unexpected ;
        // (for some reason I'll get that warning twice)
    }
}
@:meta(Null<Test>) public function myFunc() {}

// Test.hx:3: characters 20-21 : Unexpected )

Suggestion

The valid cases can be treated as Constants (CIdent) and there's no comparable structure for identifiers which represent types with parameters, so A<B> isn't a valid expression. However, an identifier (which may refer to a type) is not conceptually any more of a "constant" than a parameterized type name.

How about a CTypePath(p:TypePath)? Mainly I want this so I can specify type paths as metadata arguments (without just using a String.)

back2dos commented 6 years ago

The problem here is syntactic ambiguity. The compiler parses Array<Test as a binary operation:

var Array = Std.random(42),
    Test = Std.random(42);
trace(Array<Test/*> ... cannot put this here*/);

Scala doesn't have this problem, because their syntax is Array[Test]. Perhaps you could use that? Other that than @:meta(var pickAName:Null<Test>) will work, if that's any consolation. Perhaps we could allow for : SomeType to be parse to EComplexType(macro : SomeType)? It shouldn't be ambiguous, but it might lead to pretty confusing error messages if you do odd mistakes in ternary operator, e.g. decrement ? foo--- : foo++. That's a pretty silly example, but perhaps there are likelier mistakes?

bendmorris commented 6 years ago

Right, I was thinking this could also be resolved simply by preferring well-formed parameterized type paths over binary operations. That should only cause problems where there is overlap such as a < b > c which is intended as binary operations; but this usage seems extraordinarily rare (the example only makes sense if you've overridden the operators, because bool comparison doesn't make sense) and could be disambiguated with parens. Maybe there are other conflicts with bit shifts or parens that I haven't thought of.

: SomeType is not a terrible alternative. Something like Array[Test] would be easier to implement but I wouldn't want to introduce inconsistent syntax just for this.

back2dos commented 6 years ago

Ok, let's try something different:

foo.Module.SubType<bar.Module.SubType>
foo.Class.CONSTANT<bar.Class.CONSTANT

Of course the comparison of static constants is unlikely. But then there's also the issue that constant expressions are valid type parameters: sys.db.VarChar<255>.

Plus to implement this, the parser may have to do quite the look ahead. So I wouldn't be too optimistic about this.

ousado commented 6 years ago

While there would be a modest benefit of being able to write an expression like Array<Test> instead of (_:Array<Test>), it's a good thing that types don't have to parse as expressions and that expressions don't have to parse as types. It allows to choose the syntax for each independently. Conflating the two means being more restricted and losing flexibility down the road.

Using a colon without the parens to introduce a type annotation would be excellent, but it unfortunately does introduce ambiguities with ternary (otherwise we'd actually have that syntax now).

Other than that, it'd be a quite an effort to support this, and given the limited developer resources, I doubt the investment would be justified.

back2dos commented 6 years ago

Using a colon without the parens to introduce a type annotation would be excellent, but it unfortunately does introduce ambiguities with ternary

Would you care to be specific?

ousado commented 6 years ago

Short story: cond ? A : B : C - where's the type?

This idea has been discussed on and off. Simon took at least two shots at it, and I also experimented with it. There's another conflict with case .. : which isn't very pretty to work around. (if you're interested, see here and look for in_case which only has been introduced there to deal with that ambiguity).

I didn't really bother much trying to get ternary to work, but Simon had come to the conclusion that to remain consistent, introducing expr : SomeType would mean that ternary had to go.

back2dos commented 6 years ago

You're not addressing the question. I understand the issue with ECheckType - it just so happens that almost five years ago the conclusions you're now offering were put forward by me :P

This is a different matter. I'm saying an expression commencing with : should parse a complex type next and yield EComplexType(theType). For case <expr>: the : is not in a leading position. Same goes for the ternary, where cond ? A : B is valid and the : simply is not. I don't see how this might cause ambiguity, but if you can think of something, please do. The only issue I see is that, as stated above, if you botch the syntax within a ternary operator, the resulting error might be confusing.

ousado commented 6 years ago

Oh.. alright. Syntax-wise there are no issues I see, then. What would be the type(s) of the resulting run-time value?

back2dos commented 6 years ago

What would be the type(s) of the resulting run-time value?

It depends. If it's not a runtime value at all, then it should error, as would var x = Iterable;. I guess var x = :Array<Int> should be Array but rather typed as Class<Array<Int>> and for @:generic classes it should actually select the particular specialization.

bendmorris commented 6 years ago

It seems like the runtime value question is already answered, because this already works:

typedef MyArray = Array<Test>;

class Test {
    static function main() {
        var a = MyArray;
        trace(a);
    }
}

Seems like :Array<Test> should have the same result.

bendmorris commented 6 years ago

One question, is there an advantage to EComplexType() over EConst(CComplexType(...))? I ask because:

back2dos commented 6 years ago

There's two kinds of meta data:

As for the rest, I don't know. But since pack.MyType parses as EField, EConst wouldn't be my first pick. Especially since currently it always represents single token expressions. Hence I would opt for EComplexType, but of course this is also subject to taste ;)

ncannasse commented 6 years ago

Sorry if I have overlooked this, but any syntactic change should go through https://github.com/HaxeFoundation/haxe-evolution

bendmorris commented 6 years ago

Evolution proposal: https://github.com/HaxeFoundation/haxe-evolution/pull/44