GithubHelp home page GithubHelp logo

'using' conversion semantics about ecsharp HOT 12 CLOSED

qwertie avatar qwertie commented on May 31, 2024
'using' conversion semantics

from ecsharp.

Comments (12)

qwertie avatar qwertie commented on May 31, 2024

[Edited] These should be illegal:

WriteLine(2.5 using int);
WriteLine(bignum using int);

Here's my original proposal:

// EC# introduces a new kind of cast operator, the "using" operator, to help you 
// select an alias or interface to use. "using" is basically a kind of static 
// assertion: it asserts that "t" can act as a T. It behaves like a cast, except 
// that the implicit conversion to T must be legal. In other words, the compiler 
// must know at compile time that the conversion is valid, or there is a compiler
// error. By itself, it can be used to 
// - select an alias to use (including explicit aliases)
// - select an interface (for calling explicit interface implementations)
// - select a base class (for calling methods shadowed by 'new')
// - invoke an implicit conversion (but not an explicit conversion)
// Here's an example:
int three = (new[] { 1,2,3 } using IList).Count;

The explanation above seems to leave out part of my plan. In fact, I didn't think of using as just a cast operator. So let me rephrase the proposal: my plan for using was for it to be two separate pieces of functionality in one operator:

  1. To select an interface, base class, or alias without performing a conversion.
  2. To perform implicit conversions (e.g. int to double).

In fact I planned for item (1) to do something that standard C# couldn't do, as illustrated here:

struct Foo : ICloneable {
    object ICloneable.Clone() { return this; }
}
void Example() { new Foo()(using ICloneable).Clone(); }

Ignoring for a moment that this code is silly, I envisioned that this would call Clone() on the original struct value, not on a boxed copy of it.

I actually never verified that the CLR allows this, and C# certainly doesn't, but that's what I wanted :)

My thinking is a bit different now. Enhanced C# has an "escape hatch" - you can always quit using it and return to plain C#. That's supposed to be a key selling point. Therefore, a feature that doesn't work in plain C# is undesirable. So I think your interpretation of using as a "conversion" is acceptable.

Maybe it would be a good idea that, when a user writes something like foo(using IBar).Baz() and foo is a struct, the compiler would warn that Baz will run on a boxed copy of foo rather than the original (which leads me to wonder... maybe there should be a warning like that for readonly struct fields too...). On the other hand, if the user writes Baz(foo(using IBar)), I guess they should already know that foo will be boxed. Well, I guess this warning thing might be too much work.

from ecsharp.

jonathanvdc avatar jonathanvdc commented on May 31, 2024

Oh, I see. I've updated ecsc to adhere to your explanation in this commit.

Which leaves us with the boxing problem. So your proposals for new Foo()(using ICloneable).Clone(); are:

  1. Make it call Clone() without boxing the Foo instance. I'm fairly sure the CLR can handle this: the constrained. T opcode does just that. But this feature doesn't really need CLR support, because the compiler can just emit a direct call to Foo.ICloneable.Clone anyway. The only reason not to implement this first behavior is that it's a special case: it waives the normal rules for implicit conversions, and its semantics are inconsistent with ((ICloneable)new Foo()).Clone().
  2. Just perform the boxing conversion and issue a warning. This shouldn't be hard to implement. All it would take is a short walk down the IR tree, looking for calls to structs that are boxed.
  3. Box the struct and don't issue a warning. This does get rid of the annoyance of warning the user when Foo is an immutable struct. In that case, whether Foo gets boxed or not doesn't matter in terms of run-time semantics.

Personally, I'm leaning toward the first option.

from ecsharp.

qwertie avatar qwertie commented on May 31, 2024

If it's easy to implement without boxing, another option would be to do that but emit a warning that the code cannot be converted to plain C#.

The RangeEnumerator struct in Loyc.Essentials illustrates the usefulness of this behavior:

/// <remarks>Although there is a ICloneable{R} constraint on R, it is currently
/// worthless due to a limitation of C#. Since <see cref="IFRange{T}"/> already
/// includes <c>ICloneable(IFRange(T))</c>, this structure cannot simply
/// invoke Clone() directly because the compiler complains that Clone() is 
/// ambiguous. Consequently it is necessary to cast the range to <see 
/// cref="ICloneable{R}"/> just to clone it; if R is a value type then it is
/// boxed, which defeats the entire performance advantage of calling 
/// <c>ICloneable{R}.Clone()</c> instead of <c>ICloneable{IFRange{T}}.Clone</c>.
/// <para/>
/// Nevertheless, I have left the constraint in place, in the hope that EC# can 
/// eventually eliminate this limitation thanks to its "using" cast. Once EC#
/// compiles directly to CIL, <c>range(using ICloneable&lt;R>).Clone()</c> will 
/// not perform boxing.
/// </remarks>
public struct RangeEnumerator<R, T> : IEnumerator<T> where R : IFRange<T>, ICloneable<R>

from ecsharp.

qwertie avatar qwertie commented on May 31, 2024

This is one of two problems I remember hitting when I tried to implement D-style ranges in C#. The other one was that C# doesn't support covariant return types, which makes Clone() a massive hassle to implement...

// Since C# does not support covariant return types, implementing all the 
// ICloneables can be quite a chore. Just copy and paste these, inserting
// an appropriate constructor for your range type:
IFRange<T>  ICloneable<IFRange<T>>.Clone() { return Clone(); }
IBRange<T>  ICloneable<IBRange<T>>.Clone() { return Clone(); }
IRange<T>   ICloneable<IRange<T>> .Clone() { return Clone(); }
public MyType Clone() { return new MyType(...); }

EDIT: now I see three problems. The third is that PopFront(out bool fail) is inconvenient to use due to its out parameter. This can be solved with ref this extension methods, or by allowing users to declare out variables in-situ (as the C# team planned to do but mysteriously un-planned).

from ecsharp.

jonathanvdc avatar jonathanvdc commented on May 31, 2024

Good news, everyone. I've updated ecsc, so calls to methods on using cast-struct values don't result in boxing any more. So this

using System;

public class A
{
    public A()
    { }

    public override string ToString()
    {
        return "A";
    }
}

public class B : A
{
    public B()
    { }

    public override string ToString()
    {
        return "B";
    }
}

public struct Foo : ICloneable
{
    public int CloneCount { get; private set; }

    public object Clone() { CloneCount++; return this; }

    public override string ToString()
    {
        return CloneCount.ToString();
    }
}

public static class Program
{
    public static void Main()
    {
        B x = new B();
        Console.WriteLine(x using A);
        Console.WriteLine(x using B);
        Console.WriteLine(2 using double);
        var foo = default(Foo);
        Console.WriteLine((foo using ICloneable).Clone());
    }
}

prints the following output now:

B
B
2
1

This neat trick is accomplished by compiling

Console.WriteLine((foo using ICloneable).Clone());

as

IL_0028:  ldloca.s 1
IL_002a:  constrained. Foo
IL_0030:  callvirt instance object class [mscorlib]System.ICloneable::Clone()
IL_0035:  call void class [mscorlib]System.Console::WriteLine(object)

peverify (well, monodis, actually) seems to think that's okay, and both the Travis CI and the AppVeyor builds produce the expected result.

Compiling with the -pedantic switch currently produces the warnings below.

$ ecsc UsingCast.ecs -platform clr -pedantic

UsingCast.ecs:42:27: warning: EC# extension: the 'using' cast operator is an EC# extension. [-Wecs-using-cast]

    Console.WriteLine(x using A);
                      ^~~~~~~~~  

UsingCast.ecs:43:27: warning: EC# extension: the 'using' cast operator is an EC# extension. [-Wecs-using-cast]

    Console.WriteLine(x using B);
                      ^~~~~~~~~  

UsingCast.ecs:44:27: warning: EC# extension: the 'using' cast operator is an EC# extension. [-Wecs-using-cast]

    Console.WriteLine(2 using double);
                      ^~~~~~~~~~~~~~  

UsingCast.ecs:46:27: warning: EC# extension: the 'using' cast operator is an EC# extension. [-Wecs-using-cast]

    Console.WriteLine((foo using ICloneable).Clone());
                      ^~~~~~~~~~~~~~~~~~~~~~          

I'm open to suggestions for the warning message, though. I was thinking of changing it to:

UsingCast.ecs:42:27: warning: EC# extension: 'using' casts cannot be converted to plain C#. [-Wecs-using-cast]

    Console.WriteLine(x using A);
                      ^~~~~~~~~  

from ecsharp.

qwertie avatar qwertie commented on May 31, 2024

Well, it would be silly to warn about all the things that are "EC# extensions" (some of them your compiler could never even detect, like if I write ([#trivia_inParens] x) instead of the equivalent (x). I think a warning should only be printed when using a non-public member of a struct; if you're not doing that then in fact the code is representable in plain C#. Foo(x using IFoo) should not print a warning even if x is a struct, since boxing was going to happen anyway so the plain C# code (IFoo)x is equivalent.

from ecsharp.

qwertie avatar qwertie commented on May 31, 2024

Then again... it might be easiest to convert (x using IFoo).Foo(y) to plain C# as ((IFoo)x).Foo(y) even if Foo is public and the cast wasn't necessary. How about this warning: "warning: this usage of using cannot be translated faithfully to C#, because a cast requires boxing but using does not [-Wecs-using-cast]". P.S. whoa, do you have a separate switch for every warning?

from ecsharp.

qwertie avatar qwertie commented on May 31, 2024

Great work, by the way!

from ecsharp.

jonathanvdc avatar jonathanvdc commented on May 31, 2024

Thanks! I've changed the warning to print the message you suggested, and made ecsc display it only when a boxing conversion is elided.

The compiler's console output is now:

$ ecsc UsingCast.ecs -platform clr -pedantic

UsingCast.ecs:46:27: warning: EC# extension: this usage of 'using' cannot be translated faithfully to C#, because a cast requires boxing but 'using' does not. [-Wecs-using-cast]

    Console.WriteLine((foo using ICloneable).Clone());
                      ^~~~~~~~~~~~~~~~~~~~~~          

Yeah, every warning has its own switch in ecsc, just like in gcc and clang. Flame has support for warnings baked in, so adding a warning switch is actually sort of trivial. The following snippet of code is all it takes to define the -Wecs warning group and the -Wecs-using-cast warning.

/// <summary>
/// The -Wecs warning group.
/// </summary>
/// <remarks>
/// This is a -pedantic warning group.
/// </remarks>
public static readonly WarningDescription EcsExtensionWarningGroup = 
    new WarningDescription("ecs", Warnings.Instance.Pedantic);

/// <summary>
/// The -Wecs-using-cast warning.
/// </summary>
/// <remarks>
/// This is a -Wecs warning.
/// </remarks>
public static readonly WarningDescription EcsExtensionUsingCastWarning = 
    new WarningDescription("ecs-using-cast", EcsExtensionWarningGroup);

So -pedantic implies -Wecs, which in turn activates -Wecs-using-cast. But you can also specifically turn -Wecs-using-cast off by telling ecsc to -Wno-ecs-using-cast. I think that's lot more human-friendly than csc and mcs warning numbers, and gcc-style warning groups make it easy to add new groups of off-by-default warnings. That scheme doesn't break existing code that treats warnings as errors.

You can clone and build ecsc to play around with those warnings, if you want. ecsc is still very experimental, but the tests/cs contains some pure C# tests which compile successfully and correctly, under both -Og and -O3 optimization levels.

Contributions would be most welcome, too. But I'd totally understand if you'd rather spend your spare time doing other things.

from ecsharp.

qwertie avatar qwertie commented on May 31, 2024

I'm afraid I'm well behind on everything - here's my excuse.

I'm still working on LES. I'd like to play with ecsc when I find some time.

from ecsharp.

jonathanvdc avatar jonathanvdc commented on May 31, 2024

That sounds like quite the Kafkaesque ordeal you're going through there. But it's good to hear that you've got a somewhat decent set-up now. :)

You really don't owe me an explanation, though. I'd be delighted if you found the time to play with ecsc at some point, but please don't feel pressured to.

Speaking of LES, I'd like to add support for BigInteger literals to LES. (#40) Do you think that's a good idea?

Anyway, I think my question about the run-time and compile-time semantics of using conversions has been answered. Shall I mark this issue as closed?

from ecsharp.

qwertie avatar qwertie commented on May 31, 2024

Sure.

from ecsharp.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.